.Net Core應用框架Util介紹(五)

  上篇簡要介紹了Util在Angular Ts方面的封裝情況,本文介紹Angular封裝的另一個部分,即Html的封裝。?

標準組件與業務組件?

  對于管理后臺這樣的表單系統,你通常會使用Angular Material或Ng-Zorro這樣的UI組件庫,它們提供了標準化的UI組件?

  標準組件將Ts封裝起來,以特定標簽和屬性的方式提供使用。?

  業務組件使用標準組件拼湊頁面,并從服務端API獲取數據綁定到頁面上。?

  可以看出,標準組件是業務開發的基礎,我們必須將標準組件的開發效率提升到極致。?

使用標準組件的問題?

  直接使用原生標準組件有什么問題呢??

復雜的Html結構?

  現代流行的UI組件庫,為了構造美觀大氣的視覺效果及增強組件的功能特性,一個組件需要組裝多個Html元素來表達。?

  在帶來美觀視覺體驗的同時,也導致了Html結構變得很復雜。

  Angular Material是Google以Material設計風格開發的UI組件庫。?

  我們來看一個Angular Material文本框的例子。?

<mat-form-field><input matInput placeholder="Favorite food" value="Sushi">
</mat-form-field>

  你看到了Angular Material文本框并不是一個input標簽,input標簽嵌套在mat-form-field標簽內。

  這看上去并不算復雜,不過它只是最簡單的情況,讓我們增加兩個特性。?

<mat-form-field><input matInput placeholder="測試一下" [(ngModel)]="value" ><mat-hint>哈哈</mat-hint><button mat-button *ngIf="value" matSuffix mat-icon-button (click)="value=''"><mat-icon>close</mat-icon></button>
</mat-form-field>

  我們在文本框的下方添加了提示文本,并在文本框右側加了個按鈕,你可以點擊這個按鈕清空文本框的內容。

?

  你應該觀察到Html結構變得稍微復雜了,讓我們再添加兩個特性。

<mat-form-field><input matInput  #testControl="ngModel" name="test" placeholder="金額" [(ngModel)]="value" required max="10"><span matPrefix>$ &nbsp;</span><span matSuffix></span><mat-hint>充值金額</mat-hint><button mat-button *ngIf="value" matSuffix mat-icon-button (click)="value=''"><mat-icon>close</mat-icon></button><mat-error *ngIf="testControl.hasError('max') && !testControl.hasError('required')">最大金額不能超過10元</mat-error><mat-error *ngIf="testControl.hasError('required')">這是一個必填項</mat-error>
</mat-form-field>

  現在在文本框的左側加了一個美元符號,在文本框右側添加了后綴“元”,另外添加了必填和最大值驗證。

  這還只是一個不太復雜的文本框,Html居然這么長。  

  組件標簽結構成為前端業務開發的第一個關注點

繁瑣的數據綁定

  如果要綁定一些可選項到下拉列表,一種辦法是硬編碼。

<mat-form-field><mat-select placeholder="請選一個吧"><mat-option value="1">A</mat-option><mat-option value="2">B</mat-option><mat-option value="3">C</mat-option></mat-select>
</mat-form-field> 

  這是具有三個選項的下拉列表。

?

  如果我們要綁定56個民族,就需要硬編碼56個選項,這確實可行,不過一個下拉框就60幾行,占地太廣,復制粘貼也不方便。

  另外,下拉選項可能是動態的,這些可選值存儲在數據庫中。

  數據綁定大多從服務端獲取數據,綁定到組件上。

  Angular提倡將數據訪問與組件分離,這個設計理念被Angular Material這些標準組件庫所遵循。

  為了綁定數據,你首先需要發送一個Http請求,從服務端獲取Json數據,轉換為Ts對象,然后通過Angular提供的循環語法綁定上去。

<mat-form-field><mat-select placeholder="Favorite food"><mat-option *ngFor="let food of foods" [value]="food.value">{{food.viewValue}}</mat-option></mat-select>
</mat-form-field>

  Angular Material下拉列表能夠分組,它與普通下拉列表的Html結構不同,如果服務端返回的數據格式不太友好,綁定起來將更加困難。

<mat-form-field><mat-select placeholder="Pokemon" [formControl]="pokemonControl"><mat-option>-- None --</mat-option><mat-optgroup *ngFor="let group of pokemonGroups" [label]="group.name"[disabled]="group.disabled"><mat-option *ngFor="let pokemon of group.pokemon" [value]="pokemon.value">{{pokemon.viewValue}}</mat-option></mat-optgroup></mat-select>
</mat-form-field>

?

  下拉列表并不是唯一需要數據綁定的組件,還有一些組件也需要,且它們更加復雜,比如樹型控件,表格控件,樹型表格控件等。

  數據綁定成為前端業務開發的第二個關注點?

低效的驗證

  驗證是業務健壯性的基本保障,Angular Material表單組件提供了基本的驗證方法。?

<mat-form-field><input matInput name="test" [(ngModel)]="value" required>
</mat-form-field>

  上面演示了設置必填項的方法,它相當簡單,只要把required加到input標簽上就好了。

  遺憾的是,文本框雖然得到了驗證,但卻沒有顯示出任何錯誤提示消息。

  通過添加一個mat-error標簽,可以顯示指定錯誤提示。

<mat-form-field><input matInput  #control="ngModel" name="test" [(ngModel)]="value" required><mat-error *ngIf="control.hasError('required')">這是一個必填項</mat-error>
</mat-form-field>

  如果組件上有兩個驗證條件,你需要添加兩個mat-error標簽。

<mat-form-field><input matInput  #control="ngModel" name="test" [(ngModel)]="value" required max="10"><mat-error *ngIf="control.hasError('max') && !control.hasError('required')">最大值不能超過10</mat-error><mat-error *ngIf="control.hasError('required')">這是一個必填項</mat-error>
</mat-form-field>

  注意,為了讓提示消息只在特定驗證條件失敗時才顯示,你需要在mat-error標簽上進行驗證狀態判斷。

  如果現在組件包含5個驗證條件,mat-error和它上面的判斷條件將變得相當復雜。

  另一方面,客戶端腳本驗證只是為了提升用戶體驗,用戶可以繞過界面直接請求你的服務端,所以真正的驗證必須在服務端完成。

  這樣一來,驗證需要在客戶端和服務端編寫兩次,這造成了雙倍的工作量。

  當需求發生變動,服務端和客戶端的驗證很難同步更新,維護變得更加困難。

  驗證成為前端業務開發的第三個關注點?

解決方案?

  如果開發的時候,既不用關心Html的結構,又不用關注數據怎么綁定,驗證還能自動完成,甚至連標簽和它上面的屬性也不用記憶,這就最理想不過了,該如何實現呢??

用Angular組件包裝標準組件?

  首先我們需要用Angular組件對標準組件進行包裝,以方便功能擴展,這個自定義組件稱為包裝器?

  • 封裝Html復雜結構

  我們把標準組件的Html標簽包裝起來,以屬性的形式提供訪問。

