You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过$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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 14:24:03