概述
後臺管理系統裏面有非常多的表單需求,我們希望能夠通過寫一個json格式的數據,通過vue的循環動態地去渲染動態表單。並且能夠在外部得到渲染出來的表單的數據,可以對錶單進行重置操作。我結合element ui的控件的下拉框,輸入框,時間選擇控件和vue-treeselect,做了一個動態表單。
v-model的理解
先簡單講一下vue-model是怎麼玩的。其實vue-model相當於給表單元素傳遞一個value,外部監聽input事件。所以我們自己封裝表單組件的時候也是可以傳遞一個value值,監聽input事件獲取輸入的值。
<input type="text" v-model="something">
<!--等價於-->
<input type="text"
v-bind:value="something"
v-on:input="something = $event.target.value">
封裝表單組件
組件最重要的開發思想就是設計好輸入輸出。這裏就以下拉框組件爲例吧。使用的是element ui的下拉框,進行一個簡單封裝。
輸入:name:每個表單的數據標識,如區域編碼輸入框,父元素應該傳遞areaCode過來。
value: 表單選擇/輸入的值,從父元素獲取後賦值給currentValue,通過監聽父元素的值實現同步變
化。
options:下拉框要渲染的選項值,一般是個對象數組。
輸出:onInputEvent,emit一個input事件,讓父元素能夠感知組件的數據變化。
也就是可以在組件使用的地方監聽input事件
<template>
<el-form-item :label="label">
<el-select v-model="currentValue" @input="onInputEvent">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</template>
<script>
import formMixins from '../../../mixins/form-model'
export default {
name: "SelectList",
props: ['name', 'label', 'value','options'],
mixins: [formMixins],
data() {
return {
currentValue: this.value
}
},
methods: {
onInputEvent(value) {
this.$emit('input', this.name, value);
}
},
watch: {
value(val) {
this.currentValue= val;
}
}
}
</script>
一點封裝
由於每個表單組件都是監聽父元素的value值變化,數據變化時都是觸發onInputEvent並執行this.$emit('input'),所以我們可以把這部分內容抽取出來放在mixins裏面。
form-model.js
export default {
props: ['name', 'value'],
data () {
return {
currentValue: this.value
};
},
methods: {
onInputEvent(value) {
this.$emit('input', this.name, value);
},
reset() {
this.currentValue = "";
}
},
watch: {
value (val) {
this.currentValue = val;
}
}
};
然後我們的下拉框組件就可以少寫一些共用的代碼,直接用 mixins: [formMixins]
<template>
<el-form-item :label="label">
<el-select v-model="currentValue" @input="onInputEvent">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</template>
<script>
import formMixins from '../../../mixins/form-model'
export default {
name: "SelectList",
props: ['name', 'label', 'value', 'options'],
mixins: [formMixins],
data() {
return {
currentValue: this.value
}
}
}
</script>
動態生成表單
這裏主要是根據配置的數據,循環生成表單組件。默認提供提交和重置按鈕,如果不需要可以通過slot傳遞其他操作按鈕。這裏的要點主要有:
監聽表單組件的數據變化:
每個表單組件都有一個name標識它的業務含義,綁定的數據也是formData[field.name],@input事件傳遞updateForm,在updateForm裏面更新this.formData[name],保證了this.formData裏面的數據是和表單組件選擇/填寫的內容一致。
重置時改變表單組件的數據:
因爲組件內部會監聽父元素的value,所以這裏只要清空this.formData的值,組件內部的數據也會跟着清空。
<template>
<div>
<el-form :inline="true" ref="form" :model="formData" class="demo-form-inline">
<el-col :span="field.cols" v-for="(field, index) in config.fieldsConfig" v-bind:key="index">
<component :key="index"
:is="field.fieldType"
:label="field.label"
:value="formData[field.name]"
:multiple="field.multiple"
@input="updateForm"
v-bind="field"
:options="field.options"
:ref="field.name"
>
</component>
</el-col>
<slot name="buttons">
<el-button type="primary" @click="submit" size="small">{{onSubmitText}}</el-button>
<el-button type="default" @click="reset" size="small">{{onResetText}}</el-button>
</slot>
</el-form>
</div>
</template>
<script>
import SelectList from './basicComponent/SelectList'
import TextInput from './basicComponent/TextInput'
import TimeSelector from './basicComponent/TimeSelector'
import SelectTree from './basicComponent/SelectTree'
import StaffSelectPopedit from './businessComponent/StaffSelectPopedit'
export default {
name: "FormGenerator",
components: { SelectList, TextInput, TimeSelector, SelectTree, StaffSelectPopedit},
props: ["config", "value"],
data() {
return {
formData: this.value,
onSubmitText: this.config.buttons.onSubmitText || '提交',
onResetText: this.config.buttons.onResetText || '重置'
}
},
methods: {
updateForm(fieldName, value) {
this.formData[fieldName] = value;
},
submit() {
this.$emit("submit");
},
reset() {
for(var name in this.formData) {
if(typeof this.formData === "String") {
this.formData[name] = "";
} else {
this.formData[name] = null;
}
}
}
}
}
</script>
業務使用的地方
像下拉框的選擇數據,這些應該是後臺渲染的,所以我們暫時用setTimeout模擬一下。感覺這裏this.config.fieldsConfig[4].options寫的不太優雅,依賴於配置數據的順序肯定不是啥好事情。求大神指點。
<template>
<div>
<form-generator :config="config"
@submit="getFormData"
v-model="formData"
>
</form-generator>
</div>
</template>
<script>
import FormGenerator from '../components/form/FormGenerator'
export default {
name: "FormGeneratorDemo",
components: { FormGenerator },
created () {
this.queryOrderType();
this.queryAreaTree();
},
data() {
return {
formData: {
orderCode: "",
orderType: "",
beginTime: "",
endTime: "",
area: [],
staff:""
},
config: {
fieldsConfig: [
{
name: 'orderCode',
label: '定單編碼',
fieldType: 'TextInput',
cols: 8
},
{
name: 'orderType',
label: '定單類型',
fieldType: 'SelectList',
options: [],
cols: 8
},
{
name: 'beginTime',
label: '開始時間',
fieldType: 'TimeSelector',
cols: 8
},
{
name: 'endTime',
label: '結束時間',
fieldType: 'TimeSelector',
cols: 8
},
{
name: 'area',
label: '區域',
fieldType: 'selectTree',
options: [],
multiple: true,
cols: 8
},
{
name: 'staff',
label: '人員選擇',
fieldType: 'StaffSelectPopedit',
cols: 8
}
],
buttons: {
onSubmitText: '提交',
onResetText: '重置'
}
}
}
},
methods: {
getFormData() {
console.log(this.formData);
},
queryOrderType() {
setTimeout(() => {
this.config.fieldsConfig[1].options = [
{ label: 'select1', value: 'key1'},
{ label: 'select2', value: 'key2'},
{ label: 'select3', value: 'key3'}
];
}, 100)
},
queryAreaTree() {
this.config.fieldsConfig[4].options = [
{
id: 'a',
label: 'a',
children: [{
id: 'aa',
label: 'AA',
}, {
id: 'ab',
label: 'AB',
}],
}, {
id: 'b',
label: 'B',
}, {
id: 'c',
label: 'C',
}
]
}
}
}
</script>
大概就是這樣的思路,我們希望我們只要寫上面那樣子的配置數據就可以動態生成各種這樣的表單組件,不用寫一大堆重複代碼。如果有更好的解決辦法,歡迎和我聯繫。