<mat-form-field><input matInput placeholder="測試一下" [(ngModel)]="value" ><mat-hint>哈哈</mat-hint><button mat-button *ngIf="value" matSuffix mat-icon-button (click)="value=''"><mat-icon>close</mat-icon></button>
</mat-form-field>

  把上面標簽包裝后變成這樣。?

<mat-textbox-wrapperplaceholder="測試一下" [(ngModel)]="value" hint="哈哈" showClearButton="true"></mat-textbox-wrapper>

  mat-hint標簽現在被轉換為hint屬性,通過showClearButton屬性來控制是否顯示清空按鈕,大幅提升了組件的易用性。?

  • 約定前后端數據格式

  不論下拉列表,還是表格,甚至樹型控件這些需要數據綁定的組件,都有一定規律可循。

  當你一遍又一遍的復制粘貼,仔細觀察這個機械乏味的綁定過程,不難抽取出公共元素,形成前后端數據綁定的通用數據格式。

  一旦抽取出前后端通用數據格式,你只需將業務數據轉換為通用格式,發送到客戶端就自動綁定完成。

  • 將數據操作內置到包裝器

  如果你曾經使用過EasyUi這樣的組件庫,定會發現它的數據綁定功能十分強大,這是因為它把數據操作內置到了標準組件中。

  基于Angular低耦合設計原則,Angular Material標準組件并不會直接請求服務端,任何數據綁定工作都需要你手工完成,不過我們可以將數據操作內置到包裝器。?

  一旦封裝完成,數據綁定變得非常簡單,比如設置一個url屬性即可,服務端返回約定的數據格式。

<mat-select-wrapper url="/api/test"></mat-select-wrapper>

用TagHelper封裝Angular組件?

  Angular包裝器組件,大幅簡化了標準組件的使用,但它提供的依然是Html,而自定義Html標簽和屬性沒有什么提示,這意味著你如果記不住這些API,就需要隨時欣賞API文檔。

  TagHelper終于閃亮登場。

  • 強類型代碼提示和編譯時檢查?

  一旦把Html標簽封裝成TagHelper,就可以跟API文檔拜拜了,把代碼提示點出來,慢慢選,只要你知道該組件確實有這功能,哪怕印象有點模糊也沒關系。

  Html標簽和屬性的拼寫錯誤也將與你無緣,VS大哥會為你把關,代碼健壯性將大幅提升。

?

  • Lambda表達式元數據解析

  很多人已經認識到HtmlHelper或TagHelper的好處是強類型提示,不過這個認識還很膚淺。

  TagHelper真正的威力來自Lambda表達式元數據解析,它提供了一個統一的抽象方式,自動設置表單組件的常規屬性、驗證,甚至數據綁定。

  對于Angular Material表單組件,通常需要設置以下常規屬性:

    •   控件名稱 name
    •   占位文本 placeholder
    •   雙向綁定 ngModel

  常規驗證:

    •   必填項驗證 required
    •   Email驗證 email
    •   最小長度驗證 minlength
    •   最大長度驗證 maxlength
    •   最小值驗證 min
    •   最大值驗證 max
    •   正則驗證 pattern

  幾乎所有表單組件都需要設置這三個常規屬性,而文本框更需要進行多種驗證,雖然這些操作并不復雜,但由于一個表單界面包含很多組件,每個組件都要挨個設置,既浪費時間又枯燥乏味。?

  如果能夠自動化設置這些常規屬性和驗證屬性,雖然從單個組件看并不起眼,但從整個項目的角度,能大幅提升生產力。?

  Lambda表達式元數據解析,通過讀取C#屬性的類型信息以及相關的特性,能夠自動化設置三大常規屬性,以及對文本框實施多種驗證,還解決了客戶端與服務端驗證無法同步的難題。?

  一旦用上Lambda表達式,界面標簽將變得干凈整潔,你的關注點將迅速轉移到業務上。

?

  上面的TagHelper標簽生成的結果Html如下。?

<mat-textbox-wrapper name="code" placeholder="應用程序編碼" requiredMessage="應用程序編碼不能為空" [(model)]="model&&model.code" [maxLength]="60" [required]="true"></mat-textbox-wrapper>

  for指向了ApplicationDto對象的Code屬性,下面是Code屬性的定義。

?

  從Code屬性定義可以解析出該組件需要設置的常規屬性和驗證屬性。

?

  上面演示了文本框組件,對于單選框,多選框,下拉框等表單組件,都可以使用相同的方式,一個for屬性,基礎工作已經完成。

封裝的弊端

  看了前面的解決方案,你知道經過幾層高強度封裝后,組件將變得簡單易用,不過在將這些方法應用到你的項目之前,你需要對這些方法有更深的了解。

  任何事物都有其兩面性,所謂此消彼長,在組件變得更加簡單易用的同時,它的靈活性也在降低

  包裝器組件將Html結構封裝起來,這會導致組件不再支持模板化,如果某個功能在你的包裝器中未實現,那么不能通過在包裝器標簽內嵌套HTML的方式組合出新的功能。

  封裝包裝器組件有相當多的講究,特別是Angular Material這樣的組件庫在功能上幾乎無法與EasyUi或Ext等企業級UI庫相提并論,你必須在易用性和靈活性間進行平衡,對于像表格這樣的重量級組件,很難封裝到完全滿足業務需求,這種情況下,你必須為其保留模板化能力。

  另一方面,封裝后的傻瓜式TagHelper,很容易把程序員慣壞,開發常用功能風升水起,一碰到超出框架范圍的需求就變得束手無策,因為他們從來沒有學習過原生的知識。

  你團隊的主力開發人員必須對原生技術有系統了解。

  一旦功能超出框架范圍,你必須有能力擴展框架,在必要的時候,直接使用原生Html進行開發,這時候你更能體會到TagHelper與Html混合編程的好處,既提升了常規功能的開發效率,又滿足了復雜功能對操作體驗的需求。

Util組件介紹

  下面簡要介紹Util中封裝的幾個常用組件,它們來自Angular Material或PrimeNg組件庫。?

文本框

  前面已經展示過文本框的用法,除了常規屬性設置和驗證以外,for指向屬性的數據類型會影響生成的文本框類型,比如屬性為日期類型,文本框會變成一個日期選擇控件。??

        /// <summary>/// 創建時間/// </summary>[Display( Name = "創建時間" )]public DateTime? CreationTime { get; set; } 
<util-textbox for="CreationTime"></util-textbox>

  生成的Html結果如下。?

<mat-datepicker-wrapper name="creationTime" placeholder="創建時間" [(model)]="model&&model.creationTime"></mat-datepicker-wrapper>

  下面再演示一下數值類型,添加了最大和最小值驗證,并設置前綴文本和后綴圖標。?當屬性為數值類型時,文本框只能輸入數字。

        /// <summary>/// 金額/// </summary>[Required( ErrorMessage = "必須填寫金額" )][Range(10,50,ErrorMessage = "有效金額在10到50之間")][Display( Name = "金額" )]public decimal Money { get; set; }

  生成的Html結果如下。?

    <mat-textbox-wrapper type="number" name="money" placeholder="金額" [(model)]="model&&model.money"   startHint="a" endHint="b" prefixText="$"   suffixFontAwesomeIcon="fa-apple"[required]="true" requiredMessage="必須填寫金額"[min]="10" [max]="50"  minMessage="有效金額在10到50之間" maxMessage="有效金額在10到50之間"></mat-textbox-wrapper>

  來看看執行效果。

