zoukankan      html  css  js  c++  java
  • 基于ng-zorro的ASP.NET ZERO前端实现

    Abp官方提供的企业版(ASP.NET ZERO)[以下简称Zero]模板中前端使用的是Metronic,本篇博客介绍使用ng-zorro和ng-alain替换官方前端,以及使用官方生成器自动生成代码。

    为什么要重复造轮子?

    ng-zorro与Abp结合其实已经有很多例子了,但好像它们都是使用了ng-alain的项目结构,导致无法使用Abp官方的代码生成器,因此必须造一轮子符合 ASP.NET Zero前端的代码结构。

    关于52ABP##

    项目参考及使用了52Abp的很多代码。52Abp早期代码使用abp-ng2-module以及delon的相关包,后来作者将这些包整合进自己的yoyo-moudle包并实现了一些自定义。考虑更新速度(我喜欢偿新),我使用了官方abp-ng2-module和delon的package,一些比较特殊的功能通过扩展来实现(比如下面的MenuService)。

    实现目标

    1. 全兼容Zero服务端(拿来即用)
    2. 兼容ABP官方代码生成器
    3. 使用ng-zorro最新版本
    4. 使用ng-alain 2.0+

    开始

    以下我只介绍实现过程中的几个重要过程,具体可参看项目源码。

    • 关于shared module: ng-alain官方有shared module的相关介绍。Zero模板中也有类似功能的module: srcsharedcommoncommon.module.ts, 所以就没有使用Shared module,而是把其中的功能都放到CommonModule里面供其他moudle引入。
    @NgModule({
    	imports: [
    		ngCommon.CommonModule,
    		AbpModule,
    		NgZorroAntdModule,
    		DelonABCModule,
    		AlainThemeModule.forChild()
    	],
    	exports: [
    		AbpModule,
    		NgZorroAntdModule,
    		AlainThemeModule,
    		DelonABCModule,
    	],
    	providers: [
    		ModalHelper,
    	],
    })
    
    • 使用ng-zorro的消息提示:这一步使用了52Abp的代码,52Abp代码位于AppConsts.ts中,这边我将其独立到srcsharedhelpersMessageProviderHelper.ts中(代码比较长就不贴了, 请自行查看源码),并在src oot.component.ts中调用:
    export class RootComponent implements OnInit {
    	constructor(
    		private _modalService: NzModalService,
    		private _messageService: NzMessageService,
    		private _notifyService: NzNotificationService,
    	) {}
    
    	ngOnInit(): void {
    		MessageProviderHelper.useNgZorroMessage(this._messageService, this._modalService);
    		MessageProviderHelper.useNgZorroNotify(this._notifyService);
    	}
    }
    
    • delon中有个Page-Header组件可以很方便的生成breadcrumb,但该功能只有在匹配到完整URL的时候才会自动生成。在Zero中有一个LanguageTexts的路由很特殊,它长这个样'languagetexts/:name/texts',在打开该页面的时候就无法完整匹配,也即无法生成Page-Header breadcrumb。解决方式其实很简单,查看Page-Header源码发现其依赖MenuService服务生成breadcrumb。那么我们就可以通过继承MenuService服务,override其中的getPathByUrl(url: string, recursive = false): Menu[]方法,其实确切应该要override getHit这个方法,但很不幸它是private的,无法override, 所以只能重写getPathByUrl,完整代码如下:
    import { Injectable } from '@angular/core';
    import { MenuService, Menu } from '@delon/theme';
    
    @Injectable()
    export class AbpMenuService extends MenuService {
    	getPathByUrl(url: string, recursive = false): Menu[] {
    		const ret: Menu[] = [];
    		let item = this.getHitEx(url, recursive);
    
    		if (!item) { return ret; }
    
    		do {
    			ret.splice(0, 0, item);
    			item = item.__parent;
    		} while (item);
    
    		return ret;
    	}
    
    	private getHitEx(url: string, recursive = false, cb: (i: Menu) => void = null) {
    		let item: Menu = null;
    
    		while (!item && url) {
    			this.visit(i => {
    				if (cb) {
    					cb(i);
    				}
    				// 或条件就是修改增加的
    				if ((i.link != null && i.link === url) || (!(i.children && i.children.length) && url.startsWith(i.link))) {
    					item = i;
    				}
    			});
    
    			if (!recursive) { break; }
    
    			url = url
    				.split('/')
    				.slice(0, -1)
    				.join('/');
    		}
    
    		return item;
    	}
    
    }
    

    接下去在delon.module.ts中将默认的MenuService替换成我们自己的:

    export class DelonModule {
    	constructor(
    		@Optional()
    		@SkipSelf()
    		parentModule: DelonModule,
    	) { }
    	static forRoot(): ModuleWithProviders {
    		return {
    			ngModule: DelonModule,
    			providers: [
    				{
    					provide: PageHeaderConfig, useFactory: pageHeaderConfig
    				},
    				{
    					provide: MenuService, useClass: AbpMenuService, multi: false
    				}
    			],
    		};
    	}
    }
    
    • 使用delon sidebar-nav组件:Zero模板和delon都有自己的Menu定义,要想直接使用sidebar-nav组件,必须将Zero的Menu定义转成delon的Menu定义,并添加到delon 的MenuService中。我们可以在srcappsharedlayout avapp-navigation.service.ts内增加一个辅助方法来完成该工作:
    mapToNgAlainMenu() {
    	let menu = this.getMenu();
    	let ngAlainRootMenu = <Menu>{
    		text: this._localizationService.l(menu.name),
    		group: false,
    		hideInBreadcrumb: true,
    		children: []
    	};
    	this.generateNgAlainMenus(ngAlainRootMenu.children, menu.items);
    
    	let ngAlainMenus = [
    		ngAlainRootMenu
    	];
    	this._ngAlainMenuService.add(ngAlainMenus);
    }
    
    generateNgAlainMenus(ngAlainMenus: Menu[], appMenuItems: AppMenuItem[]) {
    	appMenuItems.forEach(item => {
    		let ngAlainMenu: Menu;
    
    		ngAlainMenu = {
    			text: this._localizationService.l(item.name),
    			link: item.route,
    			icon: `${item.icon}`,
    			hide: !this.showMenuItem(item)
    		};
    
    		if (item.items && item.items.length > 0) {
    			ngAlainMenu.children = [];
    			this.generateNgAlainMenus(ngAlainMenu.children, item.items);
    		}
    
    		ngAlainMenus.push(ngAlainMenu);
    	});
    }
    

    srcappapp.component.ts构造函数中使用:

    constructor(        
            _appNavigationService: AppNavigationService
        ) {
            super(injector);
    
    		_appNavigationService.mapToNgAlainMenu();
    		
    		// ...
    }
    
    • 关于国际化:ng-zorro国际化在文档中描述的使用方法在这里,无论是“全局配置”还是“运行时修改”都需要在代码里硬导入相关语言模块。我们在结合AspNet zero使用的时候会遇到一个问题,Zero的多语言信息是从服务端动态获取到的,我们需要以一种动态的方式来设置多语言。其实在官方AspNet Zero前端模板里已经有实现方式,代码如下:
    function registerLocales(resolve: (value?: boolean | Promise<boolean>) => void, reject: any) {
        if (shouldLoadLocale()) {
            let angularLocale = convertAbpLocaleToAngularLocale(abp.localization.currentLanguage.name);
            import(`@angular/common/locales/${angularLocale}.js`)
                .then(module => {
                    registerLocaleData(module.default);
                    resolve(true);
                }, reject);
        } else {
            resolve(true);
        }
    }
    

    这里关键是运用WebPack的Dynamic Import Expression方式通过import语句动态导入语言模块,然后进行设置。此处相关知识可参考这篇blog:Dynamic Import of Locales in Angular

    接下来我们实现ng-zorro的国际化动态导入:

    首先,因为abp服务端的语言标志字符串和ng-zorro的有些许区别,所以必须建立一个Map来进行转换,可以把这个map写在appconfig.json中:

    "ngZorroLocaleMappings": [
        {
          "from": "en",
          "to": "en_US"
        },
        {
          "from": "zh-Hans",
          "to": "zh_CN"
        }
      ]
    

    以下代码位于root.module.ts

    建立一个方法进行转换:

    export function convertAbpLocaleToNgZorroLocale(locale: string): string {
        if (!AppConsts.ngZorroLocaleMappings) {
            return locale;
        }
    
        let localeMapings = _.filter(AppConsts.ngZorroLocaleMappings, { from: locale });
        if (localeMapings && localeMapings.length) {
            return localeMapings[0]['to'];
        }
    
        return locale;
    }
    

    最后实现动态配置:

    function registerNgZorroLocales(injector: Injector) {
        if (shouldLoadLocale()) {
            let ngZorroLcale = convertAbpLocaleToNgZorroLocale(abp.localization.currentLanguage.name);
            import(`ng-zorro-antd/esm5/i18n/languages/${ngZorroLcale}.js`)
                .then(module => {
                    let nzI18nService = injector.get(NzI18nService);
                    nzI18nService.setLocale(module.default);
                });
        }
    }
    

    结束

    平生第一次发文,不足之处请谅解。下一篇写关于使用ABP生成器生成全部代码,敬请期待。

    项目参考

    One More thing

    最最最重要的当然是上源码啦:https://github.com/rqx110/abp-ng-zorro,因 ASP.NET Zero是商业项目,这边只提供前端,不提供后端的代码。

    觉得还可以,请不要吝啬你的Star

  • 相关阅读:
    【ASP.Net MVC】在AspNet Mvc使用JQuery AutoComplete组件
    Jquery AutoComplete的使用方法实例
    .Net使用Redis详解之ServiceStack.Redis(七)
    Redis系列之key操作命令与Redis中的事务详解(六)
    Redis数据结构详解之Zset(五)
    redis数据结构详解之Hash(四)
    Redis数据结构详解之Set(三)
    Redis数据结构详解之List(二)
    Redis数据结构详解(一)
    WCF配置文件详解(一)
  • 原文地址:https://www.cnblogs.com/FocusNet/p/10030749.html
Copyright © 2011-2022 走看看