一、NG-ALAIN安装 1.介绍 NG-ALAIN 是一个企业级中后台前端/设计解决方案脚手架,秉承 Ant Design 的设计价值观,目标也非常简单,希望在Angular上面开发企业后台更简单、更快速。它内嵌了NG-ZORRO 的组件库,用户体验较为完整。
2.安装
全局 Angular Cli 1 npm install -g @angular/cli
创建Angular项目 1 ng new my-project --style less --routing
添加NG-ALAIN
以上皆顺利进行后,来到package.json文件下,小做一点点修改:
1.将scripts 下的start 的”ng s -o” 改成 “ng s -o –port 80” (指定端口号是为了避免端口号占用,80端口号的好处,在网页上显示时会省略端口,仅个人喜好。其他端口号亦可,注意避免端口相同即可)
2.配置请求的URL环境
在src/environments/environment.ts下修改如下,其中SERVER_URL 为向后端请求的地址,url 为本地项目启动地址,记得带上端口号。
3.创建服务文件
在src/app下创建目录service和文件services.module.ts以便项目整体使用,文件内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { NgModule } from '@angular/core' ;import { environment } from './../environments/environment' ;@NgModule ({ declarations : [], imports : [ ] }) export class ServicesModule { public static getBackendUrl (): string { return environment.SERVER_URL ; } }
二、路由拦截与权限管理 1.准备工作 一说到登录这块,不可避免的就会涉及到路由拦截、设置token和权限管理等功能。
首先,为了模拟较为真实的登录环境,我们肯定需要个后台配合,后台仓库地址会在最后奉上,在这里不多做赘述。
只想提两点,
(1).我的登录接口的返回例子如下,接下来有用的就是roleCode、token以及permission这三个属性。
(2).个人原因密码没有做加密操作(懒!),有兴趣的请自行处理。
2.登录 简单改造一下登录页面的逻辑:
1)html部分 1.1)文案提示修改 在src/app/routes/passport/login下的login.component.html中,将账号密码的提示文案修改一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <nz-tab [nzTitle ]="'账户密码登录'" > <nz-alert *ngIf ="error" [nzType ]="'error'" [nzMessage ]="error" [nzShowIcon ]="true" class ="mb-lg" > </nz-alert > <nz-form-item > <nz-form-control nzErrorTip ="Please enter mobile number, muse be: admin or user" > <nz-input-group nzSize ="large" nzPrefixIcon ="user" > <input nz-input formControlName ="userName" placeholder ="username: rainlotus | saber " /> </nz-input-group > </nz-form-control > </nz-form-item > <nz-form-item > <nz-form-control nzErrorTip ="Please enter password, muse be: ng-alain.com" > <nz-input-group nzSize ="large" nzPrefixIcon ="lock" > <input nz-input type ="password" formControlName ="password" placeholder ="password: 123456" /> </nz-input-group > </nz-form-control > </nz-form-item > </nz-tab >
1.2)新建路由
来到src/app/routes下右键”在集成终端中打开”,新建两个组件example01和example02
1 2 ng g c example01 ng g c example02
在routes-routing.module.ts中修改路由配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { Example01Component } from './example01/example01.component' ;import { Example02Component } from './example02/example02.component' ;{ path : 'dashboard' , component : DashboardComponent , data : { title : '仪表盘' , titleI18n : 'dashboard' }, children : [ { path : 'example01' , component : Example01Component , }, { path : 'example02' , component : Example02Component , }, ] },
在src/app/routes/dashboard/dashboard.component.html简单修改一下
1 2 3 4 <page-header > </page-header > <button nz-button nzType ="primary" [routerLink ]="['/dashboard/example01']" > example01</button > <button nz-button nzType ="primary" [routerLink ]="['/dashboard/example02']" > example02</button > <router-outlet > </router-outlet >
2)ts部分 2.1)创建一个jwt的泛型接口 在src/app/core下创建一个文件夹名为entity,文件名称为myJwtModel.ts,内容如下:
1 2 3 4 5 6 import { JWTTokenModel } from '@delon/auth' ;export class MyJwtModel extends JWTTokenModel { nickname : string ; permissions : any ; }
2.2)登录请求 在login.component.ts中的新添及修改内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 import { MyJwtModel } from 'src/app/core/entity/myJwtModel' ; this .form = fb.group ({ userName : [null , [Validators .required , Validators .pattern (/^(rainlotus|saber)$/ )]], password : [null , [Validators .required , Validators .pattern (/^(123456)$/ )]], mobile : [null , [Validators .required , Validators .pattern (/^1\d{10}$/ )]], captcha : [null , [Validators .required ]], remember : [true ], }); submit (): void { this .error = '' ; if (this .type === 0 ) { this .userName .markAsDirty (); this .userName .updateValueAndValidity (); this .password .markAsDirty (); this .password .updateValueAndValidity (); if (this .userName .invalid || this .password .invalid ) { return ; } } else { this .mobile .markAsDirty (); this .mobile .updateValueAndValidity (); this .captcha .markAsDirty (); this .captcha .updateValueAndValidity (); if (this .mobile .invalid || this .captcha .invalid ) { return ; } } (this .http .post (ServicesModule .getBackendUrl () + 'passport/login?_allow_anonymous=true' , { username : this .userName .value , password : this .password .value , }) as any ) .subscribe ((res ) => { if (res.code !== '200' ) { this .error = res.msg ; return ; } this .reuseTabService .clear (); const myJwtModel = new MyJwtModel (); myJwtModel.nickname = res.user .username ; myJwtModel.user = res.user ; myJwtModel.token = res.user .token ; myJwtModel.permissions = res.user .permission ; this .tokenService .set (myJwtModel); this .startupSrv .load ().then (() => { console .log (myJwtModel.user .roleCode ); setTimeout (() => { if (myJwtModel.user .roleCode === 'superAdmin' ) { const permission = myJwtModel.user .permission ; const roles = []; for (const i in permission) { if (i) { roles.push (i); } } const menuList = ['example01' , 'example02' ]; const urlList = [ '/dashboard/example01' , '/dashboard/example02' , ]; let url = 'dashboard' ; if (roles.indexOf ('index' ) === -1 ) { for (let i = 0 ; i < menuList.length ; i++) { if (roles.indexOf (menuList[i]) !== -1 ) { url = urlList[i]; break ; } } } this .router .navigateByUrl (url); return ; } if (myJwtModel.user .roleCode === 'member' ) { const permission = myJwtModel.user .permission ; const roles = []; for (const i in permission) { if (i) { roles.push (i); } } const menuList = ['example01' , 'example02' ]; const urlList = [ '/dashboard/example01' , '/dashboard/example02' , ]; let url = 'dashboard' ; if (roles.indexOf ('index' ) === -1 ) { for (let i = 0 ; i < menuList.length ; i++) { if (roles.indexOf (menuList[i]) !== -1 ) { url = urlList[i]; break ; } } } this .router .navigateByUrl (url); return ; } }, 300 ); }); });
3)退出登录 这块没有改的,但是一套流程还是要走一遍的!
在src/app/layout/default/header/components/user.component.ts中,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { ChangeDetectionStrategy , Component , Inject } from '@angular/core' ;import { Router } from '@angular/router' ;import { DA_SERVICE_TOKEN , ITokenService } from '@delon/auth' ;constructor ( private settings: SettingsService, private router: Router, @Inject (DA_SERVICE_TOKEN) private tokenService: ITokenService ) {}logout (): void { this .tokenService .clear (); this .router .navigateByUrl (this .tokenService .login_url !); }
4)实际登录效果图
3.权限控制 1)介绍 ACL 全称叫访问控制列表(Access Control List),是一种非常简单的基于角色权限控制方式。一个完全独立 @delon/acl
模块。
2)粒度控制 来到src/app/core/startup/startup.service.ts文件,代码修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import { ACLService } from '@delon/acl' ;import { DA_SERVICE_TOKEN , ITokenService } from '@delon/auth' ;constructor ( private aclService: ACLService, @Inject (DA_SERVICE_TOKEN) private tokenService: ITokenService, ) { } private viaMock (resolve : any , reject : any ): any { const ACLS = this .tokenService .get (MyJwtModel ).user ; if (ACLS ) { const role = ACLS .roleCode ; this .aclService .setRole ([role]); } const app : any = { name : `ng-alain` , description : `Ng-zorro admin panel front-end framework` }; const user : any = { name : 'Admin' , avatar : './assets/tmp/img/avatar.jpg' , email : 'cipchk@qq.com' , token : '123456789' }; this .settingService .setApp (app); this .settingService .setUser (user); this .menuService .add ([ { text : 'Main' , group : true , children : [ { text : 'Dashboard' , link : '/dashboard' , icon : { type : 'icon' , value : 'appstore' } }, { text : 'Quick Menu' , icon : { type : 'icon' , value : 'rocket' }, shortcutRoot : true } ] } ]); this .titleService .suffix = app.name ; resolve ({}); }
在example01.component.ts中代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import { Component , Inject , Injector , OnInit } from '@angular/core' ;import { ACLService } from '@delon/acl' ;import { DA_SERVICE_TOKEN , ITokenService } from '@delon/auth' ;import { MyJwtModel } from 'src/app/core/entity/myJwtModel' ;@Component ({ selector : 'app-example01' , templateUrl : './example01.component.html' , styles : [ ] }) export class Example01Component implements OnInit { constructor ( private injector: Injector, private aclservice: ACLService, @Inject (DA_SERVICE_TOKEN) private tokenService: ITokenService ) { }ngOnInit (): void { console .log (this .tokenService .get (MyJwtModel ).user ); const ACLS = this .tokenService .get (MyJwtModel ).user ; const aclPermission = ACLS .permission ; const arr = Object .keys (aclPermission); this .aclservice .attachRole (arr); } }
在example01.component.html中代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 <h1 style ="margin-top: 30px;" > 权限管理测试</h1 > <h3 > 以下会有四个按钮分别为:example01、example02、index、xxx</h3 > <button nz-button nzType ="primary" > example01</button > <button nz-button nzType ="primary" > example02</button > <button nz-button nzType ="primary" > index</button > <button nz-button nzType ="primary" > xxx</button > <h3 style ="margin-top: 10px;" > 其中前三个后端返回的permission中实际就有的,index这个权限会根据账号不同显示或者隐藏,而xxx就是随便编的用做衬托</h3 > <h1 > 以下就是带上权限后的页面</h1 > <button nz-button nzType ="primary" [acl ]="['example01','superAdmin']" > example01</button > <button nz-button nzType ="primary" [acl ]="'example02'" > example02</button > <button nz-button nzType ="primary" [acl ]="'index'" > index</button > <button nz-button nzType ="primary" [acl ]="'xxx'" > xxx</button >
页面效果图如下:
3)路由守卫 来到路由配置文件,在此以routes-routing.module.ts举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import { ACLGuard } from '@delon/acl' ;{ path : 'dashboard' , component : DashboardComponent , canActivate : [ACLGuard ], data : { title : '仪表盘' , titleI18n : 'dashboard' , guard : 'superAdmin' }, children : [ { path : 'example01' , component : Example01Component , }, { path : 'example02' , component : Example02Component , }, ] },
页面效果图如下:
4.路由拦截 1)请求统一配置 在此只是做了统一的加上服务端前缀,请求头添加token处理。
在src/app/core/net/default.interceptor.ts中修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 constructor ( private injector: Injector, @Inject (DA_SERVICE_TOKEN) private tokenService: ITokenService, ) { }intercept (req : HttpRequest <any >, next : HttpHandler ): Observable <HttpEvent <any >> { const jwtModel = this .tokenService .get (JWTTokenModel ); let url = req.url ; if (!url.startsWith ('https://' ) && !url.startsWith ('http://' )) { url = environment.SERVER_URL + url; } let newReq = req.clone ({ url });if (jwtModel.token ) { newReq = req.clone ({ url, setHeaders : { Authorization : `Bearer ${jwtModel.token} ` }, }); } return next.handle (newReq).pipe ( mergeMap ((ev ) => { if (ev instanceof HttpResponseBase ) { return this .handleData (ev, newReq, next); } return of (ev); }), catchError ((err: HttpErrorResponse ) => this .handleData (err, newReq, next)), ); }
ps:官方原生的代码有问题,即NzNotificationService服务说没有引用,所以遇到这样情况,只需来到app.module.ts中引入即可。
1 2 3 4 import { NzNotificationService } from 'ng-zorro-antd/notification' ;providers : [ NzNotificationService , ],
2)路由设置 将SimpleGuard改为JWTGuard,并引入即可,目的是为了请求时token过期,会自动导向到登录界面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import { JWTGuard } from '@delon/auth' ; { path : '' , component : LayoutDefaultComponent , canActivate : [JWTGuard ], children : [ { path : '' , redirectTo : 'dashboard' , pathMatch : 'full' }, { path : 'dashboard' , component : DashboardComponent , data : { title : '仪表盘' , titleI18n : 'dashboard' , guard : 'superAdmin' }, children : [ { path : 'example01' , component : Example01Component , }, { path : 'example02' , component : Example02Component , }, ] }, ] }
3)请求实例演示 3.1)HTML部分 在src/app/routes/example02/example02.component.html中,修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <p style ="margin-top: 30px;" > example02 works!</p > <input nz-input style ="width: 300px;margin-right: 20px;" type ="tel" name ="" id ="" [(ngModel )]="telNUm" placeholder ="数字:1" ><input nz-input style ="width: 300px;margin-right: 20px;" type ="text" [(ngModel )]="nickName" placeholder ="名称:sega" > <button nz-button nzType ="primary" (click )="getInfo()" > 带token的post请求</button > <div style ="margin-top: 10px;" > <div *ngIf ="dataList" > <span style ="margin-right: 10px;" > name</span > <span > address</span > </div > <div *ngFor ="let item of dataList" > <span style ="margin-right: 10px;" > {{item.name}}</span > <span > {{item.address}}</span > </div > </div > <button style ="margin-top: 20px;" nz-button nzType ="primary" (click )="getData()" > 带token的get请求</button > <div style ="margin-top: 10px;" > <div *ngIf ="dataList" > <span style ="margin-right: 10px;" > name</span > <span > address</span > </div > <div *ngFor ="let item of orderList" > <span style ="margin-right: 10px;" > {{item.orderId}}</span > <span > {{item.address}}</span > </div > </div >
3.2)封装请求服务 在service文件夹下,创建一个服务,命令为:
服务代码内容如下,仅供参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import { HttpClient } from '@angular/common/http' ;import { Injectable } from '@angular/core' ;import { Observable , Subject } from 'rxjs' ;import { ServicesModule } from './services.module' ;@Injectable ({providedIn : 'root' }) export class Example02Service {constructor (private httpClient: HttpClient ) { }private COMB_URL = ServicesModule .getBackendUrl ();private EXAMPLE_URL = this .COMB_URL + 'passport/address' ;private ORDER_URL = this .COMB_URL + 'passport/order' ;postExample02Data (param : any ): any {return this .httpClient .post (this .EXAMPLE_URL , param);} getExample02Data (): any {return this .httpClient .get (this .ORDER_URL );} }
3.3)ts部分 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import { Component , OnInit } from '@angular/core' ;import { Example02Service } from 'src/app/service/example02.service' ;interface DataListType { name : string ; address : string ; } interface OrderDataType { orderId : string ; address : string ; } @Component ({ selector : 'app-example02' , templateUrl : './example02.component.html' , styles : [ ] }) export class Example02Component implements OnInit { telNUm = 1 ; nickName = 'sega' ; dataList; orderList; constructor ( private example02: Example02Service ) { } ngOnInit (): void { } getInfo (): void { const params = { id : Number (this .telNUm ), name : this .nickName }; this .example02 .postExample02Data (params).subscribe (res => { this .dataList = res.data as DataListType ; }); } getData (): void { this .example02 .getExample02Data ().subscribe (res => { this .orderList = res.data as OrderDataType ; }); } }
3.4)效果演示
三、国际化 Angular 国际化常见有 Angular 内置和基于 @ngx-translate/core (请参考官网 了解更多实现细节)两种不同国际化方案。而@ngx-translate/core 是社区版本的 Angular 国际化,相比较 Angular 内置它是动态性,无须针对不同语言构建和部署单独版本,并且大部分情况下可以立即呈现。
以下便是@ngx-translate/core的配置过程:
1)准备工作 1.1)安装插件 1 2 3 4 5 npm i @ngx-translate/core --save npm i @ngx-translate/http-loader --save
1.2)创建服务文件 在src/app/core文件夹下创建一个名为i18n的文件夹,以及旗下文件i18n.service.ts。
ps:之所以在这儿配置,其一ng-alian的github源码就是这么配置的;其二也是因为这种文件虽然属于服务一块,但同时它本身就属于项目一启动,全局配置使用的文件,放在这儿显然更好。当然,放在哪里还是你的自由。
i18n.service.ts的代码内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 import { Platform } from '@angular/cdk/platform' ;import { registerLocaleData } from '@angular/common' ;import ngEn from '@angular/common/locales/en' ;import ngZh from '@angular/common/locales/zh' ;import ngZhTw from '@angular/common/locales/zh-Hant' ;import ngJa from '@angular/common/locales/ja' ;import { Injectable } from '@angular/core' ;import { AlainI18NService , DelonLocaleService , en_US as delonEnUS, SettingsService , zh_CN as delonZhCn, zh_TW as delonZhTw, } from '@delon/theme' ; import { TranslateService } from '@ngx-translate/core' ;import { enUS as dfEn, zhCN as dfZhCn, zhTW as dfZhTw, ja as dfJaJp } from 'date-fns/locale' ;import { NzSafeAny } from 'ng-zorro-antd/core/types' ;import { en_US as zorroEnUS, ja_JP as zorroJaJp, NzI18 nService, zh_CN as zorroZhCN, zh_TW as zorroZhTW } from 'ng-zorro-antd/i18n' ;import { BehaviorSubject , Observable } from 'rxjs' ;import { filter } from 'rxjs/operators' ;interface LangData { abbr : string ; text : string ; ng : NzSafeAny ; zorro : NzSafeAny ; date : NzSafeAny ; delon : NzSafeAny ; } const DEFAULT = 'zh-CN' ;const LANGS : { [key : string ]: LangData } = { 'zh-CN' : { text : '简体中文' , ng : ngZh, zorro : zorroZhCN, date : dfZhCn, delon : delonZhCn, abbr : '🇨🇳' , }, 'zh-TW' : { text : '繁体中文' , ng : ngZhTw, zorro : zorroZhTW, date : dfZhTw, delon : delonZhTw, abbr : '🇭🇰' , }, 'en-US' : { text : 'English' , ng : ngEn, zorro : zorroEnUS, date : dfEn, delon : delonEnUS, abbr : '🇺🇸' , }, 'ja-JP' : { text : '日本语' , ng : ngJa, zorro : zorroJaJp, date : dfJaJp, delon : delonZhCn, abbr : '🇯🇵' , }, }; @Injectable ({ providedIn : 'root' })export class I18NService implements AlainI18NService { private _default = DEFAULT ; private change$ = new BehaviorSubject <string | null >(null ); private _langs = Object .keys (LANGS ).map ((code ) => { const item = LANGS [code]; return { code, text : item.text , abbr : item.abbr }; }); constructor ( private settings: SettingsService, private nzI18nService: NzI18nService, private delonLocaleService: DelonLocaleService, private translate: TranslateService, private platform: Platform, ) { const lans = this ._langs .map ((item ) => item.code ); translate.addLangs (lans); const defaultLan = this .getDefaultLang (); if (lans.includes (defaultLan)) { this ._default = defaultLan; } this .updateLangData (this ._default ); } private getDefaultLang (): string { if (!this .platform .isBrowser ) { return DEFAULT ; } if (this .settings .layout .lang ) { return this .settings .layout .lang ; } return (navigator.languages ? navigator.languages [0 ] : null ) || navigator.language ; } private updateLangData (lang : string ): void { const item = LANGS [lang]; registerLocaleData (item.ng ); this .nzI18nService .setLocale (item.zorro ); this .nzI18nService .setDateLocale (item.date ); this .delonLocaleService .setLocale (item.delon ); } get change (): Observable <string > { return this .change$ .asObservable ().pipe (filter ((w ) => w != null )) as Observable <string >; } use (lang : string ): void { lang = lang || this .translate .getDefaultLang (); if (this .currentLang === lang) { return ; } this .updateLangData (lang); (this .translate .use (lang) as any ).subscribe (() => this .change$ .next (lang)); } getLangs (): Array <{ code : string ; text : string ; abbr : string }> { return this ._langs ; } fanyi (key : string , interpolateParams?: {}): any { return this .translate .instant (key, interpolateParams); } get defaultLang (): string { return this ._default ; } get currentLang (): string { return this .translate .currentLang || this .translate .getDefaultLang () || this ._default ; } }
1.3)语言文件 在assets文件夹下,添加i18n文件夹,因为上述服务文件共配置了四种语言,因此,这里我们也添加了四个json文件,名称与上述服务文件中LANGS 变量的属性名一致,即”en-US.json”,”ja-JP.json”,”zh-CN.json”,”zh-TW.json”
1 2 3 4 5 6 { "GENERAL.I18N.CHINESE" : "简体中文" , "GENERAL.I18N.PRO.APPLY" : "立即试用" , "GENERAL.I18N.NUM" : "数字是{{num}}个" }
1 2 3 4 5 6 { "GENERAL.I18N.CHINESE" : "簡體中文" , "GENERAL.I18N.PRO.APPLY" : "立即試用" , "GENERAL.I18N.NUM" : "數字是{{num}}個" }
1 2 3 4 5 6 { "GENERAL.I18N.CHINESE" : "English" , "GENERAL.I18N.PRO.APPLY" : "Try now" , "GENERAL.I18N.NUM" : "The Num is {{num}}" }
1 2 3 4 5 6 { "GENERAL.I18N.CHINESE" : "日本语" , "GENERAL.I18N.PRO.APPLY" : "すぐに試用する" , "GENERAL.I18N.NUM" : "数字は{{num}}つあります" }
2)国际化配置 2.1)app.module.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import { ALAIN_I18N_TOKEN } from '@delon/theme' ;import { TranslateLoader , TranslateModule } from '@ngx-translate/core' ;import { TranslateHttpLoader } from '@ngx-translate/http-loader' ;import { I18NService } from '../app/core/i18n/i18n.service' ;export function I18nHttpLoaderFactory (http: HttpClient ): any { return new TranslateHttpLoader (http, `assets/i18n/` , '.json' ); } const I18NSERVICE_MODULES = [ TranslateModule .forRoot ({ loader : { provide : TranslateLoader , useFactory : I18nHttpLoaderFactory, deps : [HttpClient ], }, }), ]; const I18NSERVICE_PROVIDES = [{ provide : ALAIN_I18N_TOKEN , useClass : I18NService, multi : false }];@NgModule ({ imports : [ ...I18NSERVICE_MODULES , ], providers : [ ...I18NSERVICE_PROVIDES , ], })
ps:@delon/*
类库有许多带有 i18n 字样的数据接口属性(例如:page-header
、st
列描述、Menu
菜单数据等等),当你希望这些组件的数据接口能动态根据 Key 值按当前语言自动切换时,你还需要对 ALAIN_I18N_TOKEN
定义一个自实现服务接口(例如:I18NService ),并在根模块下注册。
2.2)请求拦截修改 在src/app/core/net/default.interceptor.ts下对intercept 方法做进一步的拦截修改,完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 intercept (req : HttpRequest <any >, next : HttpHandler ): Observable <HttpEvent <any >> {const jwtModel = this .tokenService .get <JWTTokenModel >(JWTTokenModel );let url = req.url ;if (url.indexOf ('assets' ) !== -1 ) { url = url; } else { if (!url.startsWith ('https://' ) && !url.startsWith ('http://' )) {url = environment.SERVER_URL + url; } } let newReq = req.clone ({ url });if (jwtModel.token ) {newReq = req.clone ({ url, setHeaders : { Authorization : `Bearer ${jwtModel.token} ` }, }); } return next.handle (newReq).pipe (mergeMap ((ev ) => {if (ev instanceof HttpResponseBase ) { return this .handleData (ev, newReq, next); }return of (ev);}), catchError ((err: HttpErrorResponse ) => this .handleData (err, newReq, next)),); }
2.3)startup.service.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 import { HttpClient } from '@angular/common/http' ;import { Inject , Injectable , Injector } from '@angular/core' ;import { Router } from '@angular/router' ;import { ACLService } from '@delon/acl' ;import { DA_SERVICE_TOKEN , ITokenService } from '@delon/auth' ;import { ALAIN_I18N_TOKEN , MenuService , SettingsService , TitleService } from '@delon/theme' ;import { zip } from 'rxjs' ;import { catchError } from 'rxjs/operators' ;import { TranslateService } from '@ngx-translate/core' ;import { NzIconService } from 'ng-zorro-antd/icon' ;import { ICONS } from '../../../style-icons' ;import { ICONS_AUTO } from '../../../style-icons-auto' ;import { MyJwtModel } from '../entity/myJwtModel' ;import { I18NService } from '../i18n/i18n.service' ;constructor ( iconSrv: NzIconService, private menuService: MenuService, private settingService: SettingsService, private aclService: ACLService, private titleService: TitleService, @Inject (DA_SERVICE_TOKEN) private tokenService: ITokenService, private httpClient: HttpClient, private injector: Injector, private translate: TranslateService, @Inject (ALAIN_I18N_TOKEN) private i18n: I18NService, ) { iconSrv.addIcon (...ICONS_AUTO , ...ICONS ); } private viaMock (resolve : any , reject : any ): any { const ACLS = this .tokenService .get (MyJwtModel ).user ; if (ACLS ) { const role = ACLS .roleCode ; this .aclService .setRole ([role]); } let language = 'zh-CN' ; let lang = '简体中文' ; if (!this .settingService .layout .lang ) { if (navigator.language .startsWith ('zh' )) { language = 'zh-CN' ; lang = '简体中文' ; } else if (navigator.language .startsWith ('en' )) { language = 'en-US' ; lang = 'English' ; } else { language = 'zh-CN' ; lang = '简体中文' ; } } else { language = this .settingService .layout .lang ; } (this .httpClient .get (`assets/i18n/${language} .json?nocache=${new Date ().getTime()} ` ) as any ) .subscribe (langData => { this .translate .setTranslation (language, langData); this .translate .setDefaultLang (language); this .i18n .use (language); this .settingService .setLayout ('lang' , language); resolve ({}); }); const app : any = { name : `ng-alain` , description : `Ng-zorro admin panel front-end framework` }; const user : any = { name : 'Admin' , avatar : './assets/tmp/img/avatar.jpg' , email : 'cipchk@qq.com' , token : '123456789' }; this .settingService .setApp (app); this .settingService .setUser (user); this .menuService .add ([ { text : 'Main' , group : true , children : [ { text : 'Dashboard' , link : '/dashboard' , icon : { type : 'icon' , value : 'appstore' } }, { text : 'Quick Menu' , icon : { type : 'icon' , value : 'rocket' }, shortcutRoot : true } ] } ]); this .titleService .suffix = app.name ; resolve ({}); }
2.4)shared.module.ts 1 2 3 4 5 6 import { TranslateModule } from '@ngx-translate/core' ; @NgModule ({ exports : [ TranslateModule , ] })
3)页面引用 以下为偷懒写法:
在src/app/routes/example02/example02.component.html中添加:
1 2 3 4 5 6 7 <ul > <li *ngFor ="let item of langs" (click )="change(item.code)" style ="width: 120px;height: 36px;cursor: pointer;" > {{ item.text }} </li > </ul > <h1 > {{'GENERAL.I18N.PRO.APPLY'|translate}}</h1 > <h3 > 当前语言为-------{{'GENERAL.I18N.CHINESE'|translate}} </h3 >
在login.component.ts中添加代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { Component , Inject , OnInit } from '@angular/core' ;import { ALAIN_I18N_TOKEN , SettingsService } from '@delon/theme' ;import { I18NService } from 'src/app/core/i18n/i18n.service' ; get langs (): any {return this .i18n .getLangs ();} constructor (@Inject (ALAIN_I18N_TOKEN) private i18n: I18NService,private settings: SettingsService ) {}change (lang : string ): void {this .i18n .use (lang);this .settings .setLayout ('lang' , lang);const currentLang = this .i18n .currentLang ; console .log (currentLang);}
4)效果演示