基于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)。
实现目标
- 全兼容Zero服务端(拿来即用)
- 兼容ABP官方代码生成器
- 使用ng-zorro最新版本
- 使用ng-alain 2.0+
开始
以下我只介绍实现过程中的几个重要过程,具体可参看项目源码。
- 关于shared module: ng-alain官方有shared module的相关介绍。Zero模板中也有类似功能的module:
src\shared\common\common.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中,这边我将其独立到src\shared\helpers\MessageProviderHelper.ts中(代码比较长就不贴了, 请自行查看源码),并在src\root.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[]方法,其实确切应该要overridegetHit这个方法,但很不幸它是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中。我们可以在src\app\shared\layout\nav\app-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);
});
}
在src\app\app.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。
基于ng-zorro的ASP.NET ZERO前端实现的更多相关文章
- 基于Microsoft Azure、ASP.NET Core和Docker的博客系统
欢迎阅读daxnet的新博客:一个基于Microsoft Azure.ASP.NET Core和Docker的博客系统 2008年11月,我在博客园开通了个人帐号,并在博客园发表了自己的第一篇博客 ...
- 基于DDD的现代ASP.NET开发框架--ABP系列之3、ABP分层架构
基于DDD的现代ASP.NET开发框架--ABP系列之3.ABP分层架构 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ABP的官方网站:ht ...
- 一种基于自定义代码的asp.net网站首页根据IP自动跳转指定页面的方法!
一种基于自定义代码的asp.net网站首页根据IP自动跳转指定页面的方法! 对于大中型网站,为了增强用户体验,往往需要根据不同城市站点的用户推送或展现相应个性化的内容,如对于一些大型门户网站的新闻会有 ...
- 基于HBuilderX+UniApp+ThorUI的手机端前端开发处理
现在的很多程序应用,基本上都是需要多端覆盖,因此基于一个Web API的后端接口,来构建多端应用,如微信.H5.APP.WInForm.BS的Web管理端等都是常见的应用.本篇随笔概括性的介绍基于HB ...
- FineUI 基于 ExtJS 的专业 ASP.NET 控件库
FineUI 基于 ExtJS 的专业 ASP.NET 控件库 http://www.fineui.com/
- 基于微软平台IIS/ASP.NET开发的大型网站有哪些呢?
首先说明一下,本文绝不是要说Microsoft平台多么好,多么牛.只是要提醒一些LAMP/JAVA平台下的同志们,微软平台不至于像你们说的,和想象的那么不堪!只是你们自己不知道而已.同时,也希望广大M ...
- 基于微软平台IIS/ASP.NET开发的大型网站有哪些?
首先说明一下,本文绝不是要说Microsoft平台多么好,多么牛.只是要提醒一些LAMP/Java平台下的同志们,微软平台不至于像你们说的,和想象的那么不堪!只是你们自己不知道而已.同时,也希望广大M ...
- 基于DDD的现代ASP.NET开发框架--ABP系列之2、ABP入门教程
基于DDD的现代ASP.NET开发框架--ABP系列之2.ABP入门教程 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boi ...
- (转)基于微软平台IIS/ASP.NET开发的大型网站有哪些?
首先说明一下,本文绝不是要说Microsoft平台多么好,多么牛.只是要提醒一些LAMP/JAVA平台下的同志们,微软平台不至于像你们说的,和想象的那么不堪!只是你们自己不知道而已.同时,也希望广大M ...
随机推荐
- qtchooser - a wrapper used to select between Qt development binary(2种方法)
---------------------------------------------------------------------------------------------------- ...
- 通过内核对象在服务程序和桌面程序之间通信的小问题 good
关于在通过 事件对象 在服务程序和普通桌面应用程序相互之间通信的问题,分类情况进行讨论:1.普通桌面应用程序中创建事件,服务程序中打开事件 XP的情况普通桌面应用程序中创建: m_hEvent = : ...
- qobject_cast<QPushButton*>(sender()) 简化信号与槽的编写(sender()取得发信号的对象后,就取得了它的全部信息,为所欲为)
当你觉得写代码是一件重复性极高的工作时,这时你就应该考虑换个方式来实现了. 提高代码效率,减少代码量. 代码片: void Widget::onClicked() { QPushButton* but ...
- java多线程之生产者-消费者
public class Product { private String lock; public Product(String lock) { this.lock = lock; } public ...
- 揭秘重度MMORPG手游后台性能优化方案
本文节选自<2018腾讯移动游戏技术评审标准与实践案例>手册,由腾讯互娱工程师王杰分享<仙剑奇侠传online>项目中游戏后台的优化经验,深度解析寻路算法.视野管理.内存优化. ...
- 【转载】Spring Boot引起的“堆外内存泄漏”排查及经验总结
背景 为了更好地实现对项目的管理,我们将组内一个项目迁移到MDP框架(基于Spring Boot),随后我们就发现系统会频繁报出Swap区域使用量过高的异常.笔者被叫去帮忙查看原因,发现配置了4G堆内 ...
- Demo小细节
(1) 程序如下: public class Example { static int i = 1, j = 2; static { display(i); i = i + j; } static v ...
- Python开发【第九篇】: 并发编程
内容概要 操作系统介绍 进程 线程 协程 二. 进程 python并发编程之多进程理论部分 在python程序中的进程操作 运行中的程序就是一个进程.所有的进程都是通过它的父进程来创建的.因此,运行起 ...
- 使用Python脚本伪造指定时间区间的数据库备份
为监管需求,需要保留时间非常长的数据库备份.存储代价太大.所以存在了,临时抱佛脚,伪造备份.. 以下脚本功能,在于根据一个备份,复制出一段时间的备份.并且更改备份的文件时间戳.可以用shell轻松写出 ...
- 为什么Java只有值传递?
形参和实参 形式参数,是在方法定义阶段,是定义某个函数时使用的参数,用于接收实参传入.例f(x,y)中x和y是形参. 实际参数,是在方法调用阶段,是主调函数调用有参函数时,实际传递的内容.例f(3,7 ...