表單在中後臺開發的時,是最多也是最另人頭疼的,多級聯動,繁雜的驗證,動態解析等可算是苦不堪言。因此出現了無數的表單解決方案,像Uform, formily, NoForm等等一大堆用來解決中後臺開發表單,可想而知,解決複雜的表單開發是多麼另人頭大;有XML的,有json-schema格式的,不管哪種都是想可以輕鬆的解決另人頭腦的表單開發,提升生產力。
固然,其中有部分不是,這只是爲了作一個 DEMO, 解釋一下這是個啥
服裝:前端
數碼:vue
根據一個複雜的需求,練手一個複雜程度還行的表單也是不錯的選擇
<fd-form :data.sync="codeCompxPlus" @event="codeCompxPlusEvent" @submit="codeCompxPlusSubmit" :columns="[ {type: 'select-remote', prop: 'selectRemote', label: '遠程搜索', placeholder: '遠程搜索選擇', options({resolve, query}) { resolve(['選項1', '選項2', '選項3'].map(e => query + e).toString()) }}, {type: 'input-remote', prop: 'inputRemote', label: '搜索&建立', placeholder: '遠程搜索, 搜索不到建立: a/b/c', options({resolve, query}) { if (query && 'abc'.includes(query)) { resolve([{value: query + '選項1'}, {value: query + '選項2'}]) } else { resolve([]) } }, style: {width: '280px'}}, {type: 'input', prop: 'name', label: '商品名稱', placeholder: '請輸入商品名稱', rule: 'must'}, {type: 'input', prop: 'title', label: '副標題', placeholder: '請輸入副標題'}, {type: 'cascader', prop: 'kind', label: '分類', placeholder: '請選擇分類/模擬遠程', options({resolve}) { //能夠在此訪問api .then 函數中使用resolve resolve([ {label: '服裝', value: 1, children: [{label: '外套', value: 11}, {label: '襯衫', value: 12}]}, {label: '家用', value: 2} ]) }}, //此處切換選擇對應options已經變了,但若是已經選擇過那麼值不會清空,能夠手動監聽事件去清除 this.codeCompxPlus.city = '' 這樣 [ {type: 'select', prop: 'province', label: '省', options({resolve}) { resolve({1: '江蘇', 2: '河南', 3: '山東'}) }}, {type: 'select', prop: 'city', label: '市', options({resolve, data}) { //老規矩,能夠從後臺取,能夠是靜態文件取,格式如何,自行設計 resolve({ 1: {11: '蘇州', 12: '南京'}, 2: {21: '鄭州'}, 3: {31: '濟南'} }[data.province]) }}, {type: 'select', prop: 'area', label: '區', options({resolve, data}) { resolve({ 11: '蘇州AB,蘇州DD', 12: '南京CC,南京UU', 21: '鄭州VV,鄭州KK', 31: '濟南MMM,濟南LLL', }[data.city]) }}, //若是多個formitem都有rule,要添加個prop(不重複就行) {type: 'formitem', label: '地址', prop: 'address', rule({resolve, data}) { resolve((!data.province || !data.city || !data.area) && '必需要選的') }} ], {type: 'radios-button', prop: 'yh', label: '優惠方式', value: 1, options: {1: '無優惠', 2: '促銷', 3: '會員特價', 4: '滿減'}}, //促銷 {type: 'render', load: ({data}) => data.yh == 2, prop: 'cx', render({createElement, value}) { return createElement('FdForm', { props: { columns: [ {type: 'date', prop: 'cxDateStart', label: '開始時間'}, {type: 'date', prop: 'cxDateEnd', label: '結束時間'}, {type: 'input', prop: 'cxPrice', label: '價格', style: {width: '220px'}} ], data: value, config: {labelWidth: '75px'} } }) }, rule({resolve, value}) { let message if (!value.cxDateStart || !value.cxDateEnd || !value.cxPrice) { message = '別看了,都是必填項,可驗證非空' } if (value.cxDateStart > value.cxDateEnd) { message = '結束日期不能小於開始日期' } resolve(message) }}, //會員特價 {type: 'render', load: ({data}) => data.yh == 3, prop: 'hytj', render({createElement, value}) { return createElement('FdForm', { props: { columns: [ {type: 'input', prop: 'hytjGlod', label: '黃金會員', style: {width: '220px'}}, {type: 'input', prop: 'hytjPlatinum', label: '白金會員', style: {width: '220px'}} ], data: value, config: {labelWidth: '75px'} } }) }, rule({resolve, value}) { resolve((!value.hytjGlod || !value.hytjPlatinum) && '必需要選的, 數值驗證,啥亂七八糟的驗證自行寫') }}, //滿減 {type: 'render', load: ({data}) => data.yh == 4, prop: 'mj', render({createElement, value}) { return createElement('FdTable', { props: { columns: [ {type: 'input', prop: 'mjEnough', label: '購買金額滿'}, {type: 'input', prop: 'mjReduce', label: '減'}, {label: '操做', render() { return [ {type: 'button-text', value: '刪除', prop: 'del'}, {type: 'button-text', value: '添加', prop: 'add'}, ] }} ], data: value }, on: { event(params) { if (params.prop == 'del') { value.splice(params.$index, 1) if (value.length <= 0) { value.push({}) } } else if (params.prop == 'add') { value.push({}) } } } }) }, rule({resolve, value}) { let message, len = value.length value.forEach(e => { if (!e.mjEnough || !e.mjReduce) { if (!e.mjEnough && !e.mjReduce) len -- else message = '要填寫的' } }) if (len < 1) message = '至少填寫一條' resolve(message) }}, {type: 'span', load: ({data}) => data.yh == 4, value: '提示:至少寫一行,若是兩個屬性都沒寫,那麼這行不作記錄', style: {fontWeight: '600'}}, //動態聯動 {type: 'select', prop: 'type', label: '類型', placeholder: '類型不一樣對應不一樣結構', options({resolve}) { //一樣,能夠動態從api獲取 resolve({1: '服裝', 2: '數碼'}) }, rule: 'must'}, //這裏須要根據 data.type 改變來強制刷新 render 函數 {type: 'render', label: '規格', prop: 'gg', load: ({data}) => data.type, forceUpdate: true, render({createElement, data, value}) { let _columns = [] //這裏模擬一下根據type select改變,改變爲不一樣的屬性 if (data.type == 1) { _columns = [ {type: 'span', value: '顏色:'}, {type: 'br'}, {type: 'tags-create', prop: 'ggColor'}, {type: 'span', value: '尺寸:'}, {type: 'br'}, {type: 'check-boxs', prop: 'ggSize', options: 'M,X,XL,L,2XL'}, ] } else { _columns = [ {type: 'span', value: '容量:'}, {type: 'br'}, {type: 'check-boxs', prop: 'ggSize', options: '1G,2G,3G'}, ] } return createElement('FdRegion', { props: { columns: _columns, data: value }, on: { event(params) { let _columns = [] for (let _value in Object.keys(data.gg)) { key = Object.keys(data.gg)[_value] if (data.gg[key] && data.gg[key].length) { _columns.push(data.gg[key].map(el => { return {value: el, prop: key} })) } } codeCompxPlus.propList = calcMultiplyData(_columns) } } }) }}, {type: 'render', prop: 'propList', load: ({data}) => data.type, forceUpdate: true, render({createElement, data, value}) { let _columns = [] //這裏也是根據type 的切換改變不一樣的columns if (data.type == 1) { _columns.push( {label: '顏色', prop: 'ggColor'}, {label: '尺寸', prop: 'ggSize'} ) } else { _columns.push( {label: '容量', prop: 'ggSize'} ) } _columns.push( {label: '價格', prop: 'price', type: 'input'}, {label: '庫存', prop: 'store', type: 'input'}, {label: '預警值', prop: 'val', type: 'input'}, {label: 'SKU編輯', prop: 'sku', type: 'input'}, {label: '操做', prop: 'del', type: 'button-text', value: '刪除'} ) return createElement('FdTable', { props: { columns: _columns, data: value } }) //一樣能夠加rule進行對錶格進行驗證 }}, {type: 'render', prop: 'prop', label: '商品參數', load: ({data}) => data.type == 1, render({createElement, value}) { return createElement('FdForm', { props: { columns: [ {type: 'input', label: '商品編號', prop: 'propNo', rule: 'must', style: {width: '220px'}}, {type: 'select', label: '季節', prop: 'propSeason', options: '春季,夏季,秋季,冬季'}, {type: 'select-multiple', label: '人羣種類', prop: 'propCrowd', options: '兒童,青少年,中年,老年'}, {type: 'date', label: '上市時間', prop: 'propUpTime'} ], config: {labelWidth: '85px'}, data: value } }) }}, [ {type: 'button-info', prop: 'fullData', value: '填充數據'}, {type: 'button-primary', prop: '$submit', value: '提交'}, {type: 'button', prop: '$reset', value: '重置'}, {type: 'formitem'} ] ]" /> methods: { calcMultiplyData(arr) { let res = [], cur = {} function search(deep = 0) { if (deep >= arr.length) { res.push(cur) cur = Object.assign({}, cur) return } for (let obj of arr[deep]) { cur[obj.prop] = obj.value search(deep + 1) } } search() return res }, codeCompxPlusSubmit(data) { alert('提交成功,打開控制檯查看提交的數據') console.log(data) }, codeCompxPlusEvent(params) { if (params.prop == 'province') { this.codeCompxPlus.city = '' this.codeCompxPlus.area = '' } else if (params.prop == 'city') { this.codeCompxPlus.area = '' } else if (params.prop == 'type') { this.codeCompxPlus.propList = [] } else if (params.prop == 'fullData') { this.codeCompxPlus = { selectRemote: '選項1', inputRemote: '選項2', name: '汪仔', title: '仍是那個味道', kind: [1,12], province: '1', city: '11', area: '蘇州AB', yh: 4, mj: [{mjEnough: 200, mjReduce: 5}, {mjEnough: 400, mjReduce: 18}], type: 1, gg: {ggColor: 'blue', ggSize: ['XL', 'L']}, propList: [ {ggColor: 'blue', ggSize: 'XL', price: 280, store: 2299, val: 105, sku: 'wtf'}, {ggColor: 'blue', ggSize: 'L', price: 288, store: 2009, val: 100, sku: 'crete'}, ], prop: {propNo: 'abc111', propSeason: '秋季', propCrowd: ['兒童','青少年'], propUpTime: new Date()} } } } }
上面是實現這個表單的所有代碼,使用的是基於ElementUI 的vue-elementui-freedomen 製做的vue語法表單,表單的展現在: http://115.159.65.195:8080/vefdoc 示例的最後一個
作這些的目的爲了提供可供參考的解決思路,方法。來共同進步,使前端愈來愈好。