效果:
預期:像這樣的表單結構,如果在form中一行一行寫每個文本域,有點麻煩,封裝成一個組件,同類型支持新增和刪除
①DynamicForm.vue
<template> <div class="dynamic-form"> <div class="title"> <p>{{template.title}}</p> <van-icon name="add-o" @click="handleAdd" /> </div> <div class="content" v-for="(item,index) in MyList" :key="index"> <p class="icon" @click="handleDelete(index)"> <img src="@/assets/public/delete.svg"> </p> <van-field v-for="(ele,i) in template.labels" :key="i" v-model="item[ele.field]" rows="3" autosize :label="ele.name" placeholder="請輸入" type="textarea" maxlength="200" show-word-limit clearable required :rules="[{required:true,message:'必填項,不能為空'}]" /> </div> </div> </template> <script> export default { model: { prop: 'data', event: 'change' }, computed: { MyList: { get: vm => vm.data, set(data) { this.$emit('change', data) } } }, props: { template: { type: Object, required: true }, data: { type: Array, require: true } }, methods: { handleAdd() { this.MyList.push({}) }, handleDelete(index) { if (this.MyList.length === 1) return this.$toast('必須保留一項') this.MyList.splice(index, 1) } } } </script>

<style lang="less" scoped> .dynamic-form { & + & { margin-top: 20px; } .title { display: flex; justify-content: space-between; align-items: center; position: relative; > p { margin-left: 15px; font-size: 16px; color: #4d5c82; } .van-icon { color: #198cff; font-size: 20px; font-weight: 600; } .van-icon::after { content: ''; position: absolute; top: -5px; bottom: -5px; left: -5px; right: -5px; border-radius: 50%; } &::before { content: '*'; position: absolute; color: #fc5e5e; } } /deep/ .content { margin-top: 10px; background-color: #f0f4f8; border-radius: 5px; padding: 15px; position: relative; > .icon { position: absolute; right: 15px; top: 10px; z-index: 10; } > .icon::after { content: ''; position: absolute; top: -5px; bottom: -5px; left: -5px; right: -5px; border-radius: 50%; } .van-cell--required::before { color: #fc5e5e; left: 0; } .van-cell::after { border-bottom: none; } .van-cell { padding: 0; line-height: 1; align-items: center; margin-top: 20px; display: block; background-color: #f0f4f8; &:first-of-type { margin-top: 0; } .van-field__label { margin-left: 15px; width: 90%; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; word-break: break-all; > span { font-size: 16px; color: #4d5c82; } } .van-field__value { margin-top: 10px; padding-left: 8px; padding-right: 8px; line-height: 32px; background-color: #fff; border-radius: 5px; input::-webkit-input-placeholder, textarea::-webkit-input-placeholder { color: rgba(#4d5c82, 0.6); } .van-field__control { color: #4d5c82; font-size: 16px; } .van-field__word-limit { position: absolute; right: 5px; bottom: 5px; color: rgba(#4d5c82, 0.6); font-size: 12px; } } } } } </style>
②拜訪計划中使用
// 引入 import DynamicForm from '@/components/DynamicForm' // data page2Data: [ { title: '拜訪計划', variable: 'requirements', labels: [ { name: '初步預測需求點', field: 'requirement' }, { name: '判斷理由', field: 'reason' } ], data: [{}] }, { title: '提問設計(我方提問)', variable: 'questions', labels: [{ name: '問題', field: 'question' }], data: [{}] }, { title: '杜賓清單(客戶會提的問題或者顧慮)', variable: 'duBins', labels: [ { name: '問題', field: 'question' }, { name: '解決方案', field: 'solution' } ], data: [{}] }, { title: '信任等級', variable: 'trustLevels', labels: [ { name: '提升計划', field: 'improvementPlan' }, { name: '原因', field: 'reason' } ], data: [{}] }, { title: '物料准備', variable: 'materials', labels: [ { name: '具體物料', field: 'material' }, { name: '選擇原因', field: 'reason' } ], data: [{}] } ]
DOM:
<van-form @submit="handleSubmit" validate-trigger='onSubmit'> <DynamicForm v-for="(item,index) in page2Data" :key="index" v-model="item.data" :template='item' /> <div class="page2-footer"> <van-button native-type="submit" :loading='submitLoading'>提交</van-button> </div> </van-form>
得到的數據結構:

const arr = [ { title: '拜訪計划', variable: 'requirements', labels: [ { name: '初步預測需求點', field: 'requirement' }, { name: '判斷理由', field: 'reason' } ], data: [{ requirement: '2', reason: '2' }] }, { title: '提問設計(我方提問)', variable: 'questions', labels: [{ name: '問題', field: 'question' }], data: [{ question: '1' }] }, { title: '杜賓清單(客戶會提的問題或者顧慮)', variable: 'duBins', labels: [ { name: '問題', field: 'question' }, { name: '解決方案', field: 'solution' } ], data: [{ question: '1', solution: '1' }] }, { title: '信任等級', variable: 'trustLevels', labels: [ { name: '提升計划', field: 'improvementPlan' }, { name: '原因', field: 'reason' } ], data: [{ improvementPlan: '6', reason: '6' }] }, { title: '物料准備', variable: 'materials', labels: [ { name: '具體物料', field: 'material' }, { name: '選擇原因', field: 'reason' } ], data: [{ material: '16', reason: '6' }] } ]
③拜訪反饋中使用
// 引入 import DynamicForm from '@/components/DynamicForm' // data page2Data: [ { title: '需求調研結果匯總', variable: 'requirementList', labels: [ { name: '客戶實際需求', field: 'requirement' }, { name: '判斷理由', field: 'reason' } ], data: [{}] }, { title: '客戶實際提出的顧慮/問題', variable: 'questionList', labels: [{ name: '顧慮/問題', field: 'concern' }], data: [{}] } ]
DOM:
<van-form @submit="handleSubmit" validate-trigger='onSubmit'> <DynamicForm v-for="(item,index) in page2Data" :key="index" v-model="item.data" :template='item' /> <div class="page2-footer"> <van-button native-type="submit" :loading='submitLoading'>提交</van-button> </div> </van-form>
得到的數據結構:

const arr = [ { title: '需求調研結果匯總', variable: 'requirementList', labels: [ { name: '客戶實際需求', field: 'requirement' }, { name: '判斷理由', field: 'reason' } ], data: [ { requirement: '1', reason: '2' }, { requirement: '11', reason: '22' } ] }, { title: '客戶實際提出的顧慮/問題', variable: 'questionList', labels: [{ name: '顧慮/問題', field: 'concern' }], data: [{ concern: '1' }] } ]