Appearance
结论(TL;DR)
你说的没错:双规则(change + submit)如果都报 error,会出现“两个上传失败”的重复提示。 最佳做法:把 change 阶段的校验改成非阻断的 warning,submit 阶段再做真正的 error 拦截。另外给 Form.Item 开 validateFirst,避免一次校验堆叠多条错误。
只给需要修改的代码片段(含关键注释)
ts
// 复杂逻辑:变更时只提示“失败(warning)”,不阻断提交、不产生 error 堆叠
rules.push({
validator: uploadChangeRule(field.label),
trigger: ["change"],
warningOnly: true, // ★ 把 change 阶段的校验降级为“警告”,避免与 submit 的 error 重复
});ts
// 复杂逻辑:提交时做真正拦截——先拦“失败”,再拦“仍在上传”,最后拦“必填未满足”
rules.push({
validator: uploadSubmitRule(field.label, field.required !== false),
trigger: ["submit"],
});tsx
// 复杂逻辑:避免一次校验出现多条错误信息
<Form.Item name={name} rules={rules} validateFirst>
{/* ...Upload组件... */}
</Form.Item>说明:
warningOnly: true会让这条规则的“失败”以黄色提示显示,不会把字段置为 error,也不会阻断提交。submit规则仍然是 error,只在点击提交时触发,不会与 change 阶段的 warning 叠加为两条 error。validateFirst可保证同一次校验遇到第一条失败就停,不再堆叠错误文案。
可选的进一步优化(按需选用)
ts
// 复杂逻辑:为 change 阶段的“失败”换成气泡/通知,而不是表单里的文案(彻底消除重复感)
if (list.some((f) => f?.status === "error")) {
window.GtcNotification("error", t("files.form.rule.uploadError", { label }));
return Promise.resolve(); // 不在字段上报错
}ts
// 复杂逻辑:减少重复触发带来的扰动(表单级别节流)
<Form form={form} validateDebounce={300}>
{/* ... */}
</Form>为什么这样能避免“两条失败”
职责拆分:
change只做即时反馈(warning),不拉低字段为 error;submit只在最终关口做阻断(error)。
渲染层面:warning 与 error 样式不同,且不会叠加两条 error。
流程层面:
validateFirst+submit独立触发,避免在同一校验周期堆叠多条失败文案。
小结
- 保留“双规则”的好体验,但把 change → warning、submit → error。
- 打开
validateFirst,必要时配合validateDebounce。 - 如仍担心视觉重复,
change阶段用通知/气泡替代表单内文案。
title: 动态表单上传校验:避免重复报错的最小改动(双规则:change=warning,submit=error)
需要调整的代码片段
1) <a-form-item> 增加 validate-first(避免同一次校验堆叠多条错误)
vue
<!-- 复杂逻辑:开启 validate-first,遇到第一条失败即停止,避免同周期多条错误文案叠加 -->
<a-form-item
:label="f.controlType === 'stockholder' ? '' : `${props.showIndex ? i + 1 : ''} ${f.label}`"
:name="f.name"
:rules="getValidationRules(f)"
:validate-trigger="['change', 'blur']"
:validate-first="true"
:data-question-id="f.name"
>vue
<a-form-item
:label="item.label"
:name="item.name"
:rules="getValidationRules(item)"
:validate-trigger="['change', 'blur']"
:validate-first="true"
:data-question-id="item.name"
>2 image/file 的规则改为 “change=warning,submit=error”
ts
// 复杂逻辑:变更阶段只给 warning(不阻断、不置红),提交阶段再做真正的 error 拦截
else if (['image', 'file'].includes(field.controlType ?? '')) {
rules.push(
{ validator: uploadChangeRule(field.label), trigger: ['change'], warningOnly: true }, // 修改:加 warningOnly
{ validator: uploadSubmitRule(field.label, field.required !== false) } // 修改:去掉 trigger,让其在提交校验时生效
)
}说明:
warningOnly: true会把uploadChangeRule的抛错降级为黄色警告,不会与提交时的 error 形成“双重报错”。- 第二条规则不设置 trigger,在表单提交(或手动
validateFields)时会参与校验,作为最终拦截。
3)(可选)如果你希望变更阶段完全不飘红,只弹通知
ts
// 复杂逻辑:把 change 阶段“失败”改成通知,不在字段上表现为 warning(纯体验优化,可选)
const uploadChangeRule = (label: string) => async (_: any, v: any[]) => {
const list = (v ?? []).filter((f) => f?.status !== "removed");
const hasError = list.some((f) => f?.status === "error");
if (hasError) {
window.GtcNotification(
"error",
t("files.form.rule.uploadError", { label })
);
return Promise.resolve(); // 不抛错 -> 不产生 warning
}
return Promise.resolve();
};若采用这一可选改法,请把第 2 步里的
warningOnly: true保留或移除均可(已不抛错时,warningOnly 不影响结果)。
为什么这样改
- 避免“两条失败”:
change阶段仅提示 warning,submit阶段才 error。 - 严格把关:提交时统一拦住三种情况:有失败、仍在上传、无成功文件。
- 不误判:对
percent=100 但 status='uploading'的中间态,change阶段不报错,体验更顺滑。
ts
// 失败优先,其次必填(至少一个 done)
const uploadChangeRule = (label: string) => async (_: any, v: any[]) => {
const list = (v ?? []).filter((f) => f?.status !== 'removed')
const hasError = list.some((f) => f?.status === 'error')
if (hasError) {
throw new Error(t('files.form.rule.uploadError', { label }))
}
// 上传中(包括 percent=100 但仍 uploading)不在 change 阶段报错,交给提交阶段兜底
return Promise.resolve()
}
const uploadSubmitRule =
(label: string, required = true) =>
async (_: any, v: any[]) => {
const list = (v ?? []).filter((f) => f?.status !== 'removed')
const hasError = list.some((f) => f?.status === 'error')
if (hasError) {
throw new Error(t('files.form.rule.uploadError', { label }))
}
const hasUploading = list.some((f) => f?.status === 'uploading')
if (hasUploading) {
// 阻止提交,给“仍在上传”的明确提示
throw new Error(t('files.form.rule.uploading', { label })) // 如:`{label} 正在上传,请稍候`
}
const hasDone = list.some((f) => f?.status === 'done')
if (required && !hasDone) {
throw new Error(t('files.form.rule.uploadInvalid', { label })) // 如:`请至少成功上传 1 个 {label}`
}
return Promise.resolve()
}
if (['image', 'file'].includes(field.controlType ?? '')) {
rules.push(
{ validator: uploadChangeRule(field.label), trigger: ['change'], warningOnly: true },
{ validator: uploadSubmitRule(field.label, field.required !== false), trigger: ['submit'] },
)