?

下拉列表

  下拉列表的封裝,重點在于數據綁定

  • 綁定枚舉?

  下面演示如何把民族枚舉綁定到下拉列表。?

  C#代碼如下。?

  1    /// <summary>
  2     /// 民族
  3     /// </summary>
  4     public enum Nation {
  5         /// <summary>
  6         /// 漢族        
  7         /// </summary>
  8         [Description( "漢族" )]
  9         Hz = 0,
 10         /// <summary>
 11         /// 蒙古族        
 12         /// </summary>
 13         [Description( "蒙古族" )]
 14         Mgz = 1,
 15         /// <summary>
 16         /// 回族        
 17         /// </summary>
 18         [Description( "回族" )]
 19         HuiZ = 2,
 20         /// <summary>
 21         /// 藏族        
 22         /// </summary>
 23         [Description( "藏族" )]
 24         Zz = 3,
 25         /// <summary>
 26         /// 維吾爾族        
 27         /// </summary>
 28         [Description( "維吾爾族" )]
 29         Wwez = 4,
 30         /// <summary>
 31         /// 苗族        
 32         /// </summary>
 33         [Description( "苗族" )]
 34         Mz = 5,
 35         /// <summary>
 36         /// 彝族        
 37         /// </summary>
 38         [Description( "彝族" )]
 39         Yz = 6,
 40         /// <summary>
 41         /// 壯族        
 42         /// </summary>
 43         [Description( "壯族" )]
 44         ZhuangZ = 7,
 45         /// <summary>
 46         /// 布依族        
 47         /// </summary>
 48         [Description( "布依族" )]
 49         Byz = 8,
 50         /// <summary>
 51         /// 朝鮮族        
 52         /// </summary>
 53         [Description( "朝鮮族" )]
 54         Cxz = 9,
 55         /// <summary>
 56         /// 滿族        
 57         /// </summary>
 58         [Description( "滿族" )]
 59         ManZ = 10,
 60         /// <summary>
 61         /// 侗族        
 62         /// </summary>
 63         [Description( "侗族" )]
 64         Tz = 11,
 65         /// <summary>
 66         /// 瑤族        
 67         /// </summary>
 68         [Description( "瑤族" )]
 69         YaoZ = 12,
 70         /// <summary>
 71         /// 白族        
 72         /// </summary>
 73         [Description( "白族" )]
 74         Bz = 13,//baizu
 75         /// <summary>
 76         /// 土家族        
 77         /// </summary>
 78         [Description( "土家族" )]
 79         Tjz = 14,
 80         /// <summary>
 81         /// 哈尼族        
 82         /// </summary>
 83         [Description( "哈尼族" )]
 84         Hnz = 15,
 85         /// <summary>
 86         /// 哈薩克族        
 87         /// </summary>
 88         [Description( "哈薩克族" )]
 89         Hskz = 16,
 90         /// <summary>
 91         /// 傣族        
 92         /// </summary>
 93         [Description( "傣族" )]
 94         Dz = 17,
 95         /// <summary>
 96         /// 黎族        
 97         /// </summary>
 98         [Description( "黎族" )]
 99         Lz = 18,
100         /// <summary>
101         /// 傈僳族        
102         /// </summary>
103         [Description( "傈僳族" )]
104         Lsz = 19,
105         /// <summary>
106         /// 佤族        
107         /// </summary>
108         [Description( "佤族" )]
109         Wz = 20,
110         /// <summary>
111         /// 畬族        
112         /// </summary>
113         [Description( "畬族" )]
114         Sz = 21,
115         /// <summary>
116         /// 高山族        
117         /// </summary>
118         [Description( "高山族" )]
119         Gsz = 22,
120         /// <summary>
121         /// 拉祜族        
122         /// </summary>
123         [Description( "拉祜族" )]
124         Lhz = 23,
125         /// <summary>
126         /// 水族        
127         /// </summary>
128         [Description( "水族" )]
129         ShuiZ = 24,
130         /// <summary>
131         /// 東鄉族        
132         /// </summary>
133         [Description( "東鄉族" )]
134         Dxz = 25,
135         /// <summary>
136         /// 納西族        
137         /// </summary>
138         [Description( "納西族" )]
139         Nxz = 26,
140         /// <summary>
141         /// 景頗族        
142         /// </summary>
143         [Description( "景頗族" )]
144         Jpz = 27,
145         /// <summary>
146         /// 柯爾克孜族        
147         /// </summary>
148         [Description( "柯爾克孜族" )]
149         Kekzz = 28,
150         /// <summary>
151         /// 土族        
152         /// </summary>
153         [Description( "土族" )]
154         TuZ = 29,
155         /// <summary>
156         /// 達斡爾族        
157         /// </summary>
158         [Description( "達斡爾族" )]
159         Dwez = 30,
160         /// <summary>
161         /// 仫佬族        
162         /// </summary>
163         [Description( "仫佬族" )]
164         Mlz = 31,
165         /// <summary>
166         /// 羌族        
167         /// </summary>
168         [Description( "羌族" )]
169         Qz = 32,
170         /// <summary>
171         /// 布朗族        
172         /// </summary>
173         [Description( "布朗族" )]
174         Blz = 33,
175         /// <summary>
176         /// 撒拉族        
177         /// </summary>
178         [Description( "撒拉族" )]
179         Slz = 34,
180         /// <summary>
181         /// 毛南族        
182         /// </summary>
183         [Description( "毛南族" )]
184         Mnz = 35,
185         /// <summary>
186         /// 仡佬族        
187         /// </summary>
188         [Description( "仡佬族" )]
189         Ylz = 36,
190         /// <summary>
191         /// 錫伯族        
192         /// </summary>
193         [Description( "錫伯族" )]
194         Xbz = 37,
195         /// <summary>
196         /// 阿昌族        
197         /// </summary>
198         [Description( "阿昌族" )]
199         Acz = 38,
200         /// <summary>
201         /// 普米族        
202         /// </summary>
203         [Description( "普米族" )]
204         Pmz = 39,
205         /// <summary>
206         /// 塔吉克族        
207         /// </summary>
208         [Description( "塔吉克族" )]
209         Tjkz = 40,
210         /// <summary>
211         /// 怒族        
212         /// </summary>
213         [Description( "怒族" )]
214         Nz = 41,
215         /// <summary>
216         /// 烏孜別克族        
217         /// </summary>
218         [Description( "烏孜別克族" )]
219         Wzbkz = 42,
220         /// <summary>
221         /// 俄羅斯族        
222         /// </summary>
223         [Description( "俄羅斯族" )]
224         Elsz = 43,
225         /// <summary>
226         /// 鄂溫克族        
227         /// </summary>
228         [Description( "鄂溫克族" )]
229         Ewkz = 44,
230         /// <summary>
231         /// 德昂族        
232         /// </summary>
233         [Description( "德昂族" )]
234         Daz = 45,
235         /// <summary>
236         /// 保安族        
237         /// </summary>
238         [Description( "保安族" )]
239         Baz = 46,
240         /// <summary>
241         /// 裕固族        
242         /// </summary>
243         [Description( "裕固族" )]
244         Ygz = 47,
245         /// <summary>
246         /// 京族        
247         /// </summary>
248         [Description( "京族" )]
249         Jz = 48,
250         /// <summary>
251         /// 塔塔爾族        
252         /// </summary>
253         [Description( "塔塔爾族" )]
254         Ttrz = 49,
255         /// <summary>
256         /// 獨龍族        
257         /// </summary>
258         [Description( "獨龍族" )]
259         Dlz = 50,
260         /// <summary>
261         /// 鄂倫春族        
262         /// </summary>
263         [Description( "鄂倫春族" )]
264         Elcz = 51,
265         /// <summary>
266         /// 赫哲族        
267         /// </summary>
268         [Description( "赫哲族" )]
269         Hzz = 52,
270         /// <summary>
271         /// 門巴族        
272         /// </summary>
273         [Description( "門巴族" )]
274         Mbz = 53,
275         /// <summary>
276         /// 珞巴族        
277         /// </summary>
278         [Description( "珞巴族" )]
279         Lbz = 54,
280         /// <summary>
281         /// 基諾族        
282         /// </summary>
283         [Description( "基諾族" )]
284         Jnz = 55
285     }
民族枚舉?
        /// <summary>/// 民族/// </summary>[Required( ErrorMessage = "必須選擇一個民族" )][Display( Name = "民族" )][DataMember]public Nation Nation { get; set; }

  TagHelper代碼如下。

