你寫過最複雜的表單頁面是怎樣的,你是怎麼解決的?


都說企業應用表單複雜,其實工業應用的表單更複雜。

我做過最複雜的一個表單應用就是一個工業應用,光底層的數據 model,第一個版本就寫了 4000 多行,後面又加了好多特性在裡面。

再加上界面,複雜到簡直沒數了,完全 hold 不住的感覺。

之所以這麼複雜——

第一,欄位不是固定的,是根據一個 schema 渲染出來的。那個 schema 本身也是有編輯界面的,不是一個表單,是好多表單組合成一個應用。而且 schema 是樹型的,導致通過 schema 渲染出來的是欄位套欄位的複雜表單。

第二,欄位類型多,每種類型都有個性化的渲染方式。不是開源庫里那種 booleanfiled,textfield 能解決的。

比如一個原本很簡單的數字框,要支持宏語法,類似$TIMEATAMP - 1000這種,還要判斷模板語法對不對,輸入$後還要下拉提醒宏變數列表,自動完成。工作量直線上升。

第三,本來就很複雜的東西,還要支持複製粘貼,撤銷重做,預覽等等一大堆編輯功能。最變態的是還要支持三種模式:編輯模式/調試模式/運行模式。界面看起來差不多,但是實際開發起來,要改的地方特別多,復用很難,你覺得你能靠設計模式解決,真做起來掉到坑裡就出不來。

做這種頁面,開始會覺得挺過癮的,但是做到後面其實反彈情緒很嚴重,尤其是需求加碼的時候。後來有獵頭想介紹我去華為開發 IDE,我琢磨了一下還是慫了。想想都知道,開發 IDE 一定是個更大的坑,我還是算了。


我是用無狀態組件解決的。使用vue。

現在的ng,vue,react之類的 只要組件帶有私有狀態了,在複雜的場景下都難以控制。往往需要藉助redux,vuex 把狀態都共享出去,這樣的弊端就是——組件不純了,可復用性降低,使用成本提高了。vue,react 之流並不適合無狀態組件,一般來說組件自身的狀態發生變化只渲染自個,如果把狀態抽離,那可能就會渲染所有的組件了。但是我管不了那麼多了,能優美的完成業務比損失少量性能要重要的多。 我把所有的數據都放到最外層,通過input事件和value屬性控制數據流,幸運的是性能並沒有下降多少,依然很流暢。

說下我的表單, 大概有5類不同展示的表單匯聚成一個頁面,表單欄位有上百個,同一個頁面中,可以分塊保存,也不乏有組件關聯組件,也有幾個是可編輯表格,大概有上傳圖片,圖片控制,複製,刪除功能。還有一些奇奇怪怪的組件,提交的時候從表單上數據能達到上千個(不算表單驗證的部分)。

說下我的無狀態組件。 這裡無狀態組件並不是組件只寫展示層。實際上,展示,控制,請求介面之類邏輯都有。這樣維護和管理起來比較方便,通過外層調用來組裝。也就是說組件自身的邏輯放到組件自己身上,運行的時通過外層調用將數據在外層,外層可以訪問子組件,子組件訪問不了外層組件。可能會讓你感到不爽的地方是需要使用不可變數據來傳遞給外層,改變數據從新渲染。

為此我專門寫過文章 優美的使用Vue工作 文筆寫的不是很好,但重在描述思路。。。 感興趣的可以將就著看。


我其實沒寫過很複雜的表單頁面。不過我還是冒險講兩句。

互聯網公司的同學要寫的表單頁其實複雜度都一般,因為複雜度太高,用戶用不來,產品就可以下崗了。

做企業應用的起點複雜度就高,控制項要求千奇百怪,表單的邏輯更是複雜到爆。如果你用 ng/react/vue 還是有 hold 不住的感覺,可以考慮學習下商業產品,比如 InfoPath (儘管已經被微軟 discontinued 了),或者學習下 XForms 的設計。


表單頁面的複雜度如何形成?

  1. 自身業務的複雜度
  2. 控制項的複雜度
  3. 用戶體驗足夠完美
  4. 數據的結構設計不合理

自身業務的複雜度

這個很多回答已經有說了,欄位太多,類型也有很多, 之前在做醫療應用也有體會,根據業務本身將大表單分模塊, 用一些數據驅動視圖的框架描述表單需要的數據結構, 如果能從產品上降低複雜度就太好了。

控制項的複雜度

