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

Vue3子组件向父组件传值及Vuetify步骤器按钮禁用问题

解决方案:Vuetify步骤器表单验证与组件数据传递

一、先解决子组件向父组件传递状态与数据问题

你的核心卡点是子组件没有实时向父组件同步表单的验证状态,导致父组件无法控制步骤器按钮。以下是修正后的组件代码:

1. 修正PersonalDataForm.vue

添加事件定义,实时同步表单完成状态,验证通过后提交数据:

<template>
  <v-container>
    <v-card class="rounded-lg py-4 px-8" elevation="8">
      <form @submit.prevent="handleSubmit">
        <!-- 表单字段部分保持不变 -->
        <v-row>
          <v-col cols="12" md="6">
            <v-text-field
              v-model="state.firstName"
              :error-messages="v$.firstName.$errors.map((e) => e.$message)"
              label="Nome"
              required
              variant="underlined"
              color="deep-purple-lighten-1"
              @blur="v$.firstName.$touch"
              @input="v$.firstName.$touch"
            ></v-text-field>
          </v-col>
          <!-- 其余字段省略 -->
        </v-row>
        <v-btn class="me-4" @click="handleSubmit"> submit </v-btn>
      </form>
    </v-card>
  </v-container>
</template>

<script setup>
import { ref, computed } from 'vue';
import { useVuelidate } from "@vuelidate/core";
import { required } from "@vuelidate/validators";

// 定义需要向父组件触发的事件
const emit = defineEmits(['update:isFormIncomplete', 'formDataSubmitted']);

const state = ref({
  firstName: "",
  lastName: "",
  birthDate: "",
  birthPlace: "",
  fiscalCode: "",
});

const rules = {
  firstName: { required },
  lastName: { required },
  birthDate: { required },
  birthPlace: { required },
  fiscalCode: { required }
};

const v$ = useVuelidate(rules, state.value);

// 实时计算表单是否未完成,并同步给父组件
const isFormIncomplete = computed(() => {
  const incomplete = Object.values(state.value).some(value => !value) || v$.value.$invalid;
  emit('update:isFormIncomplete', incomplete);
  return incomplete;
});

function handleSubmit() {
  v$.value.$touch();

  if (!v$.value.$invalid) {
    const formData = { ...state.value };
    emit('formDataSubmitted', formData); // 向父组件传递表单数据
  } else {
    console.log("表单验证失败,无法提交。");
  }
}
</script>

2. 修正ShippingForm.vue

同样修正事件逻辑,同步表单状态:

<template>
  <v-container>
    <v-card class="rounded-lg py-4 px-8" elevation="8">
      <form @submit.prevent="handleSubmit">
        <!-- 表单字段部分保持不变 -->
        <v-row>
          <v-col cols="12" md="6">
            <v-text-field
              v-model="state.firstName"
              :error-messages="v$.firstName.$errors.map((e) => e.$message)"
              label="Nome"
              required
              variant="underlined"
              color="deep-purple-lighten-1"
              @blur="v$.firstName.$touch"
              @input="v$.firstName.$touch"
            ></v-text-field>
          </v-col>
          <!-- 其余字段省略 -->
        </v-row>
        <v-btn class="me-4" type="submit" @click="handleSubmit"> submit </v-btn>
      </form>
    </v-card>
  </v-container>
</template>

<script setup>
import { reactive, computed, defineEmits } from 'vue';
import { useVuelidate } from "@vuelidate/core";
import { email, required, numeric, minLength, maxLength } from "@vuelidate/validators";

// 定义事件
const emit = defineEmits(['update:isFormIncomplete', 'formDataSubmitted']);

const initialState = {
  firstName: "",
  lastName: "",
  region: "",
  province: "",
  city: "",
  houseNumber: "",
  email: "",
  phoneNumber: "",
};

const state = reactive({ ...initialState });

const regions = ["Emilia", "Lombardia", "Calabria", "Piemonte"];
const provinces = ["piacenza", "Milano", "Cosenza", "Torino"];
const cities = ["Caorso", "Assago", "Lamezia", "Settimo Torinese"];

const rules = {
  firstName: { required },
  lastName: { required },
  region: { required },
  province: { required },
  city: { required },
  houseNumber: { required, numeric },
  email: { required, email },
  phoneNumber: { required, numeric, minLength: minLength(12), maxLength: maxLength(12) },
};