<util-select for="Nation"></util-select>

  生成的Html如下,可以看出,民族可選項被硬編碼到Html標簽中。

    <mat-select-wrapper name="nation" placeholder="民族" requiredMessage="必須選擇一個民族" [(model)]="model&&model.nation"[dataSource]="[{'text':'漢族','value':0,'sortId':0},{'text':'蒙古族','value':1,'sortId':1},{'text':'回族','value':2,'sortId':2},{'text':'藏族','value':3,'sortId':3},{'text':'維吾爾族','value':4,'sortId':4},{'text':'苗族','value':5,'sortId':5},{'text':'彝族','value':6,'sortId':6},{'text':'壯族','value':7,'sortId':7},{'text':'布依族','value':8,'sortId':8},{'text':'朝鮮族','value':9,'sortId':9},{'text':'滿族','value':10,'sortId':10},{'text':'侗族','value':11,'sortId':11},{'text':'瑤族','value':12,'sortId':12},{'text':'白族','value':13,'sortId':13},{'text':'土家族','value':14,'sortId':14},{'text':'哈尼族','value':15,'sortId':15},{'text':'哈薩克族','value':16,'sortId':16},{'text':'傣族','value':17,'sortId':17},{'text':'黎族','value':18,'sortId':18},{'text':'傈僳族','value':19,'sortId':19},{'text':'佤族','value':20,'sortId':20},{'text':'畬族','value':21,'sortId':21},{'text':'高山族','value':22,'sortId':22},{'text':'拉祜族','value':23,'sortId':23},{'text':'水族','value':24,'sortId':24},{'text':'東鄉族','value':25,'sortId':25},{'text':'納西族','value':26,'sortId':26},{'text':'景頗族','value':27,'sortId':27},{'text':'柯爾克孜族','value':28,'sortId':28},{'text':'土族','value':29,'sortId':29},{'text':'達斡爾族','value':30,'sortId':30},{'text':'仫佬族','value':31,'sortId':31},{'text':'羌族','value':32,'sortId':32},{'text':'布朗族','value':33,'sortId':33},{'text':'撒拉族','value':34,'sortId':34},{'text':'毛南族','value':35,'sortId':35},{'text':'仡佬族','value':36,'sortId':36},{'text':'錫伯族','value':37,'sortId':37},{'text':'阿昌族','value':38,'sortId':38},{'text':'普米族','value':39,'sortId':39},{'text':'塔吉克族','value':40,'sortId':40},{'text':'怒族','value':41,'sortId':41},{'text':'烏孜別克族','value':42,'sortId':42},{'text':'俄羅斯族','value':43,'sortId':43},{'text':'鄂溫克族','value':44,'sortId':44},{'text':'德昂族','value':45,'sortId':45},{'text':'保安族','value':46,'sortId':46},{'text':'裕固族','value':47,'sortId':47},{'text':'京族','value':48,'sortId':48},{'text':'塔塔爾族','value':49,'sortId':49},{'text':'獨龍族','value':50,'sortId':50},{'text':'鄂倫春族','value':51,'sortId':51},{'text':'赫哲族','value':52,'sortId':52},{'text':'門巴族','value':53,'sortId':53},{'text':'珞巴族','value':54,'sortId':54},{'text':'基諾族','value':55,'sortId':55}]"[required]="true"></mat-select-wrapper>

  執行效果如下。


  • 綁定服務端數據

  為了綁定服務端數據,必須約定通用數據格式,對于下拉列表,服務端C#是由Util.Item來完成的。

 1 using System;
 2 using Newtonsoft.Json;
 3 
 4 namespace Util {
 5     /// <summary>
 6     /// 列表項
 7     /// </summary>
 8     public class Item : IComparable<Item> {
 9         /// <summary>
10         /// 初始化
11         /// </summary>
12         /// <param name="text">文本</param>
13         /// <param name="value"></param>
14         /// <param name="sortId">排序號</param>
15         /// <param name="group"></param>
16         /// <param name="disabled">禁用</param>
17         public Item( string text, object value, int? sortId = null, string group = null, bool? disabled = null ) {
18             Text = text;
19             Value = value;
20             SortId = sortId;
21             Group = group;
22             Disabled = disabled;
23         }
24 
25         /// <summary>
26         /// 文本
27         /// </summary>
28         [JsonProperty( "text", NullValueHandling = NullValueHandling.Ignore )]
29         public string Text { get; }
30 
31         /// <summary>
32         ///33         /// </summary>
34         [JsonProperty( "value", NullValueHandling = NullValueHandling.Ignore )]
35         public object Value { get; }
36 
37         /// <summary>
38         /// 排序號
39         /// </summary>
40         [JsonProperty( "sortId", NullValueHandling = NullValueHandling.Ignore )]
41         public int? SortId { get; }
42 
43         /// <summary>
44         ///45         /// </summary>
46         [JsonProperty( "group", NullValueHandling = NullValueHandling.Ignore )]
47         public string Group { get; }
48 
49         /// <summary>
50         /// 禁用
51         /// </summary>
52         [JsonProperty( "disabled", NullValueHandling = NullValueHandling.Ignore )]
53         public bool? Disabled { get; }
54 
55         /// <summary>
56         /// 比較
57         /// </summary>
58         /// <param name="other">其它列表項</param>
59         public int CompareTo( Item other ) {
60             return string.Compare( Text, other.Text, StringComparison.CurrentCulture );
61         }
62     }
63 }
Util.Item

  客戶端Typescript定義了對應的結構。

  1 //============== 列表=============================
  2 //Copyright 2018 何鎮汐
  3 //Licensed under the MIT license
  4 //================================================
  5 import { ISort, sort } from '../core/sort';
  6 import { util } from '../index';
  7 
  8 /**
  9  * 列表
 10  */
 11 export class Select {
 12     /**
 13      * 初始化列表
 14      * @param items 列表項集合
 15      */
 16     constructor(private items: SelectItem[]) {
 17     }
 18 
 19     /**
 20      * 轉換為下拉列表項集合
 21      */
 22     toOptions(): SelectOption[] {
 23         return this.getSortedItems().map(value => new SelectOption(value));
 24     }
 25 
 26     /**
 27      * 獲取已排序的列表項集合
 28      */
 29     private getSortedItems() {
 30         return sort(this.items);
 31     }
 32 
 33     /**
 34      * 轉換為下拉列表組集合
 35      */
 36     toGroups(): SelectOptionGroup[] {
 37         let result: SelectOptionGroup[] = new Array<SelectOptionGroup>();
 38         let groups = util.helper.groupBy(this.getSortedItems(), t => t.group);
 39         groups.forEach((items, key) => {
 40             result.push(new SelectOptionGroup(key, items.map(item => new SelectOption(item)), false));
 41         });
 42         return result;
 43     }
 44 
 45     /**
 46      * 是否列表組
 47      */
 48     isGroup(): boolean {
 49         return this.items.every(value => !!value.group);
 50     }
 51 }
 52 
 53 /**
 54  * 列表項
 55  */
 56 export class SelectItem implements ISort {
 57     /**
 58      * 文本
 59      */
 60     text: string;
 61     /**
 62      * 值
 63      */
 64     value;
 65     /**
 66      * 禁用
 67      */
 68     disabled?: boolean;
 69     /**
 70      * 排序號
 71      */
 72     sortId?: number;
 73     /**
 74      * 組
 75      */
 76     group?: string;
 77 }
 78 
 79 /**
 80  * 下拉列表項
 81  */
 82 export class SelectOption {
 83     /**
 84      * 文本
 85      */
 86     text: string;
 87     /**
 88      * 值
 89      */
 90     value;
 91     /**
 92      * 禁用
 93      */
 94     disabled?: boolean;
 95 
 96     /**
 97      * 初始化下拉列表項
 98      * @param item 列表項
 99      */
100     constructor(item: SelectItem) {
101         this.text = item.text;
102         this.value = item.value;
103         this.disabled = item.disabled;
104     }
105 }
106 
107 /**
108  * 下拉列表組
109  */
110 export class SelectOptionGroup {
111     /**
112      * 初始化下拉列表組
113      * @param text 文本
114      * @param value 值
115      * @param disabled 禁用
116      */
117     constructor(public text: string, public value: SelectOption[], public disabled?: boolean) {
118     }
119 }
SelectItem

  下面來演示一下用法。?

  先把Nation屬性的類型改成int。

        /// <summary>/// 民族/// </summary>[Required( ErrorMessage = "必須選擇一個民族" )][Display( Name = "民族" )][DataMember]public int Nation { get; set; }

  在WebApi控制器中,添加一個方法,用來獲取民族枚舉可選項。

  通過Util.Helpers.Enum.GetItems方法可以提取出枚舉項列表,返回值為List<Item>,這正是我們約定的標準格式,如果返回的是業務類型列表,應轉換為List<Item>。

  Success方法用來將List<Item>轉換為前后端約定的標準結果類型Result。?

        /// <summary>/// 獲取民族可選項列表/// </summary>[HttpGet( "nationItems" )]public IActionResult GetNationItems() {List<Item> items = Util.Helpers.Enum.GetItems<Util.Biz.Enums.Nation>();return Success( items );}

  再來看TagHelper標簽,for屬性承包了常規的機械工作,你將注意力集中在業務上,通過手工設置url屬性來加載遠程數據。?