奇奇怪怪的控制項我沒遇到太多(太年輕),基本上都能從網上找到對應的控制項,可是過多地依賴外部控制項這本身就是自己增加複雜度, 例如你用vue,從網上拿了個搜索下拉的jquery插件,你包裝一下vue組件使用, 可是插件提供的API不能完全滿足業務,你改掉插件裡面的邏輯, 後來你發現在vue中自己寫一個符合業務的搜索下拉組件更簡單。

用戶體驗足夠完美

表單校驗提示、上傳loading、重置修改等都是讓用戶體驗足夠地好,這個to c的產品比較關注。

數據的結構設計不合理

因為服務端設計不完全匹配產品展示的數據, 所以在提交數據給服務端之前要做一層數據封裝,在表單初始化數據的時候也要再做一層數據轉換。這個要麼叫服務端的同學更改一下,要麼跟產品溝通一下~

我做過的表單頁面算不上很複雜, 以上的是屬於個人的一些總結,希望有幫助。


表單複雜主要是需求沒有理解好造成的,還有信息結構不合理。


我寫過最複雜的表單還是當年的企業應用,基於Delphi和VB。其實任何ERP框架的表單系統都很複雜,你可以看看開源的Odoo的設計。


原型都畫不出來,產品經理都說不清楚,搞不明白。最後一邊開發,一邊和產品討論。那種離了產品1分鐘都干不下去活的感覺。企業中控系統。這算複雜嗎?

解決也很簡單呀,case by case。因為業務性太強,造成無法頂層封裝復用,即使封裝意義也不是很大,反倒容易造成過度封裝。

當然,還有一種究極的解決方案。

圖片來自朕說


react寫的,使用rc-form或者用babel自定義一個雙向綁定的插件可解決絕大部分問題


多,不算動態的接近的50多條;

雜,tag列表,單,多選聯動;

前後不分離,這個有我自己技術不過關的原因,需要後端參與代碼,所以也不方便上什麼vue之類的框架了,框架語法外加後端模板語法整個頁面酸爽…

部分表單項目需要可動態生成(好在可動態的種類被限定三種,數字和單多選);

表單需要可還原方便多次修改…

目前是一條條手動寫了50多個表單項然後想辦法把聯動動態表單獨立出來…其實寫完動態表單之後就想突突了自己…一開始就全部都用動態表單的生成邏輯不就完事了…不過動態生成是後來提的需求之前沒想到有這種奇怪的東西


web端的imovie不知道算不算


正在寫……

簡直就是瀏覽器里的excel.

大概有六個tabs, 每個tab底下有不定量的sub-tabs.

輸入方式有數字/date-picker/freetext/單選/多選/文件拖動上傳/下載等等,有些部分還有全部勾選/全部清空/本地搜索/伺服器搜索等功能。甚至還有嵌套了五六層的樹狀結構,根據情況可以單選/多選/一次選中某個range/刪除,並且可以對某些單項設置時間/價格等等。輸入內容之間互相有關聯和限制。圖表的顯示方式用的D3.js.

目前的選擇是Redux-form+手寫所有的components, 一堆formatter/helper來做數據的變換和確認(目前還在用lodash,之後可能會轉成ramda)。實在需要操作dom的地方用refs(但很少).

因為數據比較敏感,validation是後端做,有錯誤需要高亮各種tab/輸入項並顯示提示。

唯一的好處是不用兼容IE…


剛好最近有一點表單頁面的實踐,可以拿出來講一下。由於業務調整,我們所有的表單頁面改為通過配置文件生成,並且精確到組件基本的配置。

什麼意思?即只要需求中的組件,代碼中已經存在了,那麼改改配置文件就可以生成一個新的頁面。

會有兩份配置文件,一份用來描述編輯的數據元信息 meta.js,一份用來描述 ui 組成(ui.js)。表單的校驗、觸發時機,關聯表單的顯隱、校驗,表單項的動態增刪,以及一些特殊的表單,比如圖片上傳、地圖和組合表單。

主要用到的框架有 rc-form、async-validator、mobx,mobx 會在前端本地維護一份數據的中間層,所有表單項觸發的數據修改都會同步到中間層。

export default Form.create({
onFieldsChange(props, changedFields) {
forEach(changedFields, field =&> {
store.set(fields)
})
},
})(Content)

rc-form 的 getFiledProps 極其好用,只需要提供 path,value, onChange 就可以很方便將組件的數據和 store 中的數據保持同步,因此所有的組件都會盡量寫成無狀態組件,暴露出 value和 onChange,與 store 中的數據保持同步即可。

