Skip to content

结论(TL;DR)

你说的没错:双规则(change + submit)如果都报 error,会出现“两个上传失败”的重复提示最佳做法:把 change 阶段的校验改成非阻断的 warningsubmit 阶段再做真正的 error 拦截。另外给 Form.ItemvalidateFirst,避免一次校验堆叠多条错误。


只给需要修改的代码片段(含关键注释)

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>

为什么这样能避免“两条失败”

  1. 职责拆分

    • change 只做即时反馈(warning),不拉低字段为 error;
    • submit 只在最终关口做阻断(error)。
  2. 渲染层面:warning 与 error 样式不同,且不会叠加两条 error

  3. 流程层面validateFirst + submit 独立触发,避免在同一校验周期堆叠多条失败文案。


小结

  • 保留“双规则”的好体验,但把 change → warningsubmit → 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 阶段仅提示 warningsubmit 阶段才 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'] },
    )

本站总访问