<util-select for="Nation" url="/api/test/nationItems"></util-select>

  效果跟直接綁定枚舉一樣,不過生成的Html簡單很多。?

<mat-select-wrapper name="nation" placeholder="民族" requiredMessage="必須選擇一個民族" url="/api/application/nationItems" [(model)]="model&&model.nation" [required]="true"></mat-select-wrapper>

  上面演示的下拉列表并未分組,我們來改造一下,讓它以分組顯示。?

        /// <summary>/// 獲取民族可選項列表/// </summary>[HttpGet( "nationItems" )]public IActionResult GetNationItems() {var result = Util.Helpers.Enum.GetItems<Util.Biz.Enums.Nation>().GroupBy( t => Util.Helpers.String.PinYin( t.Text.Substring( 0,1 ) ) ).SelectMany( t => t.ToList().Select( item => new Item( item.Text, item.Value, item.SortId, t.Key ) ) );return Success( result );}

  Util包含大量有用的Helper,Util.Helpers.String.PinYin方法能夠將漢字轉換為拼音首字母縮寫,使用GroupBy方法將民族拼音首字母進行分組,并轉換為Item標準格式。

  執行效果如下。

?

  TagHelper沒有任何變化,Angular Material下拉列表是否分組,其原生Html格式完全不同,但封裝以后,你根本感覺不到它們的區別,你不需要編寫任何一行Ts代碼,就完成了分組下拉列表的綁定,你應該已經體會到封裝的強大之處。?

單選按鈕

  單選按鈕和下拉列表類似,下面演示一下枚舉綁定。

  C#代碼如下。

    /// <summary>/// 性別/// </summary>public enum Gender {/// <summary>////// </summary>        [Description( "女士" )]Female = 1,/// <summary>////// </summary>[Description( "先生" )]Male = 2}
        /// <summary>/// 性別/// </summary>[Display( Name = "性別" )]public Gender Gender { get; set; }

  TagHelper標簽如下。

<util-radio for="Gender"></util-radio>

  生成的Html標簽如下。

    <mat-radio-wrapper label="性別" name="gender" [(model)]="model&&model.gender" [dataSource]="[{'text':'女士','value':1,'sortId':1},{'text':'先生','value':2,'sortId':2}]"></mat-radio-wrapper>

  執行效果如下。

?

復選框

  復選框用于操作布爾類型。

  C#代碼如下。

        /// <summary>/// 啟用/// </summary>[Display( Name = "啟用" )][DataMember]public bool? Enabled { get; set; }

  TagHelper標簽如下。

<util-checkbox for="Enabled"></util-checkbox>

  生成的Html標簽如下。