const v$ = useVuelidate(rules, state);

// 实时同步表单状态到父组件
const isFormIncomplete = computed(() => {
  const incomplete = Object.values(state).some(value => !value) || v$.value.$invalid;
  emit('update:isFormIncomplete', incomplete);
  return incomplete;
});

function handleSubmit() {
  v$.value.$touch();

  if (!v$.value.$invalid) {
    const formData = { ...state };
    emit('formDataSubmitted', formData);
  } else {
    console.log("表单验证失败,无法提交。");
  }
}
</script>

二、配置Vuetify步骤器控制下一步按钮禁用

改用v-stepper-item结构,自定义控制按钮,根据子组件传递的状态禁用下一步:

<template>
  <v-stepper 
    color="deep-purple-darken-1" 
    v-model="currentStep"
    class="my-5"
  >
    <!-- 第一步:个人信息 -->
    <v-stepper-item :step="1" :complete="!personalDataFormIsIncomplete">
      <template v-slot:label>Dati Anagrafici</template>
      <v-card>
        <PersonalDataForm 
          @update:isFormIncomplete="val => personalDataFormIsIncomplete = val"
          @formDataSubmitted="handlePersonalDataForm"
        />
      </v-card>
    </v-stepper-item>

    <!-- 第二步:配送地址 -->
    <v-stepper-item :step="2" :complete="!shippingFormIsIncomplete">
      <template v-slot:label>Indirizzo di spedizione</template>
      <v-card>
        <ShippingForm 
          @update:isFormIncomplete="val => shippingFormIsIncomplete = val"
          @formDataSubmitted="handleShippingForm"
        />
      </v-card>
    </v-stepper-item>

    <!-- 第三步:确认页 -->
    <v-stepper-item :step="3">
      <template v-slot:label>Riepilogo</template>
      <v-card>
        <Recap @completed="submitData" :disabled="isFormIncomplete"/>
      </v-card>
    </v-stepper-item>

    <!-- 自定义步骤控制按钮 -->
    <template v-slot:controls>
      <v-btn @click="currentStep--" :disabled="currentStep === 1">
        Indietro
      </v-btn>
      <v-btn 
        color="deep-purple-darken-1" 
        @click="currentStep++"
        :disabled="isCurrentStepIncomplete"
      >
        {{ currentStep === 3 ? 'Conferma' : 'Avanti' }}
      </v-btn>
    </template>
  </v-stepper>
</template>

<script setup>
import { ref, computed } from 'vue';
import PersonalDataForm from '@/components/forms/PersonalDataForm.vue';
import ShippingForm from '@/components/forms/ShippingForm.vue';
import Recap from '../components/Recap.vue';

const currentStep = ref(1);
const personalDataFormIsIncomplete = ref(true);
const shippingFormIsIncomplete = ref(true);

// 判断当前步骤是否未完成,用于禁用下一步按钮
const isCurrentStepIncomplete = computed(() => {
  switch(currentStep.value) {
    case 1: return personalDataFormIsIncomplete.value;
    case 2: return shippingFormIsIncomplete.value;
    case 3: return false;
    default: return true;
  }
});

const isFormIncomplete = computed(() => {
  return personalDataFormIsIncomplete.value || shippingFormIsIncomplete.value;
});

const handlePersonalDataForm = (formData) => {
  console.log("收到个人数据:", formData);
};

const handleShippingForm = (formData) => {
  console.log("收到配送数据:", formData);
};

const submitData = () => {
  console.log('提交数据...');
};
</script>

核心逻辑说明

  1. 组件通信:子组件通过defineEmits定义事件,实时向父组件同步表单的完成状态(update:isFormIncomplete),验证通过后传递表单数据(formDataSubmitted)。
  2. 步骤器控制:通过v-model="currentStep"管理当前步骤,自定义controls插槽实现按钮逻辑,根据isCurrentStepIncomplete计算属性判断是否禁用下一步。
  3. 验证同步:子组件的表单完成状态结合了字段非空检查和Vuelidate的$invalid状态,确保只有完全验证通过的表单才能进入下一步。

内容的提问来源于stack exchange,提问作者Scheggia

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 04:39:50