Vue3 子组件表单验证 + 父组件弹窗流程
父组件打开弹窗 → 弹窗里是子组件表单 → 父组件点击“确定” → 调用子组件校验 → 校验成功后提交数据
子组件:表单校验(Form.vue)
目的
- 子组件是一个通用表单,接收 formInline 数据,并对其进行验证
// props 接收来自父组件的默认表单数据
const props = withDefaults(defineProps<FormProps>(), {
formInline: () => ({
username: "",
role: "",
password: "",
sex: null,
age: null,
phone: "",
avatar: []
})
});
// 为避免直接修改 props(只读),创建新的响应式数据
const newFormInline = ref(props.formInline);子组件暴露方法给父组件
作用
- 暴露验证方法给父组件,通过ref访问(使用父组件可以直接调用子组件里面的这些函数)
//挂载后:formRef.value 是一个 Element Plus ElForm 组件实例,
// 里面有 validate、resetFields 等方法
const formRef = ref<InstanceType<typeof ElForm> | null>(null);
// 暴露验证方法给父组件,通过ref访问(使用父组件可以直接调用子组件里面的这些函数)
defineExpose({
// 表单校验:返回 Promise<boolean>
async validate() {
if (!formRef.value) return false;
try {
await formRef.value.validate();
return true;
} catch (err) {
ElMessage.error("表单验证失败");
return false;
}
},
// 重置表单
resetFields() {
formRef.value?.resetFields();
},
// 暴露一个获取当前表单数据的方法,父组件可以通过调用它来读取表单内容(通常用于预览、提交前检查、或父组件需要读取子表单的数据时)
getFormData() {
return newFormInline.value;
}
});父组件:打开弹窗并渲染表单内容
目的
父组件需要收集:
表单初始值
子组件实例
const formInline = ref({
username: "",
role: "",
password: "",
sex: null,
age: null,
phone: "",
avatar: []
});
// 接收 Form.vue 暴露的方法
interface FormExpose {
validate: () => Promise<boolean>;
resetFields: () => void;
getFormData: () => any;
}
// 使用 shallowRef 存储子组件实例(只对 .value 本身做响应,不递归做深层响应式)
// 父组件就是要通过这个实例调子组件暴露的方法 (validate() / getFormData())
const formInstance = shallowRef<FormExpose | null>(null);
// 把当前表单数据(formInline.value)深拷贝一份,存到 resetFormInline 中,用于后续“重置表单”操作
const resetFormInline = cloneDeep(formInline.value);父组件调用弹窗:addDialog
function onBeforeSureClick() {
addDialog({
title: "用户注册",
width: "450px",
// 渲染弹窗内容:传入表单组件
contentRenderer: () => {
return h(forms, {
// 传递原始数据(非响应式),避免子组件意外修改父状态
formInline: toRaw(formInline.value),
// 捕获子组件实例
// instance 子组件 Forms.vue 的实例对象
ref: (instance) => {
// 挂载后,父组件拿到了子组件实例
formInstance.value = instance as unknown as FormExpose;
}
});
},
// 点击弹窗底部【确定】按钮时触发
beforeSure: async (done) => {
// 等待 DOM 渲染完成,确保 ref 已赋值
await nextTick();
const instance = formInstance.value;
if (!instance) {
ElMessage.error("未能获取表单实例");
done();
return;
}
// 调用子组件暴露的 validate 方法
const isValid = await instance.validate?.();
if (isValid) {
// 获取表单数据(假设子组件暴露了 getFormData)
const formData = instance.getFormData?.();
// 必须重新复制一个值来进行操作
const submitData = { ...formData };
// 如果有头像,提取 URL 字符串用于提交
if (Array.isArray(formData.avatar) && formData.avatar.length > 0) {
submitData.avatar = formData.avatar[0].url || "";
} else {
submitData.avatar = "";
}
console.log("注册表单数据:", submitData);
/**
* 写法比较清晰
*/
//
try {
loading.value = true; // 启动加载动画
console.log("用户名111:", submitData);
// 等待接口返回
const res = await LoginSave(submitData);
if (res.code === "0") {
ElMessage.success("注册信息已提交!");
done(); // 成功后再关闭弹窗
} else {
ElMessage.error(res.message || '登录失败,请检查用户名或密码');
}
} catch (err: any) {
console.log(err);
ElMessage.error(err.message || '请求失败');
} finally {
loading.value = false; // 保证无论成功失败都停止 loading
}
} else {
console.warn("表单验证未通过,阻止关闭弹窗")
}
},
// 弹窗关闭后回调
closeCallBack: () => {
console.log("执行关闭")
// 重置父组件的表单数据(下次打开是干净状态)
Object.assign(formInline.value, { ...resetFormInline });
}
});
}onBeforeSureClick() 做的关键事
| 步骤 | 内容 | 作用 |
|---|---|---|
| 1 | 定义 formInstance | 保存子组件实例 |
| 2 | addDialog 打开弹窗 | 显示表单 |
| 3 | 渲染 Form.vue 并获取 instance | 调用子组件暴露方法 |
| 4 | 点击确定 → 调用 validate() | 做表单校验 |
| 5 | 校验成功 → getFormData() | 获取填写内容并处理文件格式 |
| 6 | 发送后端接口 + 加载动画 | 真实提交注册数据 |
| 7 | closeCallback 重置父表单 | 保证下次打开是干净表单 |
流程
父组件点击按钮
↓
addDialog 打开弹窗
↓
渲染子组件 Form
↓
子组件 defineExpose 暴露 validate,getFormData
↓
用户点击「确定」
↓
父组件执行 formInstance.validate()
↓
(子组件执行 element-plus 表单校验)
↓
校验失败 → 阻止关闭
校验成功 → formInstance.getFormData()
↓
父组件提交后端接口
↓
成功 → done() 关闭弹窗
↓
closeCallBack 重置数据版权所有
版权归属:念宇
