如何通过$emit从包含多子组件的动态表单中获取提交数据
Vue动态子组件表单数据收集方案
问题描述
- 开发支持任务清单的应用,用户可给每个任务选择是/否响应
- 选“否”时需要选择未完成原因,选“其他”需填写自定义原因,还可添加备注
- 任务项为动态渲染的子组件,外层包裹form标签后无法直接获取子组件内的表单数据
实现方案
通过给子组件绑定引用+子组件暴露数据获取方法的方式实现数据收集,修改后可直接运行的代码如下:
App.vue 调整代码
<template> <button @click="toggleComplete">Complete All</button> <form @submit.prevent="submitModal"> <checklist-item v-for="taskList in myChecklist.tasks.data" :key="taskList.id" :taskDetails="taskList" :marked="markAll" ref="checklistItems" > </checklist-item> <div class=""> <button class="" type="submit">Submit</button> </div> </form> </template> <script> import checklistItem from "./components/checklistItem.vue"; export default { name: "App", components: { checklistItem, }, data() { return { markAll: false, myChecklist: { success: true, name: "Checklist 1", roomId: 1, notes: "Notes about the checklist", tasks: { data: [ { id: 1, name: "Task 1", frequency: "D", status: null, comment: null, reason: null, updated_at: "2021-08-09", }, { id: 2, name: "Task 2", frequency: "M", status: null, comment: null, reason: null, updated_at: "2021-08-09", }, { id: 3, name: "Task 3", frequency: "Y", status: null, comment: null, reason: null, updated_at: "2021-08-09", }, ], }, }, }; }, methods: { toggleComplete() { this.markAll = !this.markAll; // 全选同步更新子组件的单选状态 this.$refs.checklistItems.forEach(item => { if (this.markAll) { item.formData.status = true item.disableSelect() } }) }, submitModal() { // 遍历所有子组件收集数据 const submitData = this.$refs.checklistItems.map(item => item.getFormData()) console.log('提交的所有任务数据', submitData) // 后续提交接口逻辑写在这里 }, }, }; </script>
ChecklistItem.vue 调整代码
<template> <div class="flex justify-center items-center"> <div class="border border-gray-200 w-11/12 bg-white rounded-lg shadow-sm hover:shadow-2xl duration-500 px-2 py-4 my-1" > <div class="flex flex-col m-auto w-11.5/12"> <div class=""> <div class="text-xl font-semibold"> {{ taskDetails.name }} </div> <div class="pb-2 sm-pb-0"> <p>Due: <strong>Today</strong></p> </div> <!-- Yes Button --> <div class="yesClass flex justify-center-0 pb-2"> <input @change="disableSelect()" type="radio" v-model="formData.status" :value="true" :name="taskDetails.id" :id="taskDetails.id + 'yes'" class="" /> <label :for="taskDetails.id + 'yes'" class="yesClass border text-center border-orange-400 rounded-2xl w-full py-0.5 hover:bg-green-400 hover:text-white hover:border-green-200" > <span>Yes</span> </label> </div> <!-- No Button --> <div class="noClass flex justify-center pb-2"> <input @change="enableSelect()" type="radio" v-model="formData.status" :value="false" :name="taskDetails.id" :id="taskDetails.id + 'no'" class="" /> <label :for="taskDetails.id + 'no'" class="border text-center border-orange-400 rounded-2xl w-full py-0.5 label-checked:bg-green-200 hover:bg-red-800 hover:text-white hover:border-red-200" > <span>No</span> </label> </div> <!-- Reason Dropdown --> <p>Reason:</p> <div class="disabled flex justify-center"> <br /> <select :disabled="!notCompletedToggle" v-model="formData.reason" id="reasons" class="w-full mt-1 rounded-lg py-0.5 my-0.5" :class="[notCompletedToggle ? 'bg-red-500' : '']" ref="selectedItem" @change="customEventToggle($event)" > <option value="" :selected="!notCompletedToggle"> Select Reason </option> <option value="reason_1">Reason 1</option> <option value="reason_2">Reason 2</option> <option value="reason_3">Reason 3</option> <option value="other">Other --></option> </select> </div> <div class="flex justify-center my-0.5" :class="{ hidden: !customReasonToggle }" > <input type="text" class="bg-gray-200 w-full rounded-md mt-1 py-0.5" v-model="formData.customReason" /> </div> </div> <div class=""> <div class="flex items-end justify-start text-grey"> <label>Comment:</label> </div> <div class="flex items-end justify-center"> <textarea type="text" class="bg-gray-200 rounded-lg w-full h-16" v-model="formData.comment" ></textarea> </div> </div> </div> </div> </div> </template> <script> export default { components: {}, emits: ["custom-event-toggle"], props: ["taskDetails", "marked"], data() { return { notCompletedToggle: false, customReasonToggle: false, // 统一管理当前任务的所有表单数据 formData: { taskId: this.taskDetails.id, status: null, reason: '', customReason: '', comment: '' } }; }, watch: { // 监听全选标记同步状态 marked: function(newVal) { this.formData.status = newVal if (newVal) { this.disableSelect() } } }, methods: { // 对外暴露获取当前组件数据的方法 getFormData() { return { ...this.formData, // 最终提交的原因如果是其他就取自定义输入值 finalReason: this.formData.reason === 'other' ? this.formData.customReason : this.formData.reason } }, customEventToggle(event) { if (event.target.value === "other") { this.customReasonToggle = true; } else { this.customReasonToggle = false; } }, disableSelect() { this.notCompletedToggle = false; this.customReasonToggle = false; this.formData.reason = '' this.formData.customReason = '' }, enableSelect() { this.notCompletedToggle = true; }, }, }; </script> <style scoped> input[type="radio"] { display: none; } .yesClass input[type="radio"]:checked + label { background-color: #78be20; color: white; } .noClass input[type="radio"]:checked + label { background-color: #da291c; color: white; } </style>
方案说明
- 子组件内部统一用
formData管理当前任务的所有填写字段,避免数据分散 - 父组件通过
ref获取所有子组件实例,提交时调用每个子组件的getFormData方法即可拿到格式化后的所有任务数据 - 全选逻辑同步更新所有子组件的单选状态,无需额外手动处理
- 最终提交的数据里已经整合了最终原因:如果选了其他就取自定义输入的内容,否则取下拉选择的原因
内容的提问来源于stack exchange,提问作者babis95
相关产品推荐
相关产品推荐

