博客地址:https://ainyi.com/66
又接到新需求了吧~~
背景
在一個大表單里,有可能會出現這種需求,用戶可以自己操作動態添加、移除表單,更加個性化的效果。
常見於填寫個人信息、附加內容的表單
例如:
“工作經歷”可以用戶自己點擊繼續添加按鈕,在原有的表單后面 append 多一個表單,不需要就點擊右上方 X 按鈕移除
問題
在實現之前,提出幾個問題
- vue 怎么動態渲染或移除表單上去
- v-model 怎么綁定動態添加表單的 value 值
- 動態新增的表單如何驗證
- 動態表單怎么填寫對應的 prop
...
好吧,我當時也思考了一會,最后選擇數組方式,動態渲染
代碼實現講解
利用數組,v-for 循環方式,可以完美實現動態渲染和移除,因為操作的只有對象數組而已
請格外注意動態添加表單的 rule 和 prop
每個動態添加的表單都要加上 rule
prop 需要根據對象數組下標綁定設置對應的 value(:prop="'azList[' + index + '].azName'")
<div class="section-form" v-for="(item, index) in form.azList" :key="index">
<span v-if="isShowCloseBtn" class="close" @click="deleteItem(index)">
<i class="el-icon-close"></i>
</span>
<el-form-item label="可用區名稱:"
:rules="[{ required: true, message: '可用區名稱不能為空' }]"
:prop="'azList[' + index + '].azName'"
label-width="150px">
<el-input placeholder="請輸入可用區名稱" v-model="item.azName" :maxlength="30"></el-input>
</el-form-item>
<el-form-item label="邏輯可用區編碼:"
:rules="[{ required: true, message: '邏輯可用區編碼不能為空' }]"
label-width="150px"
:prop="'azList[' + index + '].logicCode'">
<el-input placeholder="請輸入唯一ID" v-model="item.logicCode" :maxlength="30"></el-input>
</el-form-item>
<el-form-item label="物理可用區編碼:"
:rules="[{ required: true, message: '物理可用區編碼不能為空' }]"
label-width="150px"
:prop="'azList[' + index + '].physicCode'">
<el-input placeholder="請輸入唯一ID" v-model="item.physicCode" :maxlength="30"></el-input>
</el-form-item>
</div>
那么對應的 js 代碼為
export default {
name: 'vouchersDetail',
data() {
return {
form: {
regionName: '',
regionCode: '',
// 動態添加的對象數組
azList: [
{
azName: '',
logicCode: '',
physicCode: ''
}
]
}
}
},
computed: {
// 至少保留一個動態表單的開關
isShowCloseBtn() {
return this.form['azList'].length > 1
}
},
methods: {
addItem() {
// 點擊添加表單的按鈕,只需要將表單綁定的 value 作為對象 push 到對象數組
this.form['azList'].push({
azName: '',
logicCode: '',
physicCode: '',
weight: ''
})
},
deleteItem(index) {
// 點擊移除表單的按鈕,根據點擊的當前 index 移除對象數組的元素
this.form['azList'].splice(index, 1)
},
goBack() {
window.history.back(-1)
}
}
}
更新
19號更新,分離組件方法,寫法更簡便,易維護,還可以將校驗規則剝離出去
根據上面的方法 利用數組,v-for 循環方式
此次更新,關鍵在於,是父組件引用子組件的 template 循環
v-for 循環數組的 item 對象傳入子組件 template
每個子組件的 form 的 :model = 傳入的 item,也就不需要用到數組下標 index,每個子組件是獨立的一個 form,也就是說,每個動態添加字段的校驗規則可以剝離出去了
父組件
template 循環
<create-region
class="section-form"
ref="refCreateAz"
:infoData="item"
:indexNum="index"
:isShowCloseBtn="isShowCloseBtn"
v-for="(item, index) in form.azList"
:key="index"
@deleteItem="deleteItem">
</create-region>
js 與原來無差,只是多了引入子組件的 component
components: {
CreateRegion: () => import('@/views/region/models/CreateRegion')
}
子組件
<template>
<el-form :model="infoData" :rules="rulesAz" label-width="150px" ref="formAz">
<span v-if="isShowCloseBtn" class="close" @click="deleteItem">
<i class="el-icon-close"></i>
</span>
<el-form-item label="可用區名稱:" prop="azName" label-width="150px">
<el-input placeholder="請輸入可用區名稱" v-model="infoData.azName" :maxlength="30"></el-input>
</el-form-item>
<el-form-item label="邏輯可用區編碼:" label-width="150px" prop="logicCode">
<el-input placeholder="請輸入唯一ID" v-model="infoData.logicCode" :maxlength="30"></el-input>
</el-form-item>
<el-form-item label="物理可用區編碼:" label-width="150px" prop="physicCode">
<el-input placeholder="請輸入唯一ID" v-model="infoData.physicCode" :maxlength="30"></el-input>
</el-form-item>
<el-form-item label="權重設置:" label-width="150px">
<el-input placeholder="請設置權重" v-model="infoData.weight"></el-input>
</el-form-item>
</el-form>
</template>
<script>
import { ORGION_AZLIST_RULES } from '@/views/service/rules'
export default {
props: {
infoData: {
require: true
},
indexNum: {
type: Number
},
isShowCloseBtn: {
type: Boolean
}
},
data() {
return {
form: this.infoData,
rulesAz: ORGION_AZLIST_RULES.call(this)
}
},
computed: {},
methods: {
deleteItem() {
this.$emit('deleteItem', this.indexNum)
},
validates() {
return new Promise((resolve, reject) => {
this.$refs['formAz'].validate(async valid => {
if (valid) {
// 驗證通過
resolve(true)
} else {
reject(false)
}
})
})
}
}
}
</script>
校驗
export const ORGION_AZLIST_RULES = function() {
return {
logicCode: [
{
required: true,
message: '邏輯可用區編碼不能為空',
trigger: 'blur'
},
{
validator: CHECK_AZEXITS_CODE.bind(this),
trigger: 'blur'
}
],
physicCode: [
{
required: true,
message: '物理可用區編碼不能為空',
trigger: 'blur'
},
{
validator: CHECK_AZEXITS_CODE.bind(this),
trigger: 'blur'
}
],
azName: [
{
required: true,
message: '可用區名稱不能為空',
trigger: 'blur'
}
]
}
}
自定義校驗
export const CHECK_AZEXITS_CODE = async function(rule, value, callback) {
let paramName = rule.field
let reqData = {}
reqData[paramName] = value
let { result } = await getAzExist(reqData)
if (result.result) {
if (paramName === 'logicCode') {
callback(new Error('邏輯可用區編碼已存在,請重新輸入'))
} else {
callback(new Error('物理可用區編碼已存在,請重新輸入'))
}
} else {
callback()
}
}
寫在后面
如果大家有啥更好的方法實現,歡迎在評論區相互探討~
寫完下班、
博客地址:https://ainyi.com/66