<mat-checkbox name="gender" [(ngModel)]="model&&model.gender">性別</mat-checkbox>

  執行效果如下。

?

?

滑動開關

  滑動開關與復選框功能相同,但長像更具現代化氣質。

  TagHelper標簽如下。

<util-slide-toggle for="Enabled"></util-slide-toggle>

  生成的Html標簽如下。

<mat-slide-toggle name="enabled" [(ngModel)]="model&&model.enabled">啟用</mat-slide-toggle>

  執行效果如下。

?

?

表格?

  Angular Material表格提供了一套模板化機制,你需要任何功能,往表格標簽中添加元素就好了。

  像序號,多選,分頁等常規功能都沒有內置到Angular Material表格中,Angular Material官網以Demo的形式提供了參考樣例,如果你直接使用它來進行業務開發,將導致十分低效的開發效率。

  Util將自動生成序號,多選,分頁,排序等常見功能以及數據綁定能力封裝到表格包裝器組件中,同時,Util保留了Angular Material表格的模板化能力,你依然可以通過往表格標簽中添加元素的方式擴展功能。

  由于大多表格都需要分頁,約定的后臺數據格式由PagerList承載,Ts也定義了類似的分頁列表對象。

  下面演示一個簡單的表格示例。

  服務端已經封裝了通用的查詢方法,留待下篇介紹。

  先看看TagHelper代碼。?

 1 <util-table id="tableApplication" query-param="queryParam" base-url="application"
 2             sort="CreationTime" sort-direction="Desc" max-height="500">
 3     <util-table-column type="Checkbox"></util-table-column>
 4     <util-table-column type="LineNumber"></util-table-column>
 5     <util-table-column for="Code" sort="true"></util-table-column>
 6     <util-table-column for="Name" sort="true"></util-table-column>
 7     <util-table-column for="Enabled" sort="true"></util-table-column>
 8     <util-table-column for="RegisterEnabled" sort="true"></util-table-column>
 9     <util-table-column for="CreationTime" sort="true"></util-table-column>
10     <util-table-column title="操作" column="operation">
11         <util-table-cell>
12             <util-a styles="Icon" tooltip="編輯" bind-link="['update',row.id]">
13                 <util-icon material-icon="Edit"></util-icon>
14             </util-a>
15             <util-button styles="Icon" menu-id="menu">
16                 <util-icon material-icon="More_Vert"></util-icon>
17                 <util-menu id="menu">
18                     <util-menu-item label="刪除" material-icon="Delete" on-click="delete(row.id)"></util-menu-item>
19                     <util-menu-item label="查看詳細" material-icon="Visibility" bind-link="['detail',row.id]"></util-menu-item>
20                 </util-menu>
21             </util-button>
22         </util-table-cell>
23     </util-table-column>
24 </util-table>

  生成的Html如下。?

 1 <mat-table-wrapper #tableApplication="" baseUrl="application" key="application" maxHeight="500" [(queryParam)]="queryParam"><mat-table matSort="" matSortActive="CreationTime" matSortDirection="desc" matSortDisableClear="" [dataSource]="tableApplication.dataSource" [style.max-height]="tableApplication.maxHeight?tableApplication.maxHeight+'px':null" [style.min-height]="tableApplication.minHeight?tableApplication.minHeight+'px':null">
 2     <ng-container matColumnDef="selectCheckbox"><mat-header-cell *matHeaderCellDef=""><mat-checkbox (change)="$event?tableApplication.masterToggle():null" [checked]="tableApplication.isMasterChecked()" [disabled]="!tableApplication.dataSource.data.length" [indeterminate]="tableApplication.isMasterIndeterminate()"></mat-checkbox></mat-header-cell><mat-cell *matCellDef="let row"><mat-checkbox (change)="$event?tableApplication.checkedSelection.toggle(row):null" (click)="$event.stopPropagation()" [checked]="tableApplication.checkedSelection.isSelected(row)"></mat-checkbox></mat-cell></ng-container>
 3     <ng-container matColumnDef="lineNumber"><mat-header-cell *matHeaderCellDef="">ID</mat-header-cell><mat-cell *matCellDef="let row">{{ row.lineNumber }}</mat-cell></ng-container>
 4     <ng-container matColumnDef="code"><mat-header-cell *matHeaderCellDef="" mat-sort-header="">應用程序編碼</mat-header-cell><mat-cell *matCellDef="let row">{{ row.code }}</mat-cell></ng-container>
 5     <ng-container matColumnDef="name"><mat-header-cell *matHeaderCellDef="" mat-sort-header="">應用程序名稱</mat-header-cell><mat-cell *matCellDef="let row">{{ row.name }}</mat-cell></ng-container>
 6     <ng-container matColumnDef="enabled"><mat-header-cell *matHeaderCellDef="" mat-sort-header="">啟用</mat-header-cell><mat-cell *matCellDef="let row"><mat-icon *ngIf="row.enabled">check</mat-icon><mat-icon *ngIf="!row.enabled">clear</mat-icon></mat-cell></ng-container>
 7     <ng-container matColumnDef="registerEnabled"><mat-header-cell *matHeaderCellDef="" mat-sort-header="">啟用注冊</mat-header-cell><mat-cell *matCellDef="let row"><mat-icon *ngIf="row.registerEnabled">check</mat-icon><mat-icon *ngIf="!row.registerEnabled">clear</mat-icon></mat-cell></ng-container>
 8     <ng-container matColumnDef="creationTime"><mat-header-cell *matHeaderCellDef="" mat-sort-header="">創建時間</mat-header-cell><mat-cell *matCellDef="let row">{{ row.creationTime | date:"yyyy-MM-dd" }}</mat-cell></ng-container>
 9     <ng-container matColumnDef="operation"><mat-header-cell *matHeaderCellDef="">操作</mat-header-cell>
10         <mat-cell *matCellDef="let row">
11             <a mat-icon-button="" matTooltip="編輯" [routerLink]="['update',row.id]">
12                 <mat-icon>edit</mat-icon>
13             </a>
14             <button mat-icon-button="" type="button" [matMenuTriggerFor]="menu">
15                 <mat-icon>more_vert</mat-icon>
16                 <mat-menu #menu="matMenu"><ng-template matMenuContent="">
17                     <button (click)="delete(row.id)" mat-menu-item=""><mat-icon>delete</mat-icon><span>刪除</span></button>
18                     <button mat-menu-item="" [routerLink]="['detail',row.id]"><mat-icon>visibility</mat-icon><span>查看詳細</span></button>
19                 </ng-template></mat-menu>
20             </button>
21         </mat-cell>
22     </ng-container>
23 <mat-header-row *matHeaderRowDef="['selectCheckbox','lineNumber','code','name','enabled','registerEnabled','creationTime','operation'];sticky:true"></mat-header-row><mat-row (click)="tableApplication.selectedSelection.select(row)" *matRowDef="let row;columns:['selectCheckbox','lineNumber','code','name','enabled','registerEnabled','creationTime','operation']" class="mat-row-hover" [class.selected]="tableApplication.selectedSelection.isSelected(row)"></mat-row></mat-table></mat-table-wrapper>

  可以看見,Html比TagHelper代碼要復雜得多,這還是封裝過后的情況,如果完全沒有封裝,折騰一個表格將會耗費你大量精力,且Bug遍地,難以維護。?

  Ts代碼幾乎看不見,你只需設置base-url屬性,數據綁定就完成了。?

  base-url是一個基地址,根據約定創建服務端請求地址/api/baseUrl,如果你的請求地址不同,可以改為設置url屬性。?

  執行效果如下。

