Util UI 已經(jīng)開(kāi)發(fā)多年, 并在多家公司的項(xiàng)目使用.
不過(guò)一直以來(lái), Util UI 存在一些缺陷, 始終未能解決.
最近幾個(gè)月, Util 團(tuán)隊(duì)下定決心, 終于徹底解決了所有已知缺陷.
Util 應(yīng)用框架 UI 介紹
Util 應(yīng)用框架 UI 建立在 Angular , Ng-Zorro, Ng-Alain 基礎(chǔ)之上, 用于開(kāi)發(fā)企業(yè)中后臺(tái).
Util 應(yīng)用框架 UI 的特點(diǎn)
-
簡(jiǎn)潔
Util UI 通常可以將復(fù)雜組件的 html 代碼量壓縮 3 - 10 倍,從而使項(xiàng)目的可維護(hù)性大幅提升.
下面以查詢(xún)表單為例進(jìn)行對(duì)比.
先看效果演示.
Util UI 的標(biāo)簽使用 TagHelper 進(jìn)行封裝 ,代碼如下.
<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>
上面的標(biāo)簽會(huì)轉(zhuǎn)換成 Ng Zorro 原生的 html 標(biāo)簽.
<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 的查詢(xún)表單標(biāo)簽.
查詢(xún)表單支持響應(yīng)式,并將按鈕區(qū)域始終放置在最后一行的右側(cè).
label-width 是一個(gè)擴(kuò)展的范圍設(shè)置屬性, 為每個(gè)表單組件的 <nz-form-label> 設(shè)置 style="width:120px" 樣式, 避免了分別設(shè)置每個(gè)組件的寬度.
Ng Zorro 表單組件由 <nz-form-item> , <nz-form-label> , <nz-form-control> 組合而成.
<util-input> 和 <util-select> 設(shè)置了 label-text , 這是一個(gè)擴(kuò)展屬性,它會(huì)激活 <nz-form-item> 結(jié)構(gòu)的自動(dòng)創(chuàng)建.
<util-input> 是文本框, 除了為它自動(dòng)創(chuàng)建 <nz-form-item> 結(jié)構(gòu), 還會(huì)添加清除內(nèi)容的功能.
Util UI 大多常用組件的顯示文本會(huì)自動(dòng)添加 i18n 管道, 比如 'identity.application.code'|i18n ,用于支持多語(yǔ)言.
從前面的示例可以看到 Util UI 可以大幅提升 html 標(biāo)簽的書(shū)寫(xiě)效率, 降低維護(hù)成本.
-
易用
Util 對(duì)常用功能進(jìn)行了高度封裝, 并提供簡(jiǎn)單易用的 API.
易用性是 Util UI 封裝的關(guān)鍵目標(biāo),也是 Util UI 存在的意義.
本文后續(xù)將以最近更新的一個(gè)關(guān)鍵功能 - 表格設(shè)置, 演示易用性.
-
強(qiáng)類(lèi)型提示
Util UI 提供的標(biāo)簽使用 TagHelper 技術(shù)封裝, 支持強(qiáng)類(lèi)型提示.
如果你使用 Vs Code 開(kāi)發(fā), Util UI 標(biāo)簽提示信息大致與 Ng Zorro Vs Code 插件提示效果相當(dāng).
Vs Code 的標(biāo)簽提示信息并不精準(zhǔn), 包含很多與 html 相關(guān)的屬性, 比如 aria- 打頭的屬性就占了幾屏, 這降低了代碼提示的作用.
如果使用 Vs 開(kāi)發(fā), 甚至安裝了 Resharper , 代碼提示就能達(dá)到最佳效果.
-
持續(xù)更新和改進(jìn)
Util UI 不僅僅是對(duì) Ng Zorro 功能的簡(jiǎn)單包裝, 更提供了常用功能的擴(kuò)展.
Util UI 擴(kuò)展功能來(lái)自之前使用其它 UI 框架的經(jīng)驗(yàn), 另外收集項(xiàng)目開(kāi)發(fā)時(shí)的實(shí)際需求,并加以整理,以滿(mǎn)足使用 Util UI 的項(xiàng)目.
Util 團(tuán)隊(duì)傾聽(tīng)開(kāi)發(fā)人員的心聲, 并持續(xù)改進(jìn), 從而更好的滿(mǎn)足項(xiàng)目需求.
Util 應(yīng)用框架 UI 的封裝實(shí)現(xiàn)方式
-
使用 .cshtml 替代 .html 頁(yè)面.
.cshtml 是 .Net 提供的一種高級(jí) html 封裝技術(shù).
Util 創(chuàng)造性的將 .cshtml 引入 Angular 應(yīng)用開(kāi)發(fā).
Util 將 cshtml 頁(yè)面作為 html 抽象層, 用來(lái)隱藏 html 的復(fù)雜性.
Ng Zorro 組件庫(kù)定義了大量的 Angular 組件.
使用 Angular 組件, 就是在 html 頁(yè)面中書(shū)寫(xiě)自定義的標(biāo)簽.
Util 應(yīng)用框架使用 TagHelper 對(duì) Ng Zorro 標(biāo)簽進(jìn)行封裝, 以提供更加簡(jiǎn)潔的用法.
TagHelper 是一種 .Net 標(biāo)簽, 在 .cshtml 文件中使用.
雖然 TagHelper 標(biāo)簽看上去也是一些自定義標(biāo)簽 , 但它們不是 Angular 組件.
Util 會(huì)在開(kāi)發(fā)階段將 .cshtml 文件轉(zhuǎn)換成 html.
-
使用 Angular 指令進(jìn)行擴(kuò)展.
Ng Zorro 組件庫(kù)與 EasyUI 這樣的組件庫(kù)具有顯著差異.
Ng Zorro 組件庫(kù)提供的 API 具有粒度細(xì), 擴(kuò)展性強(qiáng)的特點(diǎn).
Ng Zorro 組件的很多功能并不內(nèi)置于組件中,而是通過(guò) Demo 的形式告訴你怎么使用.
這為你提供了很大的靈活性和自由.
但也意味著,如果你不加封裝,直接在項(xiàng)目中復(fù)制使用, 就會(huì)造成大量的冗余代碼, 降低項(xiàng)目的可維護(hù)性.
要擴(kuò)展 Ng Zorro 組件, 僅使用 TagHelper 封裝 html 是不夠的, 還需要找到編寫(xiě)腳本的地方.
封裝和擴(kuò)展 Ng Zorro 組件, 通常有兩種方式.
-
一種方式是創(chuàng)建新的 Angular 組件對(duì)原始組件進(jìn)行包裝.
使用組件包裝, 可以提供更加易用的 Api.
不過(guò)這種封裝方式也有一些缺陷.
-
新組件的 API 與原始組件可能不同, 增加了學(xué)習(xí)成本.
-
由于需要將原始組件的 API 暴露出來(lái) , 導(dǎo)致更多的冗余代碼.
-
擴(kuò)展性降低.
對(duì)于表格這樣復(fù)雜的組件, html 結(jié)構(gòu)相當(dāng)復(fù)雜, 使用組件包裝通常不會(huì)保留原有的 html 結(jié)構(gòu).
擴(kuò)展點(diǎn)完全由新組件控制, 從而降低擴(kuò)展性.
-
-
另一種方式是使用 Angular 指令對(duì)原始組件進(jìn)行擴(kuò)展.
Angular 指令使用起來(lái)就像標(biāo)簽上的屬性一樣.
使用 Angular 指令進(jìn)行擴(kuò)展, 最大優(yōu)勢(shì)是保留原始組件的全部用法, 不會(huì)降低其擴(kuò)展性.
當(dāng)然指令封裝方式也帶來(lái)了新的挑戰(zhàn),那就是 html 標(biāo)簽會(huì)更加復(fù)雜.
Util UI 使用 Angular 指令進(jìn)行封裝擴(kuò)展, 并使用 TagHelper 標(biāo)簽來(lái)隱藏 html 的復(fù)雜度.
-
-
Lambda表達(dá)式支持
在 .cshtml 文件中使用 TagHelper 標(biāo)簽, 你可以直接設(shè)置標(biāo)簽上的屬性.
不過(guò) , 如果使用 .Net 開(kāi)發(fā) API 后端, 并創(chuàng)建了 DTO 對(duì)象, 你可以將 DTO 屬性直接綁定到標(biāo)簽上.
下面演示查詢(xún)表單組件如何使用Lambda表達(dá)式綁定 DTO 屬性.
DTO 代碼如下:
/// <summary> /// 應(yīng)用程序查詢(xún)參數(shù) /// </summary> public class ApplicationQuery : QueryParameter { /// <summary> /// 應(yīng)用程序編碼 ///</summary> [Description( "identity.application.code" )] public string Code { get; set; } /// <summary> /// 應(yīng)用程序名稱(chēng) ///</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> /// 起始創(chuàng)建時(shí)間 /// </summary> [Display( Name = "util.beginCreationTime" )] public DateTime? BeginCreationTime { get; set; } /// <summary> /// 結(jié)束創(chuàng)建時(shí)間 /// </summary> [Display( Name = "util.endCreationTime" )] public DateTime? EndCreationTime { get; set; } /// <summary> /// 起始最后修改時(shí)間 /// </summary> [Display( Name = "util.beginLastModificationTime" )] public DateTime? BeginLastModificationTime { get; set; } /// <summary> /// 結(jié)束最后修改時(shí)間 /// </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表達(dá)式會(huì)讀取 DTO 對(duì)象的元數(shù)據(jù), 并自動(dòng)設(shè)置常用屬性, 從而再次大幅提升生產(chǎn)力.
Util 應(yīng)用框架 UI 的組成
-
Util.Ui.NgZorro
Util.Ui.NgZorro 類(lèi)庫(kù)包含 Ng Zorro TagHelper 標(biāo)簽, 目前已封裝官方正式發(fā)布的全部組件.
-
Util.Ui.NgAlain
Util.Ui.NgAlain 類(lèi)庫(kù)包含 Ng Alain 部分組件 TagHelper 標(biāo)簽.
-
util-angular
util-angular 是一個(gè) typescript 腳本庫(kù), 包含 Ng Zorro 擴(kuò)展指令和常用操作 Helper.
Util 應(yīng)用框架 UI 最新進(jìn)展
Util 應(yīng)用框架 UI 最近進(jìn)行了全面改進(jìn),并取得了重大突破.
最大的進(jìn)展有2點(diǎn), 一是開(kāi)發(fā)機(jī)制的改進(jìn), 二是增加了表格設(shè)置功能.
-
開(kāi)發(fā)機(jī)制改進(jìn)
-
架構(gòu)缺陷
Util 應(yīng)用框架將 .cshtml 文件引入 Angular 已有相當(dāng)長(zhǎng)的年頭.
由于這種非主流的用法并沒(méi)有微軟官方的支持,所以一直存在相當(dāng)多的問(wèn)題.
-
最主要的影響是導(dǎo)致開(kāi)發(fā)階段運(yùn)行緩慢.
之前的開(kāi)發(fā)流程, Angular 組件在開(kāi)發(fā)階段直接訪(fǎng)問(wèn) cshtml 頁(yè)面,所以開(kāi)發(fā)階段必須使用 Angular JIT 模式, 它比 Angular AOT 模式要慢一些.
cshtml 在第一次訪(fǎng)問(wèn)時(shí), 尚未創(chuàng)建緩存 , 會(huì)比較慢.
Angular 應(yīng)用啟動(dòng)時(shí),將訪(fǎng)問(wèn)根模塊引用的所有頁(yè)面, 所以啟動(dòng)時(shí)會(huì)產(chǎn)生相當(dāng)?shù)目D.
這個(gè)問(wèn)題通過(guò) Angular 延遲加載模塊得到緩解.
如果項(xiàng)目比較大,包含數(shù)十個(gè)業(yè)務(wù)模塊, 將每個(gè)業(yè)務(wù)模塊創(chuàng)建為延遲加載模塊.
當(dāng)應(yīng)用啟動(dòng)時(shí), 并不會(huì)訪(fǎng)問(wèn)所有頁(yè)面, 只有請(qǐng)求了某個(gè)業(yè)務(wù)模塊的功能, 才會(huì)訪(fǎng)問(wèn)該模塊包含的 cshtml 頁(yè)面.
不過(guò)從 Angular 13 開(kāi)始, Angular 移除了傳統(tǒng)的視圖引擎, 導(dǎo)致上述開(kāi)發(fā)方式無(wú)法使用延遲加載模塊.
這意味著所有業(yè)務(wù)模塊在開(kāi)發(fā)階段必須在根模塊中引用.
Angular 應(yīng)用啟動(dòng)后將訪(fǎng)問(wèn)所有 cshtml 頁(yè)面, 這顯然是不可接受的.
一種可行的解決辦法是使用微前端方案.
微前端架構(gòu)將業(yè)務(wù)模塊分離到不同的項(xiàng)目從而減少應(yīng)用啟動(dòng)時(shí)間.
一些較大的項(xiàng)目和團(tuán)隊(duì)使用微前端架構(gòu)是合適的.
但微前端架構(gòu)具有復(fù)雜性, 使用微前端架構(gòu)代替延遲加載模塊則非常牽強(qiáng).
這是 Util 團(tuán)隊(duì)進(jìn)行全面改造的根本原因.
-
另一個(gè)影響是項(xiàng)目結(jié)構(gòu)比較復(fù)雜.
Util 采用的項(xiàng)目結(jié)構(gòu)最早來(lái)自 .Net Core Angular 項(xiàng)目模板, 并加以修改.
Angular 應(yīng)用被放在 ClientApp 目錄中.
.cshtml 文件則被放在 Pages 目錄中.
這導(dǎo)致組件與模板的對(duì)應(yīng)關(guān)系比較復(fù)雜.
-
-
改進(jìn)方案
很多時(shí)候, 解決問(wèn)題最重要是思路的轉(zhuǎn)變.
之前的架構(gòu)缺陷主要來(lái)自在開(kāi)發(fā)階段讓 Angular 組件直接請(qǐng)求 cshtml 頁(yè)面,從而與原生 Angular 應(yīng)用產(chǎn)生差別.
不過(guò), Util 使用 cshtml 僅限于開(kāi)發(fā)階段, 發(fā)布之后實(shí)際上與 cshtml 沒(méi)有任何關(guān)系.
cshtml 的作用只是幫助生成 html 而已.
現(xiàn)代化開(kāi)發(fā)一個(gè)重要的功能是熱更新, 比如 Angular 應(yīng)用, 它會(huì)持續(xù)監(jiān)視你的相關(guān)文件.
當(dāng)你編輯完 .ts 或 .html 文件時(shí), 瀏覽器就會(huì)自動(dòng)刷新.
如果我們監(jiān)視所有 .cshtml 文件,并在保存 cshtml 文件時(shí)自動(dòng)生成對(duì)應(yīng)的 html 文件,就能從根本上解決問(wèn)題.
由于只需要處理保存的 cshtml 文件, 生成 html 的速度將非常迅速.
當(dāng) html 生成完成, 后續(xù)流程則與原生 angular 應(yīng)用相同, 從而解決引入 cshtml 相關(guān)的所有缺陷.
現(xiàn)在, 編輯并保存 .cshtml 文件, 瀏覽器就會(huì)自動(dòng)刷新, 與原生 Angular 應(yīng)用相比, 大致慢幾百毫秒, 通??梢院雎圆挥?jì).
項(xiàng)目結(jié)構(gòu)復(fù)雜的問(wèn)題則很好解決, 將 .cshtml 與 Angular 組件放在一起即可.
這與原生 Angular 應(yīng)用相似, 只需修改 .cshtml 生成 html 文件的路徑規(guī)則.
一直以來(lái), Util UI的架構(gòu)比較臃腫, 只能在 Vs 中開(kāi)發(fā).
但現(xiàn)在前端基本都使用 Vs Code.
最新 UI 架構(gòu)與原生 Angular 應(yīng)用差別很小, 同樣適合使用 Vs Code 開(kāi)發(fā).
下面是使用 Vs Code 打開(kāi)的項(xiàng)目結(jié)構(gòu).
-
-
表格設(shè)置
表格是業(yè)務(wù)系統(tǒng)的基石.
我們收集了一些項(xiàng)目上使用 Ng Zorro 表格的反饋意見(jiàn).
-
當(dāng)表格列較多時(shí),如果不進(jìn)行寬度設(shè)置, 則會(huì)顯示得很畸形.
要解決這個(gè)問(wèn)題, 需要設(shè)置表格 nzScroll 屬性的 x 值.
nzScroll 的 x 可以讓表格產(chǎn)生橫向的滾動(dòng)條, 從而將表格內(nèi)容拉伸.
不過(guò)這個(gè)值應(yīng)該設(shè)置成多少合適, 則是一門(mén)學(xué)問(wèn).
通常需要計(jì)算表格中有多少列,每列大致占多少寬度, nzScroll.x 的值大致是這些寬度之和.
手工計(jì)算寬度費(fèi)時(shí)費(fèi)力, 最好是能自動(dòng)計(jì)算.
-
另一個(gè)問(wèn)題是凍結(jié)表格頭, 并讓表格在一定高度滾動(dòng).
通過(guò)設(shè)置 nzScroll 屬性的 y 值可以做到這一點(diǎn).
不過(guò)設(shè)置 nzScroll.y 也是一門(mén)學(xué)問(wèn), 因?yàn)椴煌聊淮笮】赡苄枰O(shè)置不同的值,在開(kāi)發(fā)階段很難固定.
一些公司使用某些方法計(jì)算以達(dá)到自適應(yīng)高度,不過(guò)大多針對(duì)比較固定的頁(yè)面布局,且相對(duì)簡(jiǎn)單.
更好的辦法是讓用戶(hù)在運(yùn)行時(shí)根據(jù)自己的要求動(dòng)態(tài)更新.
-
除了表格的總寬度, 每個(gè)列的寬度設(shè)置也是一個(gè)頭痛的問(wèn)題.
列寬大多與內(nèi)容相關(guān), 在開(kāi)發(fā)階段設(shè)置固定列寬, 當(dāng)內(nèi)容超過(guò)固定寬度就會(huì)出現(xiàn)換行,影響美觀.
如果在開(kāi)發(fā)階段設(shè)置一個(gè)默認(rèn)寬度, 并在運(yùn)行時(shí)可由用戶(hù)修改就能解決問(wèn)題.
當(dāng)然最好能支持拖動(dòng)表頭修改列寬, 則更為方便.
-
自定義列是很多項(xiàng)目的必備功能.
當(dāng)表格列非常多, 用戶(hù)希望只顯示其中感興趣的一部分列, 并能修改列的顯示順序.
Ng Zorro 支持自定義列功能, 不過(guò)使用起來(lái)比較復(fù)雜.
當(dāng)你啟用了自定義列, 用來(lái)固定左右側(cè)的 nzLeft 和 nzRight 就變得不那么利索.
列與列之間經(jīng)常會(huì)出現(xiàn)一些縫隙或?qū)Σ积R的現(xiàn)象, Ng Zorro 官方文檔給出了一些調(diào)整建議, 不過(guò)也是非常麻煩.
-
諸如表格批量編輯,表格行編輯, 樹(shù)形異步加載等需求都是很早之前就已經(jīng)擴(kuò)展支持, 就不在此一一列出.
下面介紹 Util UI 表格設(shè)置功能.
先來(lái)一個(gè)表格設(shè)置的效果圖.
可以看到, 它確實(shí)解決了前面提到的棘手問(wèn)題.
如何開(kāi)啟表格設(shè)置功能?
表格標(biāo)簽示例代碼.
@*表格*@ <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>
要開(kāi)啟表格設(shè)置功能, 只需要在 <util-table> 標(biāo)簽設(shè)置 enable-table-settings 屬性為 true.
你可能要問(wèn), 需要編寫(xiě) ts 腳本代碼嗎?
不用 !!!
如果你看過(guò) Ng Zorro 官方自定義列的示例, 知道需要將一個(gè) NzCustomColumn[] 對(duì)象傳入 <nz-table>的 nzCustomColumn 屬性.
那么, Util UI 的自定義列功能是否使用 Ng Zorro 官方的實(shí)現(xiàn)呢?
下面來(lái)看看生成的 html , 答案就會(huì)揭曉.
<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> 標(biāo)簽, 可以發(fā)現(xiàn) [nzCustomColumn]="ts_tb.columns" , 說(shuō)明確實(shí)使用的是 Ng Zorro 官方提供的自定義列功能.
生成的 html 比較復(fù)雜, enable-table-settings 除了開(kāi)啟自定義列外,還會(huì)啟用拖動(dòng)列寬等功能.
前面提到, Util Ui 提供的標(biāo)簽可以壓縮 3-10 倍的 html 代碼量 , 從這里可以看出, 絕非信口雌黃.
<x-table-settings> 是由 util-angular 腳本庫(kù)提供的表格設(shè)置組件.
<x-table-settings> 的 initColumns 屬性設(shè)置了一個(gè)列信息數(shù)組, 將列集合傳入表格設(shè)置組件.
<x-table-settings> 組件經(jīng)過(guò)系列工序, 輸出 Ng Zorro 需要的自定義列信息.
所以, 無(wú)需手工編寫(xiě)任何 ts 腳本代碼, 即可完成相關(guān)功能.
可以看到, TagHelper 不僅可以封裝 html 復(fù)雜度,甚至能為你生成一些簡(jiǎn)單的 js 對(duì)象.
要打開(kāi)表格設(shè)置對(duì)話(huà)框, 需要一個(gè)按鈕.
.cshtml 代碼如下.
show-table-settings 用于顯示表格設(shè)置對(duì)話(huà)框, 傳入表格的引用變量名 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 的擴(kuò)展指令和組件具有一些約定的命名.
表格組件的引用變量名為 tb , 對(duì)應(yīng)的表格設(shè)置組件則為 ts_tb .
表格設(shè)置組件提供了一個(gè) show() 函數(shù), 調(diào)用該函數(shù)即可打開(kāi)表格設(shè)置窗口.
-
總結(jié)
本文分享了 Util 應(yīng)用框架 UI 最近的突破與進(jìn)展.
Util 應(yīng)用框架 UI 最新架構(gòu)已經(jīng)穩(wěn)定, 可以放心使用.
一些開(kāi)發(fā)人員問(wèn)到使用教程, 嗯, 這是個(gè)傷心事, Util 應(yīng)用框架一直是心傳口授模式, 確實(shí)沒(méi)有.
不過(guò) Util 也在考慮突破原有的使用群體, 面向更大的范圍傳播.文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-860932.html
使用教程和文檔已經(jīng)在路上, 歡迎大家使用 , 我們將以更快的速度提供.文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-860932.html
到了這里,關(guān)于Util 應(yīng)用框架 UI 全新升級(jí)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!