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

  • 相关阅读:
    剑指Offer-30.连续子数组的最大和(C++/Java)
    剑指Offer-29.最小的K个数(C++/Java)
    UVA 1616 Caravan Robbers 商队抢劫者(二分)
    UVA 10570 Meeting with Aliens 外星人聚会
    UVA 11093 Just Finish it up 环形跑道 (贪心)
    UVA 12673 Erratic Expansion 奇怪的气球膨胀 (递推)
    UVA 10954 Add All 全部相加 (Huffman编码)
    UVA 714 Copying Books 抄书 (二分)
    UVALive 3523 Knights of the Round Table 圆桌骑士 (无向图点双连通分量)
    codeforecs Gym 100286B Blind Walk
  • 原文地址:https://www.cnblogs.com/FocusNet/p/10030749.html
Copyright © 2011-2022 走看看