写点什么

Util 应用框架 UI 全新升级

作者:何镇汐
  • 2024-04-29
    四川
  • 本文字数:17581 字

    阅读完需:约 58 分钟

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 应用框架交流群: 24791014

    用户头像

    何镇汐

    关注

    15年以上.Net开发经验,擅长代码封装 2023-11-01 加入

    15年以上.Net开发经验,擅长代码封装,主要作品为Util应用框架

    评论

    发布
    暂无评论
    Util 应用框架 UI 全新升级_何镇汐_InfoQ写作社区