樹型表格

  樹型層次關系是業務常見操作之一。

  Util Angular Material的封裝主要是在Angular Material 5.x之前完成的,Angular Material 6.x才提供了樹型控件,所以Util尚未封裝樹型控件,不過為了解決編輯樹型層次困難的局面,我從PrimeNg組件庫Copy了一個樹型表格過來。

  PrimeNg是另一個開源的Angular組件庫,它的樹型表格功能非常弱,我花了數天時間來修改它的源碼,以滿足我的基本需求。

  由于樹型包含同步加載,異步加載,上移,下移,單選,多選等操作,封裝樹型表格比普通表格要復雜得多。

  服務端提供了PrimeTreeControllerBasePrimeTreeNode等對象類型來實現與客戶端通信,不過它們都還相當具體化,待后續封裝Ng-Zorro樹型組件時再來重構。

  一旦封裝完成,它用起來就跟Angular Material表格幾乎沒什么區別。

  來看個示例。

  TagHelper代碼如下。?

<util-tree-table id="treeTable_role" base-url="role" query-param="queryParam" selection-mode="Multiple" key="treeTable_role" ><util-tree-table-column for="Name"></util-tree-table-column><util-tree-table-column for="Code"></util-tree-table-column><util-tree-table-column for="Enabled"></util-tree-table-column><util-tree-table-column for="SortId"></util-tree-table-column><util-tree-table-column title="操作"><util-a styles="Icon" tooltip="添加下級角色" link="create" query-params="{id:row.data.id}"><util-icon material-icon="Add"></util-icon></util-a><util-button id="btnMoveUp" styles="Icon" tooltip="上移" ng-if="!isFirst(row)" on-click="moveUp(row,btnMoveUp,$event)"><util-icon material-icon="Arrow_Upward"></util-icon></util-button><util-button id="btnMoveDown" styles="Icon" tooltip="下移" ng-if="!isLast(row)" on-click="moveDown(row, btnMoveDown,$event)"><util-icon material-icon="Arrow_Downward"></util-icon></util-button><util-button styles="Icon" menu-id="menu" on-click="selectRow(row,$event)"><util-icon material-icon="More_Vert"></util-icon><util-menu id="menu"><util-menu-item label="編輯" material-icon="Edit" bind-link="['update',row.data.id]"></util-menu-item><util-menu-item label="禁用" material-icon="Lock" on-click="disable(row)"></util-menu-item><util-menu-item label="啟用" material-icon="Lock_Open" on-click="enable(row)"></util-menu-item><util-menu-item label="刪除" material-icon="Delete" on-click="delete(row)"></util-menu-item><util-menu-item label="詳細" material-icon="Visibility" bind-link="['detail',row.data.id]"></util-menu-item></util-menu></util-button></util-tree-table-column>
</util-tree-table>

  生成的Html如下。?

<p-tree-table #treeTable_role="" baseUrl="role" key="treeTable_role" selectionMode="checkbox" [(queryParam)]="queryParam"><p-column field="name" header="角色名稱"></p-column><p-column field="code" header="角色編碼"></p-column><p-column field="enabled" header="啟用"><ng-template let-first="first" let-i="index" let-last="last" let-row="rowData"><mat-icon *ngIf="row.data.enabled">check</mat-icon><mat-icon *ngIf="!row.data.enabled">clear</mat-icon></ng-template></p-column><p-column field="sortId" header="排序號"></p-column><p-column header="操作"><ng-template let-first="first" let-i="index" let-last="last" let-row="rowData"><a mat-icon-button="" matTooltip="添加下級角色" routerLink="create" [queryParams]="{id:row.data.id}"><mat-icon>add</mat-icon></a><mat-button-wrapper #btnMoveUp="" (onClick)="moveUp(row,btnMoveUp,$event)" *ngIf="!isFirst(row)" style="mat-icon-button" tooltip="上移"><ng-template><mat-icon>arrow_upward</mat-icon></ng-template></mat-button-wrapper><mat-button-wrapper #btnMoveDown="" (onClick)="moveDown(row, btnMoveDown,$event)" *ngIf="!isLast(row)" style="mat-icon-button" tooltip="下移"><ng-template><mat-icon>arrow_downward</mat-icon></ng-template></mat-button-wrapper><button (click)="selectRow(row,$event)" mat-icon-button="" type="button" [matMenuTriggerFor]="menu"><mat-icon>more_vert</mat-icon><mat-menu #menu="matMenu"><ng-template matMenuContent=""><button mat-menu-item="" [routerLink]="['update',row.data.id]"><mat-icon>edit</mat-icon><span>編輯</span></button><button (click)="disable(row)" mat-menu-item=""><mat-icon>lock</mat-icon><span>禁用</span></button><button (click)="enable(row)" mat-menu-item=""><mat-icon>lock_open</mat-icon><span>啟用</span></button><button (click)="delete(row)" mat-menu-item=""><mat-icon>delete</mat-icon><span>刪除</span></button><button mat-menu-item="" [routerLink]="['detail',row.data.id]"><mat-icon>visibility</mat-icon><span>詳細</span></button></ng-template></mat-menu></button></ng-template></p-column>
</p-tree-table>

  看上去Html比TagHelper沒有復雜多少,那是因為已經將功能內置到樹型表格組件內部,不得不承認,有時候修改源碼比在外圍擴展要省很多力氣。?

  執行效果如下。

?

  在完成了異步加載,多選,上移,下移,搜索,刪除行,刷新,分頁等一系列功能后,一行Ts都沒有,是否感覺到很清爽呢。

其它組件?

  Util還封裝了顏色拾取器,菜單,側邊欄等組件,限于篇幅,就不一一介紹。?

小結

  本文簡要介紹了Angular標準組件的封裝手法,它能夠大幅提升業務開發的生產力,同時也提醒你,必須系統學習原生技術,否則碰上稍微復雜點的問題就無法解決。

  本文更多的是介紹封裝思路,而封裝思想與具體UI技術無關,一旦你了解了封裝背后的動機和技巧,不論Angular還是Vue,或者Android組件,甚至小程序都可以通過封裝來提升開發效率。

  未完待續,C#服務端CRUD的封裝將在下篇介紹。

  寫文需要動力,請大家多多支持,點下推薦,Github點下星星

  Util應用框架交流一群: 24791014(已滿)

  Util應用框架交流二群: 184097033

  Util應用框架地址:https://github.com/dotnetcore/util