// 這只是一個簡單的 meta 例子,簡單體會下 meta 的作用,meta 結構與數據結構一致。
{
user: {
__meta: {
type:"Object",
name: "用戶數據",
},
basic_info: {
__meta: {
type: "Object",
name: "基本信息"
}
name: {
__meta: {
type: "String",
name: "姓名",
validate: [
{type: "required", message: "不能為空"}
]
}
},
age: {
__meta: {
type: "Integer",
name: "年齡",
validate: [
{type: "required", message: "不能為空"},
{type: "Number", message: "必須位數字"}
]
}
}
},
relate_info: [
__meta: {
type: "Array[Object]"
name: "人際關係",
validate: [
{type: "minLength", value: 1, message: "只要有一個"},
]
},
__Object: {
__meta: {
type: "Object",
name: "人際關係"
},

relate_type: {
__meta: {
type: "Intege",
name: "關係類型"
},
},
relate_deep: {
__meta: {
type: "Intege",
name: "關係深度"
},
}
}
],
}
}

meta.js 中定義了數據的結構、類型、options、校驗信息等,因此我們會把 meta 中的數據統一轉換成 path: {rules, options, value} 的結構,將其塞給每個具體的組件。類似於 lodash 的 _.get 和 _.set 方法,通過 path 可以我們方便去獲取和操作 store 中的數據。自定義的一些校驗也會統一轉換成 async-validator 可以接受的形式,使用 async-validator 也可以方便的自定義觸發的時機,一般在 DidMount 中調用下 form.validateFields() 就可以進入頁面時觸發校驗。

// 簡單版的 ui 文件,用於描述 ui tree
{
entry: {
type: "menu",
title: "入口",
subpage: [
"user_center", "home", "detail",
]
},
user_center: {
type: "content",
title: "用戶中心",
items: [
{
type: "InputComponent",
label: "用戶名",
path: "user.basic_info.name"
},
{
type: "SelectComponent",
label: "age",
path: "user.basic_info.age"
},
]
},
home: {
type: "content",
title: "首頁"
},
detail: {
type: "content",
title: "詳情"
}
}

ui.js 中定義了 ui tree 的結構,因為真實的 ui 就是一個樹形的結構,最小單位為定義的 {path :component},根據需要可以增加 menu -&> content -&> form -&> component 這樣的層級,遞歸向下依次渲染。這樣我們可以控制頁面的關係和渲染的層級,並且也可以很方便的做出上一步、下一步這樣的操作。因為最小單位為 {path: component},即定義了這個表單項的 path,這樣很方便的關聯上數據、校驗等信息,component 定義了表單項用到的組件,你可以為該表單選擇合適的組件。

// 實際操作的數據大概是這樣的結構,更複雜的數據也是可以支持的
{
user: {
basic_info: {
name: "xxx",
age: 23,
sex: "female"
},
relate_info: [
{name: "張三",relate_type: 1, relate_deep:1},
{name: "李四",relate_type: 2, relate_deep:2},
]
}
}

其他的應該只是一些細節需要處理,核心在於 meta 描述數據元信息,ui 來描述 ui 樹,通過 path 來將 meta、ui和真實的數據操作關聯起來,額外的 store 保持數據的前端持久化和同步


幾年前,我想做個大而全的域名註冊平台,各個國家和註冊局的政策各不相同且很複雜,大多數選項都是級聯的,

以東南亞某小國為例:

首先是必填的姓名/性別/電話/地址,

如果是本地個人,需要填寫身份證號,

老外個人,先選擇國家,再填寫所在國家身份證號,

本地企業,勾選填寫一堆企業類型/稅號之類的選項,本地的外企,勾選另一堆東西,外國企業,勾選填寫另一堆東西,

也可以掛靠在所在國的皮包公司名下,以上就通通不用填了,只需勾個"i agree"即可,但是要另收費。

註冊年限一般是以一年為一個計費周期,也就是步長為1,但是可以一次註冊10年的,1年x10=10年,但有的國家比較蛋疼,是一註冊就必須2年或5年的,步長為2或5。

有的國家還要上傳身份證/執照的影印件。

這些都是在頁面上聯動顯示的,否則平鋪起來非常大,而且有些選項有單獨價格的,也要立即在頁面上顯示,並給出一個小計。

