前端開發中涉及表單的頁面非常多,看似功能簡單,開發快速,實則占去了很大一部分時間。當某個表單包含元素過多時還會導致html代碼過多,vue文件過大。從而不容易查找、修改和維護。為了提高開發效率及降低維護成本,下麵介紹表單配置化組件的封裝原理與封裝方法。 ...
一、背景
前端開發中涉及表單的頁面非常多,看似功能簡單,開發快速,實則占去了很大一部分時間。當某個表單包含元素過多時還會導致html代碼過多,vue文件過大。從而不容易查找、修改和維護。為了提高開發效率及降低維護成本,下麵介紹表單配置化組件的封裝原理與封裝方法。
二、技術方案
如上圖所示,封裝表單配置化組件的關鍵點有三個一是如何解決表單元素排布的行列問題,二是表單數據的綁定問題,三是表單元素的參數配置校驗等問題。下麵分別介紹這三個問題的解決方法。
•配置化表單組件的入參及說明
參數 | 說明 | 類型 | 可選值 | 預設值 |
---|---|---|---|---|
labelWidth | 表單元素label所占寬度 | String | —— | 150px |
columnList | 表單元素所組成的配置,是一個數組 | Array | —— | [] |
formData | 表單元素值的集合 | Object | —— | {} |
columnSpan | 表單排布分欄 | Number | —— | 24 |
size | 表單元素尺寸 | String | medium / small / mini | medium |
•計算配置化表單的行數,本表單通過基礎的24分欄計算表單最終的行數和列數,通過下麵方法最終得到一個關於行列的二維數組
newColumnList() {
const newColumnList= []
const row = Math.floor(24 / this.columnSpan)
let newColumnItem = []
for(let i=0; i< this.columnList.length; i++) {
newColumnItem.push(this.columnList[i])
if(newColumnItem.length === row || i === this.columnList.length-1) {
newColumnList.push(newColumnItem)
newColumnItem = []
}
}
return newColumnList
}
•通過上面得到的二維數組進行迴圈渲染,首先迴圈渲染行,其次迴圈渲染列。本方案採用element中的表單,當然也可以用其他組件庫或者原生表單進行渲染,其原理通用。最終將會根據參數column.type決定載入哪一個具體的表單元素。
<el-form ref="form" :model="formData" :label-width="labelWidth" :size="size">
<el-row :gutter="20" v-for="(element,index) in newColumnList" :key="index+'formRow'">
<template v-for="(item, index) in element" >
<column
:key="index + 'formView'"
:columnSpan="columnSpan"
:column="item"
:formData="formData"
/>
</template>
</el-row>
</el-form>
•column組件最終根據type載入具體的表單元素。下麵展示column組件的入參及其說明,通過component載入不同的表單元素
參數 | 說明 | 類型 | 可選值 | 預設值 |
---|---|---|---|---|
column | 表單元素的具體配置 | Object | —— | {} |
formData | 表單元素值的集合 | Object | —— | {} |
columnSpan | 表單排布分欄 | Number | —— | 24 |
<el-col :span="columnSpan">
<component
:is="column.type + 'View'"
:column="column"
:formData="formData"
v-model="formData[column.name]"
:columnSpan="columnSpan"/>
</el-col>
•這裡主要以select表單元素為例進行說明,表單元素的雙向綁定、校驗以及值更新等問題
參數 | 說明 | 類型 | 可選值 | 預設值 |
---|---|---|---|---|
column | 表單元素的具體配置 | Object | —— | {} |
value | 表單元素值 | Number/String/Array | —— | —— |
•column參數
參數 | 說明 | 類型 | 可選值 | 預設值 |
---|---|---|---|---|
placeholder | 空值說明 | String | —— | —— |
required | 是否必填 | Boolean | —— | —— |
rules | 校驗規則 | Array | —— | —— |
title | 表單元素label | String | —— | —— |
name | 表單元素值名稱 | String | —— | —— |
multiple | 是否多選 | Boolean | —— | —— |
filterable | 是否過濾 | Boolean | —— | —— |
disabled | 是否禁用 | Boolean | —— | —— |
dictionary | 下拉選項枚舉 | Array | —— | —— |
changeFunction | 值改變時的回調函數 | Function | —— | —— |
<el-form-item :label="column.title + ':'" :prop="column.name" :rules="rules">
<el-select
v-model="val"
clearable
:multiple="column.multiple"
:filterable="column.filterable"
:placeholder="'請選擇' + column.title"
:disabled="column.disabled"
style="width: 100%"
@change="onChange"
@clear="onClear">
<el-option v-for="item in column.dictionary" :key="item.code" :label="item.name" :value="item.code">
</el-option>
</el-select>
</el-form-item>
rules: [
{
required: this.column.required,
message: this.column.placeholder placeholder ? this.column.placeholder : `請輸入${this.column.title}`,
trigger: 'change'
},
...this.column.rules
]
onChange(){
this.$emit('input',this.val)
if(this.column && this.column.changeFunction){
this.column.changeFunction(this.val)
}
},
onClear(){
this.onChange()
}
三、項目實踐
•配置化表單為bs-form,在頁面中引入bs-form表單組件
<bs-form ref="formDemo"
:columnList="columnList"
:formData="formData"
:columnSpan="columnSpan"
labelWidth="120px">
</bs-form>
<el-row style="text-align: center;">
<el-button type="primary"
@click="onSave">保存</el-button>
<el-button @click="onCancel">取消</el-button>
</el-row>
•formData參數
formData: {
name: '',
yearIncome: '', // 業務類型
goodsCategoryId: '', // 托寄物品類id
projectManagerErp: '', // 項目經理erp
projectName: '', // 項目名稱
projectStage: '', // 項目階段編碼
projectStandardName: '', // 標準名稱
projectYear: 2023, // 年份
startRegionId: '', // 始發區域id
startBattleId: '', // 始發戰區id
address: [], // 省市
category: null, //圖文類型
range: [] //發佈範圍
}
•分欄參數
columnSpan: 6
•表單配置參數
columnList(){
const self = this
return [
{
type: 'text',
name: 'name',
title: '項目名稱',
required: true,
maxlength: 20,
showwordlimit: true,
placeholder: '請輸入'
},
{
name: 'category',
type: 'radio',
dictionary: [
{
code: 1,
name: '類型一'
},
{
code: 2,
name: '類型二'
}
],
title: '圖文類型',
required: true
},
{
name: 'range',
type: 'checkbox',
title: '發佈範圍',
dictionary: [
{
code: 1,
name: '範圍一'
},
{
code: 2,
name: '範圍二'
}
],
required: true
},
{
type: 'text', // 欄位類型文本框
name: 'yearIncome', //與後臺對接欄位
title: '年均收入', // 前端展示欄位
required: true, // 必填項設置
maxlength: 50, // 字元串長度限制
showwordlimit: true, // 是否顯示字元串長度
placeholder: '請輸入', // 占位文本提示
rules: [
{ pattern: /(^[1-9]([0-9]+)?(.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9].[0-9]([0-9])?$)/, message: '請輸入數字最多兩位小數' }
],
},
{
type: 'select',
name: 'goodsCategoryId',
title: '托寄物品類',
required: true,
filterable: true,
placeholder: '請選擇',
dictionary: [{
name: '蘋果',
code: '1'
},{
name: '手機',
code: '2'
},{
name: '測試',
code: '3'
},{
name: '櫻桃',
code: '7'
},{
name: '荸薺',
code: '9'
}]
},
{
type: 'select',
name: 'startRegionId',
title: '區域',
required: true,
placeholder: '請選擇',
dictionary: [{
name: '銷售-華北區域',
code: '1'
},{
name: '銷售-華東區域',
code: '2'
},{
name: '銷售-華南區域',
code: '3'
},{
name: '銷售-西南區域',
code: '4'
},{
name: '銷售-華中區域',
code: '5'
},{
name: '銷售-東北區域',
code: '6'
}],
// 點擊下來觸發切換聯動的事件,為一個函數
changeFunction: function (val) {
}
}, {
type: 'select',
name: 'startBattleId',
title: '戰區',
required: true,
placeholder: '請選擇',
dictionary: this.battleByRegionList
}, {
type: 'select',
name: 'projectStage',
title: '項目階段',
required: true,
placeholder: '請選擇',
dictionary: [{
name: '項目發起階段',
code: '10'
},{
name: '項目調研階段',
code: '20'
},{
name: '可行性分析階段',
code: '30'
},{
name: '立項階段',
code: '40'
}]
}, {
type: 'text',
name: 'projectStandardName',
title: '標準名稱',
required: true,
placeholder: '請輸入',
append: '.com', // 文本框後置內容
}, {
type: 'text',
name: 'projectManagerErp',
title: '項目經理',
required: true,
placeholder: '請輸入'
},{
type: 'cascader', // 欄位類型下拉框
name: 'address', //與後臺對接欄位
title: '省市區', // 前端展示欄位
required: true, // 必填項設置
placeholder:'請選擇', // 占位文本提示
dictionary: [{
value: 'shanxi',
label: '陝西省',
children: [{
value: 'xian',
label: '西安市',
children: [{
value: 'yanta',
label: '雁塔區'
}, {
value: 'beilin',
label: '碑林區'
}, {
value: 'xincheng',
label: '新城區'
}, {
value: 'weiyang',
label: '未央區'
}]
}]
}],
// 點擊下來觸發切換聯動的事件,為一個函數
changeFunction: function(){}
},{
type: 'static',
name: 'projectYear',
title: '年份'
}
]
}
•表單保存
// 保存
async onSave() {
const valid = await this.$refs.formDemo.onValidate()
if(valid) {
this.$message.success('校驗通過')
}else {
this.$message.error('校驗失敗')
}
}
四、成果展示
作者:京東物流 田雷雷