轉載于:https://www.cnblogs.com/xiadao521/p/Util-Introduction-5.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/249304.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/249304.shtml
英文地址,請注明出處:http://en.pswp.cn/news/249304.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SpringBoot中處理的轉發與重定向

https://blog.csdn.net/yubin1285570923/article/details/83796003

scrapy爬蟲系列之三--爬取圖片保存到本地

功能點&#xff1a;如何爬取圖片&#xff0c;并保存到本地 爬取網站&#xff1a;斗魚主播 完整代碼&#xff1a;https://files.cnblogs.com/files/bookwed/Douyu.zip 主要代碼&#xff1a; douyu.py import scrapy import json from Douyu.items import DouyuItemclass DouyuSp…

glup server 報錯 Task function must be specified

解決方案 今天像往常一樣&#xff0c;編寫文章&#xff0c;并使用gulp bulid壓縮代碼&#xff0c;但是一運行&#xff1a;gulp build 就出現了這個錯誤&#xff1a;AssertionError: Task function must be specified。 gulp項目需要全局安裝gulp和項目內安裝gulp&#xff0c;…

mybatis Example 使用方法

一、mapper接口中的方法解析 mapper接口中的函數及方法 方法 功能說明 int countByExample(UserExample example) thorws SQLException 按條件計數 int deleteByPrimaryKey(Integer id) thorws SQLException 按主鍵刪除 int deleteByExample(UserExample example) thorws SQLE…

gulp + browsersync實現頁面自動刷新

寫習慣了vue&#xff0c;特別喜歡vue的自動刷新功能&#xff0c;于是琢磨在node中如何自動刷新&#xff0c;使用過nodemon&#xff0c; 但是感覺效果差點&#xff0c;看到網上有gulp livereload的方案和gulp browsersync的方案&#xff0c;但都是褒貶不一&#xff0c;先簡單記…

[JZOJ5836] Sequence

Problem 題目鏈接 Solution 吼題啊吼題&#xff01; 首先如何求本質不同的子序列個數就是 \(f[val[i]]1\sum\limits_{j1}^k f[j]\) 其中 \(f[i]\) 表示的是以 \(i\) 結尾的子序列個數 先把原數列的不同子序列個數求出來&#xff0c;然后觀察一下這個轉移&#xff0c;貪心的發現…

numpy和pandas的基礎索引切片

Numpy的索引切片 索引 In [72]: arr np.array([[[1,1,1],[2,2,2]],[[3,3,3],[4,4,4]]]) In [73]: arr Out[73]: array([[[1, 1, 1],[2, 2, 2]],[[3, 3, 3],[4, 4, 4]]])In [74]: arr.nd…

mybatis的Example[Criteria]的使用

https://blog.csdn.net/u014756578/article/details/86490052

Thunar 右鍵菜單等自定義

Thunar 右鍵菜單等自定義 可以使用圖形界面或者直接編輯配置文件&#xff0c;二者是等價的。 圖形界面&#xff1a; 以給“zip&#xff0c;rar&#xff0c;7z”等文件添加“在此位置使用unar解壓縮”的右鍵菜單為例&#xff1a;&#xff08;unar可以很好地處理編碼問題&#xf…

JavaScript設計模式(二)之單例模式

一、單例模式的定義 單例就是保證一個類只有一個實例&#xff0c;實現的方法一般是先判斷實例存在與否&#xff0c;如果存在直接返回&#xff0c;如果不存在就創建后再返回&#xff0c;這就確保了一個類只有一個實例對象。在JavaScript里&#xff0c;單例作為一個命名空間的提…

python全棧開發_day10_函數的實參和形參

一&#xff1a;函數的實參和形參 實參是在調用函數時()出現的外界的實際的值 形參不能再函數外部直接使用 1&#xff09;實參的兩種形式 實參是調用函數時()中傳入的參數 1.位置實參 def a(a):print(a)a(1)#得到返回值:1 2.關鍵字實參 def a(a,b):print(a,b)a(b3,a5)#得到返回值…

JAVA的(PO,VO,TO,BO,DAO,POJO)解釋

JAVA的(PO,VO,TO,BO,DAO,POJO)解釋 O/R Mapping 是 Object Relational Mapping&#xff08;對象關系映射&#xff09;的縮寫。通俗點講&#xff0c;就是將對象與關系數據庫綁定&#xff0c;用對象來表示關系數據。在O/R Mapping的世界里&#xff0c;有兩個基本的也是重要的東東…

使用wsimport命令生成webService客戶端代碼實例

https://blog.csdn.net/qq_39459412/article/details/79079865

學習網站大匯集

一.綜合類學習網站&#xff08;中文&#xff09; 1.網易公開課&#xff1a;https://open.163.com/。上面有TED、可汗學院、國內外高校公開課的免費資源。站內內容完全免費&#xff0c;良心推薦。 2.網易云課堂&#xff1a;http://study.163.com/。網易旗下付費學習平臺&#…

ios怎樣在一個UIImageButton的里面加一些自己定義的箭頭

能夠採用例如以下方法&#xff0c;寫一個函數&#xff1a; -(UIImage*) getOneImageButtonWithArrow{//tmpView做附控件UIView *tmpView [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 38.0f, 32.0f)];tmpView.backgroundColor [UIColor clearColor];//bgImg作為背景…

vue從入門到精通之基礎篇(一)語法概要

(1).vue起步 1:引包2:啟動 new Vue({el:目的地,template:模板內容});options 目的地 el內容 template數據 data 保存數據屬性 數據驅動視圖 (2).插值表達式 {{ 表達式 }} 對象 (不要連續3個{{ {name:‘jack’} }})字符串 {{ ‘xxx’ }}判斷后的布爾值 {{ true }}三元表達式…

dede 文章列表頁如何倒序排列

{dede:arclist row6 typeid18 orderwayasc} <li>;<a href"[field:arcurl/]">[field:title/]</a></li> {/dede:arclist} 正常排列&#xff1a;orderwayasc倒序排列&#xff1a;orderwaydesc轉載于:https://www.cnblogs.com/php-qiuwei/p/1062…

Chapter 5 Blood Type——24

"Shes just a little faint," he reassured the startled nurse. "Theyre blood typing in Biology." "她只是有點頭暈&#xff0c;" 他讓護士放心的說道。“他們再生物課上測血型。” The nurse nodded sagely. "Theres always one."…

vue從入門到精通之基礎篇(二)組件

(1).局部組件的使用 ? 渲染組件-父使用子組件 1: 創建子組件(對象) var Header { template:模板 , data是一個函數,methods:功能,components:子組件們 } 2: 在父組件中聲明,根屬性components:{ 組件名:組件對象 }3: 在父組件要用的地方使用 <組件名></組件名> …

@Scheduled

Scheduled注解的使用這里不詳細說明&#xff0c;直接對8個參數進行講解。 參數詳解 cron 該參數接收一個cron表達式&#xff0c;cron表達式是一個字符串&#xff0c;字符串以5或6個空格隔開&#xff0c;分開共6或7個域&#xff0c;每一個域代表一個含義。 cron表達式語法 […