而這樣的域名類型有幾十上百個,各不相同,只有你想不到,沒有各國公務員們想不到,一個個的寫怕是要累死,逼不得以只好自己寫了一個表單生成的模塊,寫json格式的配置文件就可以了。

其實核心並不難,本質上都是樹形結構而已,代碼只是根據配置和用戶的選擇做些if/else的判斷和格式的校驗,前端動態顯示(就是大浮層套小浮層)和計費有點點難度。前端的格式校驗規則也是根據json輸出到html的js里的。

我看了下市面上的表單平台網站(http://jinshuju.net之類的),他們生成的表單還是一維的,沒法根據一個控制項的選項 聯動顯示 另外一堆控制項(省市區那個是聯動的,但是是寫死的模塊,用戶沒法自己編輯生成這樣的級聯選項)。當然,這是兩種不同的需求導致的,只是簡單對比下。

缺點也是顯而易見的,因為本質上是樹形結構,就沒法按指定的頁面布局顯示,比如像我們常見的銀行/公安之類的登記表那樣顯示在一個剛好擺滿的方方正正的格子里,還好css也可以寫在配置文件里,css寫的好也不會太丑,看上去也並不比http://jinshuju.net之類的丑,這種業務類型的頁面樣式都是次要的。

後來我的項目還是黃了。


用elementUI寫一個企業人事管理系統,寫的想放棄活著的權利。表單組件全上已經是基本了,簡單來說就是A組件的值可能影響下面BCD組件的選擇填寫區間範圍,然後BCD的值又可能反過來影響A的狀態,以及BCD之間的相互影響。當幾十個這樣的組件寫在一個頁面里的時候,我覺得我看破了紅塵。紅塵間為什麼這麼多事逼。


  1. 怒懟產品經理,罵他,讓他改!
  2. 模塊化,分析每個field的邏輯,抽象出公用部分。
  3. 伺服器端生成也是個路子,還有不少框架也能用

反正就是不能傻做。。要不然整出好幾千好幾萬行代碼,以後也是個坑


說起來都是淚,上班後寫了一堆表單項目,靜態動態,表單項關聯的都有。簡而言之,用 antd 配合redux 就是一梭子。不說了,我去複製粘貼了。


來描述一下我們的需求

難點就在於用戶的高度可配置

1. 欄位有十多種基礎類型,從最簡單的input,到最複雜的富文本,中間是各種交互豐富的組件。

2. 用戶可以基於這些類型自定義欄位,配置選項、是否必填等信息

3. 用戶可以自由配置級聯:當某個欄位的值為xx時,增加/刪除其他某些欄位、限制某些欄位的選項、是否必填等信息

4. 還有一些用戶看不見的隱藏欄位(如坐標)

5. 支持新建、編輯兩種操作,並支持彈窗、單頁兩種視圖。初始值還與用戶觸發之前的操作有關。

之前用JQuery實現的代碼加起來有1W行左右,目前正在用React重寫,除了級聯都完成了。


P2P的呈簽計算表單,參數不定,依靠後端提供參數。不同的參數之間可能存在關聯關係,修改一個參數之後,最多可能影響五六個參數的值。例如修改管理費率,會影響管理費,立即扣款金額,扣款總額和放款總額等幾個參數,還會影響還款計劃表。而所有參與計算的參數最多可以超過100個,並且還都是動態配置的。

此外為了不影響用戶體驗,所有的參數演算法在後端,前端和APP端都實現了。


表單複雜是ui和產品的鍋,風格都統一 了,怎麼封裝都好搞。一個input 錯誤提示搞三種。。,怎麼玩。


正面剛,jquery剛的,寫幾千行代碼,現在已經不想維護了,每次產品要繼續優化需求都要打開這個我自己都看不下去的代碼,為後面要維護我寫的代碼的人默哀。

ps:不是我不用vue,react啊什麼的,公司決定的框架,太年輕,早知道就用vue寫了,畢竟公司也不會檢查我用什麼的寫的……


推薦閱讀:

Webstorm 的 Tab 鍵怎樣調整縮進值?
為了保持Mac,Linux,Windows等平台中文字體的一致性和美觀性,你會使用哪些font-family?
自學前端,我的現在水平能不能找到工作?
開發者能從第三方視頻網站能獲取到的最大視頻預覽圖是多大?
為什麼WEB前端OR網頁設計製作工程師那麼難招?

TAG:前端開發 | JavaScript | 前端開發框架和庫 | 前端架構 |