Angular学习
环境搭建
NodeJS 安装
C:\Users\hesai>node -v v12.22.3
cnpm 安装 淘宝镜像
npm install -g cnpm --registry=https://registry.npmmirror.com cnpm install
安装 Angular CLI
npm install -g @angular/cli #查看ng 版本 C:\Users\hesai>ng version _ _ ____ _ ___ / \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _| / △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | | / ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | | /_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___| |___/ Angular CLI: 11.2.14 Node: 12.22.3 OS: win32 x64 Angular: ... Ivy Workspace: Package Version ------------------------------------------------------ @angular-devkit/architect 0.1102.14 (cli-only) @angular-devkit/core 11.2.14 (cli-only) @angular-devkit/schematics 11.2.14 (cli-only) @schematics/angular 11.2.14 (cli-only) @schematics/update 0.1102.14 (cli-only)
运行条件
由于目前各种环境(浏览器或 Node)暂不支持ES6的代码,所以需要一些shim和polyfill(IE需要)让ES6写的代码能够转化为ES5形式并可以正常运行在浏览器中。
-
systemjs - 通用模块加载器,支持AMD、CommonJS、ES6等各种格式的JS模块加载。
-
es6-module-loader - ES6模块加载器,systemjs会自动加载这个模块。
-
traceur - ES6转码器,将ES6代码转换为当前浏览器支持的ES5代码,systemjs会自动加载 这个模块。
版本升级
12.2.13 #卸载当前版本 $ npm uninstall -g angular-cli $ npm uninstall -g @angular/cli #清除未卸载干净的angular-cli缓存 $ npm cache clean -f #删除安装文件夹 #安装最新版本 $ npm install -g @angular/cli@12 或者 $ yarn global add @angular/cli@12
遇到问题
ng serve --open 运行报错
ng : 无法加载文件 C:\Users\dell\AppData\Roaming\npm\ng.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/go.microsoft.com/fwlink/?LinkID=135170 中的 about_Execution_ Policies。 所在位置 行:1 字符: 1 + ng serve --open + ~~ + CategoryInfo : SecurityError: (:) [],PSSecurityException + FullyQualifiedErrorId : UnauthorizedAccess #原因 powershell 权限问题 #1.在win10 系统中搜索框 输入 Windows PowerShell,选择 管理员身份运行 #2、使用,win+R打开了powershell命令行之后,输入set-ExecutionPolicy RemoteSigned,然后更改权限为A,最后通过 get-ExecutionPolicy 查看当前的状态
项目结构
项目目录说明
|-- 文件名 // 说明 |-- 首层目录 |-- e2e // 自动化集成测试目录 |-- node_modules // npm/cnpm第三方依赖包存放目录 |-- src // 应用源代码目录 |-- .editorconfig // 不同编译器统一代码风格 |-- .gitignore // git中的忽略文件列表 |-- angular.json // Angular的配置文件 |-- browserslist // 配置浏览器兼容性的文件 |-- karma.conf.js // 自动化测试框架Karma的配置文件 |-- package-lock.json // 依赖包版本锁定文件 |-- package.json // 标准的npm工具的配置文件 |-- README.md // 项目说明的MakeDown文件 |-- tsconfig.app.json // app项目的TypeScript的配置文件 |-- tsconfig.json // 整个工作区的TypeScript配置文件 |-- tsconfig.spec.json // 用于测试的TypeScript配置文件 |-- tslint.json // TypeScript的代码静态扫描配置 |-- src目录 |-- app // 工程源码目录 |-- assets // 资源目录 |-- environments // 环境配置目录 |-- favicon.ico // header里的icon |-- index.html // 单页应用的宿主HTML |-- main.ts // 入口ts文件 |-- polyfills.ts // 不同浏览器兼容脚本加载 |-- karma.conf.js // 自动化测试框架Karma的配置文件 |-- style.css // 整个项目的全局css |-- test.ts // 测试入口 |-- app目录 |-- app-routing.module.ts // app路由 |-- app.component.css // app的css |-- app.component.html // app的html |-- app.component.spec.ts // app的测试 |-- app.component.ts // app的组件 |-- app.module.ts // app的模块 |-- environments目录 |-- environments.prod.ts // 生产环境 |-- environments.ts // 开发环境
常用命令
$ ng g component #创建组件 $ ng g module #创建模块 $ ng g service #创建服务 $ ng g interface #创建接口
常用指令
指令 | 作用 |
---|---|
ngSubmit | 表单提交 |
基础知识
指令
[(ngModel)] : 双向绑定
构建
bundles - UMD 模块格式。
esm5 - 主要使用 es5 的模块格式,但也使用来自 es6 的导入/导出语法。
esm2015 - 使用 es2015/es6 的模块格式。
fesm5 - esm5 的扁平化版本。
fesm2015 -peerDependencies esm2015 的扁平化版本。
lib - 库的 TypeScript 定义。
名词定义
什么是UMD
所谓UMD (Universal Module Definition)
,就是一种javascript
通用模块定义规范,让你的模块能在javascript
所有运行环境中发挥作用。
模块
组件
接口
创建接口
$ ng g interface model/logBook/baseProject/BaseProjectDTO
使用接口
//imoprt 导入
服务
创建服务
$ ng g service services/storage.service
服务使用
// 1.app.module.ts 里面引入创建的服务 import { StorageService } from './services/storage.service'; // 2. NgModule 里面的 providers 里面依赖注入服务 @NgModule({ declarations: [ AppComponent, HeaderComponent, FooterComponent, NewsComponent, TodolistComponent ], imports: [ BrowserModule, FormsModule ], providers: [StorageService], bootstrap: [AppComponent] }) export class AppModule { } //3、使用的页面引入服务,注册服务 import { StorageService } from '../../services/storage.service'; constructor(private storage: StorageService) { }
组件通信
@ViewChild
属性装饰器,用于配置一个视图查询。 变更检测器会在视图的 DOM 中查找能匹配上该选择器的第一个元素或指令。 如果视图的 DOM 发生了变化,出现了匹配该选择器的新的子节点,该属性就会被更新。
主要用来父组件获取子组件
@Input
一个装饰器,用来把某个类字段标记为输入属性,并提供配置元数据。 该输入属性会绑定到模板中的某个 DOM 属性。当变更检测时,Angular 会自动使用这个 DOM 属性的值来更新此数据属性。
主要用来父组件给子组件传值或者方法
@OutPut
一个装饰器,用于把一个类字段标记为输出属性,并提供配置元数据。 凡是绑定到输出属性上的 DOM 属性,Angular 在变更检测期间都会自动进行更新。
子组件触发父组件的方法
路由
NG ZORRO
项目搭建
创建项目
$ ng new ngproject $ ng new ngproject --skip-install #跳过安装
运行
$ cd ngproject $ ng serve --open #启动项目
安装ng zorro
$ ng add ng-zorro-antd
搭建项目结构
#1、创建登陆模块 $ ng g module module/login --routing #带路由的模块 #2、创建组件 $ ng g component module/login #3、创建其他模块及组件 $ ng g module module/default --routing $ ng g component module/default $ ng g component module/default/home $ ng g component module/default/report $ ng g component module/default/keywords $ ng g component module/default/alarm
配置路由
根路由(AppRoutingModule)路由懒加载
//根路由(AppRoutingModule)中配置子路由 import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; const routes: Routes = [{ path: 'login', loadChildren: () => import('./module/login/login.module').then(m => m.LoginModule) //登陆模块 },{ path: 'default', loadChildren: () => import('./module/default/default.module').then(m => m.DefaultModule) //首页 }, { path: '**', pathMatch: 'full', redirectTo: 'login' }]; //配置没有路径时跳转到login @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
根模块中加载动态模块
<!--app.component.html中增加以下代码 --> <router-outlet></router-outlet>
登陆及首页路由配置
//登陆模块 import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { LoginComponent } from './login.component'; const routes: Routes = [ { path: '', component: LoginComponent }, ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class LoginRoutingModule { } //首页 import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { DefaultComponent } from './default.component'; import { HomeComponent } from './home/home.component'; import { ReportComponent } from './report/report.component'; import { KeywordsComponent } from './keywords/keywords.component'; import { AlarmComponent } from './alarm/alarm.component'; import { LoginGuard } from '../../service/login.guard'; const routes: Routes = [ { path: '', component: DefaultComponent, canActivate:[LoginGuard], children:[ //配置子组件路由 { path: 'home', component: HomeComponent }, { path: 'report', component: ReportComponent }, { path: 'keywords', component: KeywordsComponent }, { path: 'alarm', component: AlarmComponent }, { path: '**', component: HomeComponent } ] } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class DefaultRoutingModule { }
布局配置
//首页模块中引入nz 布局模块 DefaultModule //antd对应的模块 import { NZ_I18N } from 'ng-zorro-antd/i18n'; import { zh_CN } from 'ng-zorro-antd/i18n'; //布局组件需要引入的模块 import { NzLayoutModule } from 'ng-zorro-antd/layout'; providers: [{ provide: NZ_I18N, useValue: zh_CN }] //引入服务
首页布局组件使用 src\app\module\default\default.component.html
<nz-layout> <nz-sider nzCollapsible [(nzCollapsed)]="isCollapsed" [nzTrigger]="null"> <div class="logo"></div> <ul nz-menu nzTheme="dark" nzMode="inline" > <li nz-submenu nzTitle="起始页码" nzIcon="user" nzOpen> <ul> <li nz-menu-item> <a [routerLink]="['/default/home']" routerLinkActive="router-link-active" >首页</a> </li> </ul> </li> <li nz-submenu nzTitle="舆情报告" nzIcon="team"> <ul> <li nz-menu-item> <a [routerLink]="['/default/report']" routerLinkActive="router-link-active" >全部舆情</a> </li> <li nz-menu-item> <a [routerLink]="['/default/report']" routerLinkActive="router-link-active" >正面舆情</a> </li> <li nz-menu-item> <a [routerLink]="['/default/report']" routerLinkActive="router-link-active" >负面舆情</a> </li> </ul> </li> <li nz-submenu nzTitle="舆情设置" nzIcon="team"> <ul> <li nz-menu-item> <a [routerLink]="['/default/keywords']" routerLinkActive="router-link-active" >舆情关键词设置</a> </li> <li nz-menu-item> <a [routerLink]="['/default/alarm']" routerLinkActive="router-link-active" >舆情报警设置</a> </li> </ul> </li> </ul> </nz-sider> <nz-layout> <!-- <nz-header> <i class="trigger" nz-icon [nzType]="isCollapsed ? 'menu-unfold' : 'menu-fold'" (click)="isCollapsed = !isCollapsed"></i> </nz-header> --> <nz-content> <!-- <nz-breadcrumb> <nz-breadcrumb-item>User</nz-breadcrumb-item> <nz-breadcrumb-item>Bill</nz-breadcrumb-item> </nz-breadcrumb> --> <div class="inner-content"> <router-outlet></router-outlet> </div> </nz-content> <nz-footer>Ant Design ©2020 Implement By Angular</nz-footer> </nz-layout> </nz-layout>
Button Icon
按钮的使用
//1、引入按钮模块 import { NzButtonModule } from 'ng-zorro-antd/button'; //nzType 按钮类型 <button nz-button nzType="primary">Primary Button</button> //禁用 <i nz-icon nzType="stop" nzTheme="outline"></i> //启用 <i nz-icon nzType="check" nzTheme="outline"></i>
问题
-
部分图标无法使用
ng-alain默认只导入了图标库的几十个图标,在 style-icons-auto.ts
可进行查看
如果有需要可以在 style-icons-auto.ts
中进入import and export
Message
全局展示操作反馈信息
//使用方式 import { NzMessageModule } from 'ng-zorro-antd/message'; constructor(private message: NzMessageService) {} createBasicMessage(): void { this.message.success('This is a normal message'); }
modal
import { Component } from '@angular/core'; @Component({ selector: 'nz-demo-modal-footer', template: ` <button nz-button nzType="primary" (click)="showModal()"> <span>Show Modal</span> </button> <nz-modal [(nzVisible)]="isVisible" [nzTitle]="modalTitle" [nzContent]="modalContent" [nzFooter]="modalFooter" (nzOnCancel)="handleCancel()" > <ng-template #modalTitle>Custom Modal Title</ng-template> <ng-template #modalContent> <p>Modal Content</p> <p>Modal Content</p> <p>Modal Content</p> <p>Modal Content</p> <p>Modal Content</p> </ng-template> <ng-template #modalFooter> <span>Modal Footer:</span> <button nz-button nzType="default" (click)="handleCancel()">Custom Callback</button> <button nz-button nzType="primary" (click)="handleOk()" [nzLoading]="isConfirmLoading">Custom Submit</button> </ng-template> </nz-modal> ` }) export class NzDemoModalFooterComponent { isVisible = false; isConfirmLoading = false; constructor() {} showModal(): void { this.isVisible = true; } handleOk(): void { this.isConfirmLoading = true; setTimeout(() => { this.isVisible = false; this.isConfirmLoading = false; }, 1000); } handleCancel(): void { this.isVisible = false; } }
Select选择器
import { Component } from '@angular/core'; @Component({ selector: 'nz-demo-select-label-in-value', template: ` <p>The selected option's age is {{ selectedValue?.age }}</p> <br /> <nz-select [(ngModel)]="selectedValue" [compareWith]="compareFn" (ngModelChange)="log($event)" nzAllowClear nzPlaceHolder="Choose" > <nz-option *ngFor="let option of optionList" [nzValue]="option" [nzLabel]="option.label"></nz-option> </nz-select> `, styles: [ ` nz-select { width: 120px; } ` ] }) export class NzDemoSelectLabelInValueComponent { optionList = [ { label: 'Lucy', value: 'lucy', age: 20 }, { label: 'Jack', value: 'jack', age: 22 } ]; selectedValue = { label: 'Jack', value: 'jack', age: 22 }; // eslint-disable-next-line @typescript-eslint/no-explicit-any compareFn = (o1: any, o2: any): boolean => (o1 && o2 ? o1.value === o2.value : o1 === o2); log(value: { label: string; value: string; age: number }): void { console.log(value); } }
注: [(ngModel)] 与 (ngModelChange) 顺序不同,ngModel获取到的值不同
ngModel 在前 取到的是改变后的值
ngModel在后 取到的是改变前的值
Table
树形表格
import { Component, OnInit } from '@angular/core'; export interface TreeNodeInterface { key: string; name: string; age?: number; level?: number; expand?: boolean; address?: string; children?: TreeNodeInterface[]; parent?: TreeNodeInterface; } @Component({ selector: 'nz-demo-table-expand-children', template: ` <nz-table #expandTable [nzData]="listOfMapData" nzTableLayout="fixed"> <thead> <tr> <th>Name</th> <th>Age</th> <th>Address</th> </tr> </thead> <tbody> <ng-container *ngFor="let data of expandTable.data"> <ng-container *ngFor="let item of mapOfExpandedData[data.key]"> <tr *ngIf="(item.parent && item.parent.expand) || !item.parent"> <td [nzIndentSize]="item.level! * 20" [nzShowExpand]="!!item.children" [(nzExpand)]="item.expand" (nzExpandChange)="collapse(mapOfExpandedData[data.key], item, $event)" > {{ item.name }} </td> <td>{{ item.age }}</td> <td>{{ item.address }}</td> </tr> </ng-container> </ng-container> </tbody> </nz-table> ` }) export class NzDemoTableExpandChildrenComponent implements OnInit { listOfMapData: TreeNodeInterface[] = [ { key: `1`, name: 'John Brown sr.', age: 60, address: 'New York No. 1 Lake Park', children: [ { key: `1-1`, name: 'John Brown', age: 42, address: 'New York No. 2 Lake Park' }, { key: `1-2`, name: 'John Brown jr.', age: 30, address: 'New York No. 3 Lake Park', children: [ { key: `1-2-1`, name: 'Jimmy Brown', age: 16, address: 'New York No. 3 Lake Park' } ] }, { key: `1-3`, name: 'Jim Green sr.', age: 72, address: 'London No. 1 Lake Park', children: [ { key: `1-3-1`, name: 'Jim Green', age: 42, address: 'London No. 2 Lake Park', children: [ { key: `1-3-1-1`, name: 'Jim Green jr.', age: 25, address: 'London No. 3 Lake Park' }, { key: `1-3-1-2`, name: 'Jimmy Green sr.', age: 18, address: 'London No. 4 Lake Park' } ] } ] } ] }, { key: `2`, name: 'Joe Black', age: 32, address: 'Sidney No. 1 Lake Park' } ]; mapOfExpandedData: { [key: string]: TreeNodeInterface[] } = {}; collapse(array: TreeNodeInterface[], data: TreeNodeInterface, $event: boolean): void { if (!$event) { if (data.children) { data.children.forEach(d => { const target = array.find(a => a.key === d.key)!; target.expand = false; this.collapse(array, target, false); }); } else { return; } } } convertTreeToList(root: TreeNodeInterface): TreeNodeInterface[] { const stack: TreeNodeInterface[] = []; const array: TreeNodeInterface[] = []; const hashMap = {}; stack.push({ ...root, level: 0, expand: false }); while (stack.length !== 0) { const node = stack.pop()!; this.visitNode(node, hashMap, array); if (node.children) { for (let i = node.children.length - 1; i >= 0; i--) { stack.push({ ...node.children[i], level: node.level! + 1, expand: false, parent: node }); } } } return array; } visitNode(node: TreeNodeInterface, hashMap: { [key: string]: boolean }, array: TreeNodeInterface[]): void { if (!hashMap[node.key]) { hashMap[node.key] = true; array.push(node); } } ngOnInit(): void { this.listOfMapData.forEach(item => { this.mapOfExpandedData[item.key] = this.convertTreeToList(item); }); } }
树选择
import { Component } from '@angular/core'; import { NzFormatEmitEvent, NzTreeNodeOptions } from 'ng-zorro-antd/tree'; @Component({ selector: 'nz-demo-tree-select-async', template: ` <nz-tree-select style="width: 250px" nzPlaceHolder="Please select" [nzExpandedKeys]="expandKeys" [(ngModel)]="value" [nzDropdownMatchSelectWidth]="true" [nzDropdownStyle]="{ 'max-height': '300px' }" [nzNodes]="nodes" [nzAsyncData]="true" (nzExpandChange)="onExpandChange($event)" ></nz-tree-select> ` }) export class NzDemoTreeSelectAsyncComponent { expandKeys = ['0-0']; value?: string; nodes = [ { title: 'Node1', value: '0-0', key: '0-0', children: [ { title: 'Child Node1', value: '0-0-1', key: '0-0-1' }, { title: 'Child Node2', value: '0-0-2', key: '0-0-2' } ] }, { title: 'Node2', value: '0-1', key: '0-1' } ]; onExpandChange(e: NzFormatEmitEvent): void { const node = e.node; if (node && node.getChildren().length === 0 && node.isExpanded) { this.loadNode().then(data => { node.addChildren(data); }); } } loadNode(): Promise<NzTreeNodeOptions[]> { return new Promise(resolve => { setTimeout( () => resolve([ { title: 'Child Node', key: `${new Date().getTime()}-0` }, { title: 'Child Node', key: `${new Date().getTime()}-1` } ]), 1000 ); }); } }
Ng Alain
准备知识
-
文档类
-
TypeScript中文文档,虽然 TypeScript 跟 Java、C# 语法很像,这是语法基础需要认真阅读
-
Angular中文文档,建议一定要花时间阅读文档部分,透过它基本上就可以学会 Angular;同时,也是 Angular API 接口文档
-
NG-ZORRO中文文档,NG-ZORRO 作为 NG-ALAIN 的基础组件库,当你不懂某个组件时,它就是最好的文档,包含组件用法及API说明
-
NG-ALAIN中文文档,包含所有
@delon/*
类型的用法及API说明 -
G2图表中文文档,如果需要图表开发,则这份文档是必备
-
-
辅助类
-
Ant Design 指引文章,学习 Ant Design 的设计理念,非常值得阅读的部分
-
NG-ZORRO 社区推荐,一份非常值得学习的清单
-
代码风格
NG-ALAIN 使用 ESLint 来保证代码质量 与 Prettier 来优化代码风格。
推荐安装几个插件在 vscode 中更友好的开发:
项目搭建
新建项目
$ ng new gzga-client --style less --routing #遇到问题,无法新建项目 D:\jsWorkspace>ng new gzga-client --style less --routing setTimeout is not defined #解决办法,重新安装脚手架 #卸载当前版本 $ npm uninstall -g angular-cli $ npm uninstall -g @angular/cli #清除未卸载干净的angular-cli缓存 $ npm cache clean -f #删除安装文件夹 #安装最新版本 $ npm install -g @angular/cli@12 #安装 ng-alain $ ng add ng-alain
禁用Eslint
暂时禁用ESlint插件
创建模块
#创建模块 $ ng g ng-alain:module logBook
创建组件
#创建基础项目组件 $ ng g component userProject/userProjectList --flat $ ng g component userProject/userProjectView --flat #创建项目人员关联组件 $ ng g component baseProject/baseProjectList --flat $ ng g component baseProject/baseProjectView --flat #创建过程组件 $ ng g component process/processView --flat $ ng g component process/processList --flat #活动分解组件 $ ng g component activity/activityList --flat $ ng g component activity/activityView --flat #日志填报组件 $ ng g component logFill/logFillList --flat $ ng g component logFill/logFillView --flat
配置模块
//1、声明组件 const COMPONENTS: Array<Type<void>> = [ BaseProjectViewComponent, BaseProjectlistComponent, UserProjectListComponent, UserProjectViewComponent, ProcessListComponent, ProcessViewComponent, ActivityListComponent, ActivityViewComponent, LogFillListComponent, LogFillViewComponent ]; @NgModule({ imports: [SharedModule, LogBookRoutingModule], declarations: COMPONENTS }) export class LogBookModule {} //2、导入路由 imports: [SharedModule, LogBookRoutingModule]
配置路由
//主路由配置,配置默认路径为进入日志模块 RouteRoutingModule { path: '', redirectTo: 'logBook', pathMatch: 'full' } //日志子模块路由配置 LogBookRoutingModule const routes: Routes = [ { path: 'logFill', component: LogFillListComponent }, { path: 'base', children: [ { path: 'project', component: BaseProjectlistComponent }, { path: 'process', component: ProcessListComponent }, { path: 'activity', component: ActivityViewComponent } ] } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class LogBookRoutingModule {}
配置菜单
//app-data.json { "app": { "name": "日志管理", "description": "Ng-zorro admin panel front-end framework" }, "user": { "name": "Admin", "avatar": "./assets/tmp/img/avatar.jpg", "email": "cipchk@qq.com" }, "menu": [ { "text": "日志管理", "i18n": "menu.log", "group": true, "hideInBreadcrumb": true, "children": [ { "text": "基础数据", "i18n": "menu.log.base", "icon": "anticon-dashboard", "children": [ { "text": "项目管理", "link": "/logBook/base/project", "i18n": "menu.log.project" }, { "text": "活动管理", "link": "/logBook/base/activity", "i18n": "menu.log.activity" }, { "text": "过程管理", "link": "/logBook/base/process", "i18n": "menu.log.process" } ]}, { "text": "日志填报", "i18n": "menu.log.logFill", "link": "/logBook/logFill", "icon": "anticon-appstore" } ] } ] } //zh-CN.json { "menu.log":"日志管理", "menu.log.base":"基础数据", "menu.log.project":"项目管理", "menu.log.process":"过程管理", "menu.log.activity":"活动管理", "menu.log.logFill":"日志填报" }
创建服务
创建需要的服务
#项目信息服务 $ ng g service service/logBook/baseProject/baseProject #过程服务 $ ng g service service/logBook/process/process #活动服务 $ ng g service service/logBook/activity/activity #日志填写服务 $ ng g service service/logBook/logFill/logFill #人员管理服务 $ ng g service service/common/user/user #单位管理服务 $ ng g service service/common/unit/unit
创建接口
#项目基础信息DTO $ ng g interface model/logBook/baseProject/BaseProjectDTO #过程DTO $ ng g interface model/logBook/process/ProcessDTO #活动DTO $ ng g interface model/logBook/activity/ActivityDTO #日志填写DTO $ ng g interface model/logBook/logFill/LogFillDTO $ ng g interface model/common/base/AbstractBaseEntityDTO $ ng g interface model/common/base/ReturnJSON
创建表
--日志项目基础表 CREATE TABLE "GZGASYS"."NK_LOG_PROJECT" ( "UUID" VARCHAR2(40) NOT NULL, "PRJ_CODE" VARCHAR2(100), "PRJ_NAME" VARCHAR2(200), "START_DATE" DATE, "END_DATE" DATE, "STATE" CHAR(4) DEFAULT 1, "CREATE_TIME" VARCHAR2(20), "CREATE_USER_ID" VARCHAR2(20), "UPDATE_USER_ID" VARCHAR2(20), "UPDATE_TIME" VARCHAR2(20), NOT CLUSTER PRIMARY KEY("UUID")) STORAGE(ON "GZGA_SYS", CLUSTERBTR) ; COMMENT ON TABLE "GZGASYS"."NK_LOG_PROJECT" IS '日志管理-项目基础信息表'; COMMENT ON COLUMN "GZGASYS"."NK_LOG_PROJECT"."UUID" IS '主键'; COMMENT ON COLUMN "GZGASYS"."NK_LOG_PROJECT"."PRJ_CODE" IS '项目代码'; COMMENT ON COLUMN "GZGASYS"."NK_LOG_PROJECT"."PRJ_NAME" IS '项目名称'; COMMENT ON COLUMN "GZGASYS"."NK_LOG_PROJECT"."START_DATE" IS '开始日期'; COMMENT ON COLUMN "GZGASYS"."NK_LOG_PROJECT"."END_DATE" IS '结束日期'; COMMENT ON COLUMN "GZGASYS"."NK_LOG_PROJECT"."STATE" IS '状态 0 禁用 1 启用'; COMMENT ON COLUMN "GZGASYS"."NK_LOG_PROJECT"."CREATE_TIME" IS '创建时间'; COMMENT ON COLUMN "GZGASYS"."NK_LOG_PROJECT"."CREATE_USER_ID" IS '创建人'; COMMENT ON COLUMN "GZGASYS"."NK_LOG_PROJECT"."UPDATE_USER_ID" IS '更新人'; COMMENT ON COLUMN "GZGASYS"."NK_LOG_PROJECT"."UPDATE_TIME" IS '更新时间';
环境变量配置
export const environment = { production: false, useHash: true, api: { baseUrl: './', serverUrl: 'http://localhost:8082/api', //配置后台调用地址 refreshTokenEnabled: true, refreshTokenType: 'auth-refresh' }, modules: [DelonMockModule.forRoot({ data: MOCKDATA })] } as Environment;
拦截器配置
DefaultInterceptor 默认拦截器配置
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // 统一加上服务端前缀 let url = req.url; if (!url.startsWith('https://') && !url.startsWith('http://')) { const { baseUrl } = environment.api; const { serverUrl } = environment.api; //设置服务器地址 if (url.startsWith('/logBook')) { url = serverUrl + url; } else { url = baseUrl + (baseUrl.endsWith('/') && url.startsWith('/') ? url.substring(1) : url); } } const newReq = req.clone({ url, setHeaders: this.getAdditionalHeaders(req.headers) }); 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)) ); }
Token设置
UserLoginComponent 登陆处理类中设置token
res.user.expired = +new Date() + 1000 * 60 * 5; //设置token res.user.token = 'xxxxx'; this.tokenService.set(res.user);
日期组件
由于项目默认没有日期组件,需手动引入
//SHARED_ZORRO_MODULES import { NzDatePickerModule } from 'ng-zorro-antd/date-picker'; export const SHARED_ZORRO_MODULES = [ ..., NzSpinModule, NzDatePickerModule //引入日期组件 ];
项目结构
当使用 ng add ng-alain
生成后的 NG-ALAIN 脚手架,它的基本目录结构概略图如下:
├── _mock # Mock 数据规则目录 ├── angular.json # Angular 项目配置文件 ├── src │ ├── app │ │ ├── core # 核心模块 │ │ │ ├── i18n │ │ │ ├── net │ │ │ │ └── default.interceptor.ts # 默认HTTP拦截器 │ │ │ ├── services │ │ │ │ └── startup.service.ts # 初始化项目配置 │ │ │ └── core.module.ts # 核心模块文件 │ │ ├── layout # 通用布局 │ │ ├── routes │ │ │ ├── ** # 业务目录 │ │ │ ├── routes.module.ts # 业务路由模块 │ │ │ └── routes-routing.module.ts # 业务路由注册口 │ │ ├── shared # 共享模块 │ │ │ ├── shared-delon.module.ts # @Delon/* 次级共享模块导入 │ │ │ ├── shared-zorro.module.ts # NG-ZORRO 次级共享模块导入 │ │ │ └── shared.module.ts # 共享模块文件 │ │ ├── app.component.ts # 根组件 │ │ └── app.module.ts # 根模块 │ │ └── global-config.module.ts # @delon & ng-zorro 全局配置项 │ ├── assets # 本地静态资源 │ ├── environments # 环境变量配置 │ ├── styles # 样式目录 └── └── style.less # 样式引导入口
以下是针对各个目录及文件说明及使用目的:
名称 | 描述 |
---|---|
angular.json | Angular 工作区及项目的配置文件,参考Angular文档 |
_mock | Mock 数据规则目录,若你通过 命令行工具 创建项目时可以指定 --mock 参数决定是否需要 Mock 功能 |
src/app/core/core.module.ts | 核心模块,只会导入一次。因此,针对整个业务模块都需要使用的纯服务类(例如:消息、数据访问等) |
src/app/core/i18n | 国际化数据加载及处理相关类,若你通过 命令行工具 创建项目时可以指定 -di 参数决定是否需要国际化支持 |
src/app/core/net | 默认拦截器,你可以在这里统一处理请求参数、请求异常、业务异常等动作 |
src/app/core/services/startup.service.ts | 当你需要在 Angular 启动前执行一些远程数据(例如:应用信息、用户信息等)时非常有用 |
src/app/layout | 布局目录,包含基础布局、空白布局、用户登录布局 |
src/app/routes | 业务模块,你的所有业务代码都将在这里 |
src/app/shared/shared.module.ts | 共享模块,指当你需要针对整个业务模块都需要使用的一些第三方模块、自定义组件、自定义指令,都应该存在这里。除此之外,针对 @delon & NG-ZORRO 分别构建了 shared-delon.module.ts 、shared-zorro.module.ts 两种次级共享模块的导入。 |
src/app/global-config.module.ts | 针对 @delon & NG-ZORRO 的全局配置项 |
src/environments | 应用环境变量,包含以下值:SERVER_URL 所有HTTP请求的前缀;production 是否生产环境;useHash 路由是否useHash模式 |
项目部署
项目编译
项目编译的几种方式如下
#项目直接打包 $ ng build # 项目打包时改变base-href $ ng build --base-href ./
常见问题
变更检测策略
默认变更检测器用来检测更改的策略。设置后,将在下次触发变更检测时生效
enum ChangeDetectionStrategy { OnPush: 0 Default: 1 }
成员 | 说明 |
---|---|
OnPush: 0 | 使用 CheckOnce 策略,这意味着把此策略设置为 Default ( CheckAlways )将禁用自动变更检测,直到重新激活。变更检测仍然可以显式调用。此策略适用于所有子指令,并且不能被覆盖。 |
Default: 1 | 使用默认的 CheckAlways 策略,在该策略中,变更检测将自动执行,直到显式停用为止。 |
注意:
NG-ZORRO 及 @delon/* 组件默认在 OnPush 模式下工作,mutate 对象或者数组不会触发 Angular 的变更检测,请使用 immutable 方式
特殊选择器
名称 | 作用 |
---|---|
:host | 表示选择当前的组件 |
::ng-deep | 可以忽略中间className的嵌套层级关系。直接找到你要修改的className |
:host-context | 如果需要满足某条件才能应用样式,这个正好。它在当前组件宿主元素的祖先节点中查找 CSS 类,直到文档的根节点为止。如果找到,才会应用后面的样式到内部元素 |
用法示例
//1、忽略层级,修改本组件下面某个特定类型的样式 :host ::ng-deep .className{ 新的样式...... } //2、把宿主元素作为目标的唯一方式。除此之外,你将没办法指定它,因为宿主不是组件自身模板的一部分,而是父组件模板的一部分 :host { display: block; border: 1px solid black; } //3、如果h1标签在设计的组件中有一个黑色标题,我希望能够在黑暗的主题背景上显示时将其更改为白色。如果无法访问源码,可以在父级的css中执行下面语句 .theme-dark widget-box ::ng-deep h1 { color: white; } //或者 h1 { color: black; // 默认颜色 :host-context(.theme-dark) & { color: white; // 当黑色主题时的颜色 } // 或者设置属性 'outside' 为 [attr.theme]="'dark'" :host-context([theme='dark']) & { color: white; // 当黑色主题时的颜色 } } //4、搜索某类型下面的特定类型 .card-container ::ng-deep .ant-tabs-card .ant-tabs-content { height: 120px; margin-top: -16px; } //5、:ng-deep如果放在顶头,这种强烈不推荐,会污染全局样式 ::ng-deep .ant-tabs-card .ant-tabs-content { height: 120px; margin-top: -16px; }
设置不生成spec文件
angular.json中设置
"@schematics/angular:component": { "skipTests": true, "flat": false, "inlineStyle": true, "inlineTemplate": false, "style": "less" }
图标问题
图标不显示问题
-
部分图标无法使用
ng-alain默认只导入了图标库的几十个图标,在 style-icons-auto.ts
可进行查看
如果有需要可以在 style-icons-auto.ts
中进入import and export
// 在style-icons-auto.ts进行配置图标组件
表格列渲染
columns: STColumn[] = [ { title: '序号', type: 'no' }, { title: '项目编号', index: 'prjCode' }, { title: '项目名称', index: 'prjName' }, { title: '项目开始日期', index: 'startDate' }, { title: '项目结束日期', index: 'endDate' }, { title: '项目状态', index: 'state', render: 'state' }, //状态列渲染 { title: '操作', buttons: [ { text: '编辑', click: item => this.edit(item) }, { text: '删除', click: item => this.remove(item) } ] } ];
<st #st [columns]="columns" [data]="data" [loading]="loading"> <ng-template st-row="state" let-i let-index="index"> {{ i.state == '1' ? '启用' : '禁用' }} </ng-template> </st>
后台自动启动
echo 开始打包项目.... rem 项目目录 rem set "nkDir=D:\ideaWorkspace\nk-server" set nkDir=%cd% rem 项目发布的JAR目录 set nkJar=%nkDir%\nk-api\target rem d: cd %nkDir% git pull call mvn clean package -DskipTests cd %nkJar% java -jar nk-api-1.0.0.jar
nzValue与[nzValue]
模板中
[nzValue]="data"
nzValue="data"
nzValue="{{data}}" 有什么区别
nzValue="data"
组件收到的是字符串 data
,
nzValue="{{data}}"
等价于 [nzValue]="data.toString()"
。
如果你需要传入 number
或者 boolean
类型时,应当使用 [nzValue]="data"
的方式。
高度计算
height:calc(100% - 84);
常用接口
拦截器
HttpInterceptor
拦截 HttpRequest
并处理它们。
interface HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> }
使用说明
要想在整个应用中使用 HttpInterceptors
的同一个实例,就只能在 AppModule
模块中导入 HttpClientModule
,并且把拦截器都添加到应用的根注入器中。 如果你在不同的模块中多次导入 HttpClientModule
,则每次导入都会创建 HttpClientModule
的一个新复本,它将会覆盖根模块上提供的那些拦截器。
JSONP
Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据。
TranslateService
angular国际化类库