大家好,欢迎来到IT知识分享网。
Util 应用框架 UI 全新升级
Util UI 已经开发多年, 并在多家公司的项目使用.
不过一直以来, Util UI 存在一些缺陷, 始终未能解决.
最近几个月, Util 团队下定决心, 终于彻底解决了所有已知缺陷.
Util 应用框架 UI 介绍
Util 应用框架 UI 建立在 Angular , Ng-Zorro, Ng-Alain 基础之上, 用于开发企业中后台.
Util 应用框架 UI 的特点
- 简洁
- Util UI 通常可以将复杂组件的 html 代码量压缩 3 – 10 倍,从而使项目的可维护性大幅提升.
- 下面以查询表单为例进行对比.
- 先看效果演示.
- Util UI 的标签使用 TagHelper 进行封装 ,代码如下.
<util-card borderless="true" class="searchForm"> <util-search-form label-width="120"> <util-row gutter="24"> <util-column> <util-input id="code" name="code" ng-model="queryParam.code" label-text="identity.application.code"/> </util-column> <util-column> <util-input id="name" name="name" ng-model="queryParam.name" label-text="identity.application.name"/> </util-column> <util-column> <util-select id="enabled" name="enabled" ng-model="queryParam.enabled" label-text="identity.application.enabled"/> </util-column> <util-column> <util-input id="remark" name="remark" ng-model="queryParam.remark" label-text="identity.application.remark"/> </util-column> <util-column> <util-column> <util-range-picker id="begin_creation_time" name="begin_creation_time" label-text="util.beginCreationTime" begin-date="queryParam.beginCreationTime" end-date="queryParam.endCreationTime"/> </util-column> <util-column> <util-range-picker id="begin_last_modification_time" name="begin_last_modification_time" label-text="util.beginLastModificationTime" begin-date="queryParam.beginLastModificationTime" end-date="queryParam.endLastModificationTime" /> </util-column> <util-column class="mb-md"> <util-flex justify="FlexEnd" align="Center" gap="Small"> <util-button id="btnRefresh" icon="Sync" on-click="refresh(btnRefresh)" text-reset="true"></util-button> <util-button id="btnQuery" type="Primary" icon="Search" on-click="query(btnQuery)" text-query="true"></util-button> <util-a is-search="true" class="ml-sm"></util-a> </util-flex> </util-column> </util-row> </util-search-form> </util-card>
- 上面的标签会转换成 Ng Zorro 原生的 html 标签.
<nz-card class="searchForm" [nzBorderless]="true"> <form nz-form=""> <div nz-row="" [nzGutter]="24"> <div nz-col="" [nzLg]="8" [nzMd]="12" [nzSm]="24" [nzXl]="8" [nzXs]="24" [nzXXl]="6"> <nz-form-item> <nz-form-label style="width:120px">{{'identity.application.code'|i18n}}</nz-form-label> <nz-form-control> <nz-input-group [nzSuffix]="tmp_code"> <input #code="" #model_code="ngModel" name="code" nz-input="" [(ngModel)]="queryParam.code" /> </nz-input-group> <ng-template #tmp_code=""> <i (click)="model_code.reset()" *ngIf="model_code.value" class="ant-input-clear-icon" nz-icon="" nzTheme="fill" nzType="close-circle"> </i> </ng-template> </nz-form-control> </nz-form-item> </div> <div nz-col="" [nzLg]="8" [nzMd]="12" [nzSm]="24" [nzXl]="8" [nzXs]="24" [nzXXl]="6"> <nz-form-item> <nz-form-label style="width:120px">{{'identity.application.name'|i18n}}</nz-form-label> <nz-form-control> <nz-input-group [nzSuffix]="tmp_name"> <input #model_name="ngModel" #name="" name="name" nz-input="" [(ngModel)]="queryParam.name" /> </nz-input-group> <ng-template #tmp_name=""> <i (click)="model_name.reset()" *ngIf="model_name.value" class="ant-input-clear-icon" nz-icon="" nzTheme="fill" nzType="close-circle"> </i> </ng-template> </nz-form-control> </nz-form-item> </div> <div nz-col="" [nzLg]="8" [nzMd]="12" [nzSm]="24" [nzXl]="8" [nzXs]="24" [nzXXl]="6"> <nz-form-item> <nz-form-label style="width:120px">{{'identity.application.enabled'|i18n}}</nz-form-label> <nz-form-control> <nz-select #enabled="" #x_enabled="xSelectExtend" name="enabled" x-select-extend="" [(ngModel)]="queryParam.enabled"> <nz-option [nzLabel]="'util.defaultOptionText'|i18n"></nz-option> <ng-container *ngIf="!x_enabled.isGroup"> <nz-option *ngFor="let item of x_enabled.options" [nzDisabled]="item.disabled" [nzLabel]="item.text|i18n" [nzValue]="item.value"> </nz-option> </ng-container> <ng-container *ngIf="x_enabled.isGroup"> <nz-option-group *ngFor="let group of x_enabled.optionGroups" [nzLabel]="group.text|i18n"> <nz-option *ngFor="let item of group.value" [nzDisabled]="item.disabled" [nzLabel]="item.text|i18n" [nzValue]="item.value"> </nz-option> </nz-option-group> </ng-container> </nz-select> </nz-form-control> </nz-form-item> </div> <div *ngIf="expand" nz-col="" [nzLg]="8" [nzMd]="12" [nzSm]="24" [nzXl]="8" [nzXs]="24" [nzXXl]="6"> <nz-form-item> <nz-form-label style="width:120px">{{'identity.application.remark'|i18n}}</nz-form-label> <nz-form-control> <nz-input-group [nzSuffix]="tmp_remark"> <input #model_remark="ngModel" #remark="" name="remark" nz-input="" [(ngModel)]="queryParam.remark" /> </nz-input-group> <ng-template #tmp_remark=""> <i (click)="model_remark.reset()" *ngIf="model_remark.value" class="ant-input-clear-icon" nz-icon="" nzTheme="fill" nzType="close-circle"> </i> </ng-template> </nz-form-control> </nz-form-item> </div> <div *ngIf="expand" nz-col="" [nzLg]="8" [nzMd]="12" [nzSm]="24" [nzXl]="8" [nzXs]="24" [nzXXl]="6"> <nz-form-item> <nz-form-label style="width:120px">{{'util.beginCreationTime'|i18n}}</nz-form-label> <nz-form-control> <nz-range-picker #begin_creation_time="" #x_begin_creation_time="xRangePickerExtend" name="begin_creation_time" x-range-picker-extend="" [(beginDate)]="queryParam.beginCreationTime" [(endDate)]="queryParam.endCreationTime" [(ngModel)]="x_begin_creation_time.rangeDates"> </nz-range-picker> </nz-form-control> </nz-form-item> </div> <div *ngIf="expand" nz-col="" [nzLg]="8" [nzMd]="12" [nzSm]="24" [nzXl]="8" [nzXs]="24" [nzXXl]="6"> <nz-form-item> <nz-form-label style="width:120px">{{'util.beginLastModificationTime'|i18n}}</nz-form-label> <nz-form-control> <nz-range-picker #begin_last_modification_time="" #x_begin_last_modification_time="xRangePickerExtend" name="begin_last_modification_time" x-range-picker-extend="" [(beginDate)]="queryParam.beginLastModificationTime" [(endDate)]="queryParam.endLastModificationTime" [(ngModel)]="x_begin_last_modification_time.rangeDates"> </nz-range-picker> </nz-form-control> </nz-form-item> </div> <div class="mb-md" nz-col="" [nzLg]="{span:expand?24:24}" [nzMd]="{span:expand?24:12}" [nzSm]="24" [nzXl]="{span:expand?24:24}" [nzXs]="24" [nzXXl]="{span:expand?12:6}"> <div nz-flex="" nzAlign="center" nzGap="small" nzJustify="flex-end"> <button #btnRefresh="" (click)="refresh(btnRefresh)" nz-button="" type="button"> <i nz-icon="" nzType="sync"></i> {{'util.reset'|i18n}} </button> <button #btnQuery="" (click)="query(btnQuery)" nz-button="" nzType="primary" type="button"> <i nz-icon="" nzType="search"></i> {{'util.query'|i18n}} </button> <a (click)="expand=!expand" class="ml-sm"> {{expand?('util.collapse'|i18n):('util.expand'|i18n)}} <i nz-icon="" [nzType]="expand?'up':'down'"></i> </a> </div> </div> </div> </form> </nz-card>
- <util-search-form> 是 Util UI 的查询表单标签.
- 查询表单支持响应式,并将按钮区域始终放置在最后一行的右侧.
- label-width 是一个扩展的范围设置属性, 为每个表单组件的 <nz-form-label> 设置 style=“width:120px” 样式, 避免了分别设置每个组件的宽度.
- Ng Zorro 表单组件由 <nz-form-item> , <nz-form-label> , <nz-form-control> 组合而成.
- <util-input> 和 <util-select> 设置了 label-text , 这是一个扩展属性,它会激活 <nz-form-item> 结构的自动创建.
- <util-input> 是文本框, 除了为它自动创建 <nz-form-item> 结构, 还会添加清除内容的功能.
- Util UI 大多常用组件的显示文本会自动添加 i18n 管道, 比如 ‘identity.application.code’|i18n ,用于支持多语言.
- 从前面的示例可以看到 Util UI 可以大幅提升 html 标签的书写效率, 降低维护成本.
- 易用
- Util 对常用功能进行了高度封装, 并提供简单易用的 API.
- 易用性是 Util UI 封装的关键目标,也是 Util UI 存在的意义.
- 本文后续将以最近更新的一个关键功能 – 表格设置, 演示易用性.
- 强类型提示
- Util UI 提供的标签使用 TagHelper 技术封装, 支持强类型提示.
- 如果你使用 Vs Code 开发, Util UI 标签提示信息大致与 Ng Zorro Vs Code 插件提示效果相当.
- Vs Code 的标签提示信息并不精准, 包含很多与 html 相关的属性, 比如 aria- 打头的属性就占了几屏, 这降低了代码提示的作用.
- 如果使用 Vs 开发, 甚至安装了 Resharper , 代码提示就能达到最佳效果.
- 持续更新和改进
- Util UI 不仅仅是对 Ng Zorro 功能的简单包装, 更提供了常用功能的扩展.
- Util UI 扩展功能来自之前使用其它 UI 框架的经验, 另外收集项目开发时的实际需求,并加以整理,以满足使用 Util UI 的项目.
- Util 团队倾听开发人员的心声, 并持续改进, 从而更好的满足项目需求.
Util 应用框架 UI 的封装实现方式
- 使用.cshtml替代 .html 页面.
- .cshtml 是 .Net 提供的一种高级 html 封装技术.
- Util 创造性的将 .cshtml 引入 Angular 应用开发.
- Util 将 cshtml 页面作为 html 抽象层, 用来隐藏 html 的复杂性.
- Ng Zorro 组件库定义了大量的 Angular 组件.
- 使用 Angular 组件, 就是在 html 页面中书写自定义的标签.
- Util 应用框架使用 TagHelper 对 Ng Zorro 标签进行封装, 以提供更加简洁的用法.
- TagHelper 是一种 .Net 标签, 在 .cshtml 文件中使用.
- 虽然 TagHelper 标签看上去也是一些自定义标签 , 但它们不是 Angular 组件.
- Util 会在开发阶段将 .cshtml 文件转换成 html.
- 使用 Angular 指令进行扩展.
- Ng Zorro 组件库与 EasyUI 这样的组件库具有显著差异.
- Ng Zorro 组件库提供的 API 具有粒度细, 扩展性强的特点.
- Ng Zorro 组件的很多功能并不内置于组件中,而是通过 Demo 的形式告诉你怎么使用.
- 这为你提供了很大的灵活性和自由.
- 但也意味着,如果你不加封装,直接在项目中复制使用, 就会造成大量的冗余代码, 降低项目的可维护性.
- 要扩展 Ng Zorro 组件, 仅使用 TagHelper 封装 html 是不够的, 还需要找到编写脚本的地方.
- 封装和扩展 Ng Zorro 组件, 通常有两种方式.
- 一种方式是创建新的 Angular 组件对原始组件进行包装.
- 使用组件包装, 可以提供更加易用的 Api.
- 不过这种封装方式也有一些缺陷.
- 新组件的 API 与原始组件可能不同, 增加了学习成本.
- 由于需要将原始组件的 API 暴露出来 , 导致更多的冗余代码.
- 扩展性降低.
- 对于表格这样复杂的组件, html 结构相当复杂, 使用组件包装通常不会保留原有的 html 结构.
- 扩展点完全由新组件控制, 从而降低扩展性.
- 另一种方式是使用 Angular 指令对原始组件进行扩展.
- Angular 指令使用起来就像标签上的属性一样.
- 使用 Angular 指令进行扩展, 最大优势是保留原始组件的全部用法, 不会降低其扩展性.
- 当然指令封装方式也带来了新的挑战,那就是 html 标签会更加复杂.
- Util UI 使用 Angular 指令进行封装扩展, 并使用 TagHelper 标签来隐藏 html 的复杂度.
- Lambda表达式支持
- 在 .cshtml 文件中使用 TagHelper 标签, 你可以直接设置标签上的属性.
- 不过 , 如果使用 .Net 开发 API 后端, 并创建了 DTO 对象, 你可以将 DTO 属性直接绑定到标签上.
- 下面演示查询表单组件如何使用Lambda表达式绑定 DTO 属性.
- DTO 代码如下:
/// <summary> /// 应用程序查询参数 /// </summary> public class ApplicationQuery : QueryParameter { /// <summary> /// 应用程序编码 ///</summary> [Description( "identity.application.code" )] public string Code { get; set; } /// <summary> /// 应用程序名称 ///</summary> [Description( "identity.application.name" )] public string Name { get; set; } /// <summary> /// 启用 ///</summary> [Description( "identity.application.enabled" )] public bool? Enabled { get; set; } /// <summary> /// 备注 ///</summary> [Description( "identity.application.remark" )] public string Remark { get; set; } /// <summary> /// 起始创建时间 /// </summary> [Display( Name = "util.beginCreationTime" )] public DateTime? BeginCreationTime { get; set; } /// <summary> /// 结束创建时间 /// </summary> [Display( Name = "util.endCreationTime" )] public DateTime? EndCreationTime { get; set; } /// <summary> /// 起始最后修改时间 /// </summary> [Display( Name = "util.beginLastModificationTime" )] public DateTime? BeginLastModificationTime { get; set; } /// <summary> /// 结束最后修改时间 /// </summary> [Display( Name = "util.endLastModificationTime" )] public DateTime? EndLastModificationTime { get; set; } }
- .cshtml 代码如下:
@model ApplicationQuery <util-card borderless="true" class="searchForm"> <util-search-form label-width="120"> <util-row gutter="24"> <util-column> <util-input for="Code" /> </util-column> <util-column> <util-input for="Name" /> </util-column> <util-column> <util-select for="Enabled" /> </util-column> <util-column> <util-input for="Remark" /> </util-column> <util-column> <util-range-picker for-begin="BeginCreationTime" for-end="EndCreationTime" /> </util-column> <util-column> <util-range-picker for-begin="BeginLastModificationTime" for-end="EndLastModificationTime" /> </util-column> <util-column class="mb-md" md="24"> <util-flex justify="FlexEnd" align="Center" gap="Small"> <util-button id="btnRefresh" icon="Sync" on-click="refresh(btnRefresh)" text-reset="true"></util-button> <util-button id="btnQuery" type="Primary" icon="Search" on-click="query(btnQuery)" text-query="true"></util-button> <util-button icon="CheckSquare" on-click="container.masterToggle()" text-select-all="true" ng-if="!container.isMasterChecked()"></util-button> <util-button icon="CloseSquare" on-click="container.masterToggle()" text-deselect-all="true" ng-if="container.isMasterChecked()"></util-button> <util-a is-search="true" class="ml-sm"></util-a> </util-flex> </util-column> </util-row> </util-search-form> </util-card>
- Lambda表达式会读取 DTO 对象的元数据, 并自动设置常用属性, 从而再次大幅提升生产力.
Util 应用框架 UI 的组成
- Util.Ui.NgZorro
- Util.Ui.NgZorro 类库包含 Ng Zorro TagHelper 标签, 目前已封装官方正式发布的全部组件.
- Util.Ui.NgAlain
- Util.Ui.NgAlain 类库包含 Ng Alain 部分组件 TagHelper 标签.
- util-angular
- util-angular 是一个 typescript 脚本库, 包含 Ng Zorro 扩展指令和常用操作 Helper.
Util 应用框架 UI 最新进展
Util 应用框架 UI 最近进行了全面改进,并取得了重大突破.
最大的进展有2点, 一是开发机制的改进, 二是增加了表格设置功能.
- 开发机制改进
- 架构缺陷
- Util 应用框架将 .cshtml 文件引入 Angular 已有相当长的年头.
- 由于这种非主流的用法并没有微软官方的支持,所以一直存在相当多的问题.
- 最主要的影响是导致开发阶段运行缓慢.
- 之前的开发流程, Angular 组件在开发阶段直接访问 cshtml 页面,所以开发阶段必须使用 Angular JIT 模式, 它比 Angular AOT 模式要慢一些.
- cshtml 在第一次访问时, 尚未创建缓存 , 会比较慢.
- Angular 应用启动时,将访问根模块引用的所有页面, 所以启动时会产生相当的卡顿.
- 这个问题通过 Angular 延迟加载模块得到缓解.
- 如果项目比较大,包含数十个业务模块, 将每个业务模块创建为延迟加载模块.
- 当应用启动时, 并不会访问所有页面, 只有请求了某个业务模块的功能, 才会访问该模块包含的 cshtml 页面.
- 不过从 Angular 13 开始, Angular 移除了传统的视图引擎, 导致上述开发方式无法使用延迟加载模块.
- 这意味着所有业务模块在开发阶段必须在根模块中引用.
- Angular 应用启动后将访问所有 cshtml 页面, 这显然是不可接受的.
- 一种可行的解决办法是使用微前端方案.
- 微前端架构将业务模块分离到不同的项目从而减少应用启动时间.
- 一些较大的项目和团队使用微前端架构是合适的.
- 但微前端架构具有复杂性, 使用微前端架构代替延迟加载模块则非常牵强.
- 这是 Util 团队进行全面改造的根本原因.
- 另一个影响是项目结构比较复杂.
- Util 采用的项目结构最早来自 .Net Core Angular 项目模板, 并加以修改.
- Angular 应用被放在 ClientApp 目录中.
- .cshtml 文件则被放在 Pages 目录中.
- 这导致组件与模板的对应关系比较复杂.
- 改进方案
- 很多时候, 解决问题最重要是思路的转变.
- 之前的架构缺陷主要来自在开发阶段让 Angular 组件直接请求 cshtml 页面,从而与原生 Angular 应用产生差别.
- 不过, Util 使用 cshtml 仅限于开发阶段, 发布之后实际上与 cshtml 没有任何关系.
- cshtml 的作用只是帮助生成 html 而已.
- 现代化开发一个重要的功能是热更新, 比如 Angular 应用, 它会持续监视你的相关文件.
- 当你编辑完 .ts 或 .html 文件时, 浏览器就会自动刷新.
- 如果我们监视所有 .cshtml 文件,并在保存 cshtml 文件时自动生成对应的 html 文件,就能从根本上解决问题.
- 由于只需要处理保存的 cshtml 文件, 生成 html 的速度将非常迅速.
- 当 html 生成完成, 后续流程则与原生 angular 应用相同, 从而解决引入 cshtml 相关的所有缺陷.
- 现在, 编辑并保存 .cshtml 文件, 浏览器就会自动刷新, 与原生 Angular 应用相比, 大致慢几百毫秒, 通常可以忽略不计.
- 项目结构复杂的问题则很好解决, 将 .cshtml 与 Angular 组件放在一起即可.
- 这与原生 Angular 应用相似, 只需修改 .cshtml 生成 html 文件的路径规则.
- 一直以来, Util UI的架构比较臃肿, 只能在 Vs 中开发.
- 但现在前端基本都使用 Vs Code.
- 最新 UI 架构与原生 Angular 应用差别很小, 同样适合使用 Vs Code 开发.
- 下面是使用 Vs Code 打开的项目结构.
- 表格设置
- 表格是业务系统的基石.
- 我们收集了一些项目上使用 Ng Zorro 表格的反馈意见.
- 当表格列较多时,如果不进行宽度设置, 则会显示得很畸形.
- 要解决这个问题, 需要设置表格 nzScroll 属性的 x 值.
- nzScroll 的 x 可以让表格产生横向的滚动条, 从而将表格内容拉伸.
- 不过这个值应该设置成多少合适, 则是一门学问.
- 通常需要计算表格中有多少列,每列大致占多少宽度, nzScroll.x 的值大致是这些宽度之和.
- 手工计算宽度费时费力, 最好是能自动计算.
- 另一个问题是冻结表格头, 并让表格在一定高度滚动.
- 通过设置 nzScroll 属性的 y 值可以做到这一点.
- 不过设置 nzScroll.y 也是一门学问, 因为不同屏幕大小可能需要设置不同的值,在开发阶段很难固定.
- 一些公司使用某些方法计算以达到自适应高度,不过大多针对比较固定的页面布局,且相对简单.
- 更好的办法是让用户在运行时根据自己的要求动态更新.
- 除了表格的总宽度, 每个列的宽度设置也是一个头痛的问题.
- 列宽大多与内容相关, 在开发阶段设置固定列宽, 当内容超过固定宽度就会出现换行,影响美观.
- 如果在开发阶段设置一个默认宽度, 并在运行时可由用户修改就能解决问题.
- 当然最好能支持拖动表头修改列宽, 则更为方便.
- 自定义列是很多项目的必备功能.
- 当表格列非常多, 用户希望只显示其中感兴趣的一部分列, 并能修改列的显示顺序.
- Ng Zorro 支持自定义列功能, 不过使用起来比较复杂.
- 当你启用了自定义列, 用来固定左右侧的 nzLeft 和 nzRight 就变得不那么利索.
- 列与列之间经常会出现一些缝隙或对不齐的现象, Ng Zorro 官方文档给出了一些调整建议, 不过也是非常麻烦.
- 诸如表格批量编辑,表格行编辑, 树形异步加载等需求都是很早之前就已经扩展支持, 就不在此一一列出.
- 下面介绍 Util UI 表格设置功能.
- 先来一个表格设置的效果图.
- 可以看到, 它确实解决了前面提到的棘手问题.
- 如何开启表格设置功能?
- 表格标签示例代码.
@*表格*@ <util-table id="tb" key="identity_operation" enable-table-settings="true" show-checkbox="true" show-line-number="true" url="operation" query-param="queryParam" sort="SortId"> <util-td for="Name"></util-td> <util-td for="Uri"></util-td> <util-td for="IsBase" sort="false"></util-td> <util-td for="Remark"></util-td> <util-td for="Enabled"> <util-tag color-type="GeekBlue" ng-if="row.enabled" text-enabled="true"></util-tag> <util-tag color-type="Red" ng-if="!row.enabled" text-not-enabled="true"></util-tag> </util-td> <util-td for="CreationTime"></util-td> <util-td for="LastModificationTime"></util-td> <util-td title-operation="true"> <util-a on-click="openDetailDialog(row)" text-detail="true"></util-a> <util-container acl="operation.update"> <util-divider type="Vertical"></util-divider> <util-a on-click="openEditDrawer(row)" text-update="true"></util-a> </util-container> <util-container acl="operation.delete"> <util-divider type="Vertical"></util-divider> <util-a danger="true" on-click="delete(row.id)" text-delete="true"></util-a> </util-container> </util-td> </util-table>
- 要开启表格设置功能, 只需要在 <util-table> 标签设置 enable-table-settings 属性为 true.
- 你可能要问, 需要编写 ts 脚本代码吗?
- 不用 !!!
- 如果你看过 Ng Zorro 官方自定义列的示例, 知道需要将一个 NzCustomColumn[] 对象传入 <nz-table>的 nzCustomColumn 属性.
- 那么, Util UI 的自定义列功能是否使用 Ng Zorro 官方的实现呢?
- 下面来看看生成的 html , 答案就会揭晓.
<nz-table #tb="" #x_tb="xTableExtend" (nzPageIndexChange)="x_tb.pageIndexChange($event)" (nzPageSizeChange)="x_tb.pageSizeChange($event)" order="SortId" url="operation" x-table-extend="" [(nzPageIndex)]="x_tb.queryParam.page" [(nzPageSize)]="x_tb.queryParam.pageSize" [(queryParam)]="queryParam" [nzBordered]="ts_tb.bordered" [nzCustomColumn]="ts_tb.columns" [nzData]="x_tb.dataSource" [nzFrontPagination]="false" [nzLoading]="x_tb.loading" [nzPageSizeOptions]="x_tb.pageSizeOptions" [nzScroll]="ts_tb.scroll" [nzShowQuickJumper]="true" [nzShowSizeChanger]="true" [nzShowTotal]="total_tb" [nzSize]="ts_tb.size" [nzTotal]="x_tb.total"> <thead> <tr> <th (nzCheckedChange)="x_tb.masterToggle()" nzCellControl="util.checkbox" [nzChecked]="x_tb.isMasterChecked()" [nzDisabled]="!x_tb.dataSource.length" [nzIndeterminate]="x_tb.isMasterIndeterminate()" [nzLeft]="ts_tb.isLeft('util.checkbox')" [nzRight]="ts_tb.isRight('util.checkbox')" [nzShowCheckbox]="true" [titleAlign]="ts_tb.getTitleAlign('util.checkbox')"> </th> <th nzCellControl="util.lineNumber" [nzLeft]="ts_tb.isLeft('util.lineNumber')" [nzRight]="ts_tb.isRight('util.lineNumber')" [titleAlign]="ts_tb.getTitleAlign('util.lineNumber')"> {{'util.lineNumber'|i18n}} </th> <th (nzResizeEnd)="ts_tb.handleResize($event,'identity.operation.name')" (nzSortOrderChange)="x_tb.sortChange('name',$event)" nz-resizable="" nzBounds="window" nzCellControl="identity.operation.name" nzPreview="" [nzLeft]="ts_tb.isLeft('identity.operation.name')" [nzRight]="ts_tb.isRight('identity.operation.name')" [nzShowSort]="true" [nzSortFn]="true" [titleAlign]="ts_tb.getTitleAlign('identity.operation.name')"> {{'identity.operation.name'|i18n}} <nz-resize-handle nzDirection="right"></nz-resize-handle> </th> <th (nzResizeEnd)="ts_tb.handleResize($event,'identity.operation.uri')" (nzSortOrderChange)="x_tb.sortChange('uri',$event)" nz-resizable="" nzBounds="window" nzCellControl="identity.operation.uri" nzPreview="" [nzLeft]="ts_tb.isLeft('identity.operation.uri')" [nzRight]="ts_tb.isRight('identity.operation.uri')" [nzShowSort]="true" [nzSortFn]="true" [titleAlign]="ts_tb.getTitleAlign('identity.operation.uri')"> {{'identity.operation.uri'|i18n}} <nz-resize-handle nzDirection="right"></nz-resize-handle> </th> <th (nzResizeEnd)="ts_tb.handleResize($event,'identity.operation.isBase')" nz-resizable="" nzBounds="window" nzCellControl="identity.operation.isBase" nzPreview="" [nzLeft]="ts_tb.isLeft('identity.operation.isBase')" [nzRight]="ts_tb.isRight('identity.operation.isBase')" [titleAlign]="ts_tb.getTitleAlign('identity.operation.isBase')"> {{'identity.operation.isBase'|i18n}} <nz-resize-handle nzDirection="right"></nz-resize-handle> </th> <th (nzResizeEnd)="ts_tb.handleResize($event,'identity.operation.remark')" (nzSortOrderChange)="x_tb.sortChange('remark',$event)" nz-resizable="" nzBounds="window" nzCellControl="identity.operation.remark" nzPreview="" [nzLeft]="ts_tb.isLeft('identity.operation.remark')" [nzRight]="ts_tb.isRight('identity.operation.remark')" [nzShowSort]="true" [nzSortFn]="true" [titleAlign]="ts_tb.getTitleAlign('identity.operation.remark')"> {{'identity.operation.remark'|i18n}} <nz-resize-handle nzDirection="right"></nz-resize-handle> </th> <th (nzResizeEnd)="ts_tb.handleResize($event,'identity.operation.enabled')" (nzSortOrderChange)="x_tb.sortChange('enabled',$event)" nz-resizable="" nzBounds="window" nzCellControl="identity.operation.enabled" nzPreview="" [nzLeft]="ts_tb.isLeft('identity.operation.enabled')" [nzRight]="ts_tb.isRight('identity.operation.enabled')" [nzShowSort]="true" [nzSortFn]="true" [titleAlign]="ts_tb.getTitleAlign('identity.operation.enabled')"> {{'identity.operation.enabled'|i18n}} <nz-resize-handle nzDirection="right"></nz-resize-handle> </th> <th (nzResizeEnd)="ts_tb.handleResize($event,'util.creationTime')" (nzSortOrderChange)="x_tb.sortChange('creationTime',$event)" nz-resizable="" nzBounds="window" nzCellControl="util.creationTime" nzPreview="" [nzLeft]="ts_tb.isLeft('util.creationTime')" [nzRight]="ts_tb.isRight('util.creationTime')" [nzShowSort]="true" [nzSortFn]="true" [titleAlign]="ts_tb.getTitleAlign('util.creationTime')">{{'util.creationTime'|i18n}} <nz-resize-handle nzDirection="right"></nz-resize-handle> </th> <th (nzResizeEnd)="ts_tb.handleResize($event,'util.lastModificationTime')" (nzSortOrderChange)="x_tb.sortChange('lastModificationTime',$event)" nz-resizable="" nzBounds="window" nzCellControl="util.lastModificationTime" nzPreview="" [nzLeft]="ts_tb.isLeft('util.lastModificationTime')" [nzRight]="ts_tb.isRight('util.lastModificationTime')" [nzShowSort]="true" [nzSortFn]="true" [titleAlign]="ts_tb.getTitleAlign('util.lastModificationTime')"> {{'util.lastModificationTime'|i18n}} <nz-resize-handle nzDirection="right"></nz-resize-handle> </th> <th (nzResizeEnd)="ts_tb.handleResize($event,'util.operation')" nz-resizable="" nzBounds="window" nzCellControl="util.operation" nzPreview="" [nzLeft]="ts_tb.isLeft('util.operation')" [nzRight]="ts_tb.isRight('util.operation')" [titleAlign]="ts_tb.getTitleAlign('util.operation')"> {{'util.operation'|i18n}} <nz-resize-handle nzDirection="right"></nz-resize-handle> </th> </tr> </thead> <tbody> <tr *ngFor="let row of x_tb.dataSource;index as index"> <td (click)="$event.stopPropagation()" (nzCheckedChange)="x_tb.toggle(row)" nzCellControl="util.checkbox" [nzAlign]="ts_tb.getAlign('util.checkbox')" [nzChecked]="x_tb.isChecked(row)" [nzLeft]="ts_tb.isLeft('util.checkbox')" [nzRight]="ts_tb.isRight('util.checkbox')" [nzShowCheckbox]="true"> </td> <td nzCellControl="util.lineNumber" [nzAlign]="ts_tb.getAlign('util.lineNumber')" [nzLeft]="ts_tb.isLeft('util.lineNumber')" [nzRight]="ts_tb.isRight('util.lineNumber')"> {{row.lineNumber}} </td> <td nzCellControl="identity.operation.name" [nzAlign]="ts_tb.getAlign('identity.operation.name')" [nzEllipsis]="ts_tb.getEllipsis('identity.operation.name')" [nzLeft]="ts_tb.isLeft('identity.operation.name')" [nzRight]="ts_tb.isRight('identity.operation.name')"> {{row.name}} </td> <td nzCellControl="identity.operation.uri" [nzAlign]="ts_tb.getAlign('identity.operation.uri')" [nzEllipsis]="ts_tb.getEllipsis('identity.operation.uri')" [nzLeft]="ts_tb.isLeft('identity.operation.uri')" [nzRight]="ts_tb.isRight('identity.operation.uri')"> {{row.uri}} </td> <td nzCellControl="identity.operation.isBase" [nzAlign]="ts_tb.getAlign('identity.operation.isBase')" [nzEllipsis]="ts_tb.getEllipsis('identity.operation.isBase')" [nzLeft]="ts_tb.isLeft('identity.operation.isBase')" [nzRight]="ts_tb.isRight('identity.operation.isBase')"> <i *ngIf="!row.isBase" nz-icon nzType="close"></i> <i *ngIf="row.isBase" nz-icon nzType="check"></i> </td> <td nzCellControl="identity.operation.remark" [nzAlign]="ts_tb.getAlign('identity.operation.remark')" [nzEllipsis]="ts_tb.getEllipsis('identity.operation.remark')" [nzLeft]="ts_tb.isLeft('identity.operation.remark')" [nzRight]="ts_tb.isRight('identity.operation.remark')"> {{row.remark}} </td> <td nzCellControl="identity.operation.enabled" [nzAlign]="ts_tb.getAlign('identity.operation.enabled')" [nzEllipsis]="ts_tb.getEllipsis('identity.operation.enabled')" [nzLeft]="ts_tb.isLeft('identity.operation.enabled')" [nzRight]="ts_tb.isRight('identity.operation.enabled')"> <nz-tag *ngIf="row.enabled" nzColor="geekblue">{{'util.enabled'|i18n}}</nz-tag> <nz-tag *ngIf="!row.enabled" nzColor="red">{{'util.notEnabled'|i18n}}</nz-tag> </td> <td nzCellControl="util.creationTime" [nzAlign]="ts_tb.getAlign('util.creationTime')" [nzEllipsis]="ts_tb.getEllipsis('util.creationTime')" [nzLeft]="ts_tb.isLeft('util.creationTime')" [nzRight]="ts_tb.isRight('util.creationTime')"> {{row.creationTime|date:'yyyy-MM-dd HH:mm'}} </td> <td nzCellControl="util.lastModificationTime" [nzAlign]="ts_tb.getAlign('util.lastModificationTime')" [nzEllipsis]="ts_tb.getEllipsis('util.lastModificationTime')" [nzLeft]="ts_tb.isLeft('util.lastModificationTime')" [nzRight]="ts_tb.isRight('util.lastModificationTime')"> {{row.lastModificationTime|date:'yyyy-MM-dd HH:mm'}} </td> <td nzCellControl="util.operation" [nzAlign]="ts_tb.getAlign('util.operation')" [nzEllipsis]="ts_tb.getEllipsis('util.operation')" [nzLeft]="ts_tb.isLeft('util.operation')" [nzRight]="ts_tb.isRight('util.operation')"> <a (click)="openDetailDialog(row)">{{'util.detail'|i18n}}</a> <ng-container *aclIf="'operation.update'"> <nz-divider nzType="vertical"></nz-divider> <a (click)="openEditDrawer(row)">{{'util.update'|i18n}}</a> </ng-container> <ng-container *aclIf="'operation.delete'"> <nz-divider nzType="vertical"></nz-divider> <a (click)="delete(row.id)" class="ant-btn-dangerous">{{'util.delete'|i18n}}</a> </ng-container> </td> </tr> </tbody> </nz-table> <ng-template #total_tb="" let-range="range" let-total=""> {{ 'util.tableTotalTemplate'|i18n:{start:range[0],end:range[1],total:total} }} </ng-template> <x-table-settings #ts_tb="" key="identity_operation" [enableFixedColumn]="true" [initColumns]="[{'title':'util.checkbox','width':x_tb.config.table.checkboxWidth,'align':'left'}, {'title':'util.lineNumber','width':x_tb.config.table.lineNumberWidth,'align':'left'}, {'title':'identity.operation.name'},{'title':'identity.operation.uri'}, {'title':'identity.operation.isBase'},{'title':'identity.operation.remark'}, {'title':'identity.operation.enabled'},{'title':'util.creationTime'}, {'title':'util.lastModificationTime'},{'title':'util.operation'}]"> </x-table-settings>
- 观察 <nz-table> 标签, 可以发现 [nzCustomColumn]=“ts_tb.columns” , 说明确实使用的是 Ng Zorro 官方提供的自定义列功能.
- 生成的 html 比较复杂, enable-table-settings 除了开启自定义列外,还会启用拖动列宽等功能.
- 前面提到, Util Ui 提供的标签可以压缩 3-10 倍的 html 代码量 , 从这里可以看出, 绝非信口雌黄.
- <x-table-settings> 是由 util-angular 脚本库提供的表格设置组件.
- <x-table-settings> 的 initColumns 属性设置了一个列信息数组, 将列集合传入表格设置组件.
- <x-table-settings> 组件经过系列工序, 输出 Ng Zorro 需要的自定义列信息.
- 所以, 无需手工编写任何 ts 脚本代码, 即可完成相关功能.
- 可以看到, TagHelper 不仅可以封装 html 复杂度,甚至能为你生成一些简单的 js 对象.
- 要打开表格设置对话框, 需要一个按钮.
- .cshtml 代码如下.
- show-table-settings 用于显示表格设置对话框, 传入表格的引用变量名 tb.
<util-a show-table-settings="tb"></util-a>
- 生成的 html 如下.
<a (click)="ts_tb.show()" nz-tooltip="" [nzTooltipTitle]="'util.tableSettings'|i18n"> <i nz-icon="" nzType="setting"></i> </a>
- Util UI 的扩展指令和组件具有一些约定的命名.
- 表格组件的引用变量名为 tb , 对应的表格设置组件则为 ts_tb .
- 表格设置组件提供了一个 show() 函数, 调用该函数即可打开表格设置窗口.
总结
本文分享了 Util 应用框架 UI 最近的突破与进展.
Util 应用框架 UI 最新架构已经稳定, 可以放心使用.
一些开发人员问到使用教程, 嗯, 这是个伤心事, Util 应用框架一直是心传口授模式, 确实没有.
不过 Util 也在考虑突破原有的使用群体, 面向更大的范围传播.
使用教程和文档已经在路上, 欢迎大家使用 , 我们将以更快的速度提供.
Util应用框架交流群:
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/77359.html