diff --git a/docs/api/form/form-dialog-props.md b/docs/api/form/form-dialog-props.md index 343ab565..4e928a3a 100644 --- a/docs/api/form/form-dialog-props.md +++ b/docs/api/form/form-dialog-props.md @@ -150,6 +150,14 @@ - **类型:** `boolean` +## useFieldTextInError + +- **详情:** 透传给内部 `Form`,控制表单校验失败时错误提示前缀是否使用字段的 `text` 文案。`false` 时直接使用字段 `name` + +- **默认值:** `true` + +- **类型:** `boolean` + ## closeOnClickModal - **详情:** 是否可以通过点击 modal 关闭 Dialog diff --git a/docs/api/form/form-methods.md b/docs/api/form/form-methods.md index caaf2523..11713b20 100644 --- a/docs/api/form/form-methods.md +++ b/docs/api/form/form-methods.md @@ -40,6 +40,8 @@ - **详情:** 通过 `name` 从表单 `config` 中查找对应表单项的 `text` +- **相关:** 表单校验失败时是否使用该方法生成错误提示前缀,由 [`useFieldTextInError`](./form-props.md#usefieldtextinerror) prop 控制(默认 `true`) + ## values - **类型:** `Ref` diff --git a/docs/api/form/form-props.md b/docs/api/form/form-props.md index 8315d00f..6cf1e6bb 100644 --- a/docs/api/form/form-props.md +++ b/docs/api/form/form-props.md @@ -206,6 +206,19 @@ - **类型:** `boolean` +## useFieldTextInError + +- **详情:** + + 表单校验失败时,错误提示前缀是否使用字段的 `text` 文案(通过 `getTextByName` 从 `config` 中查找)。 + + - `true`(默认):错误提示形如 `字段文案 -> 错误信息`,找不到 `text` 时回退为字段 `name`; + - `false`:跳过查找,直接使用字段 `name` 作为错误提示前缀(形如 `字段name -> 错误信息`)。 + +- **默认值:** `true` + +- **类型:** `boolean` + ## extendState - **详情:** 扩展 formState 的钩子函数,返回的对象会被合并到 formState 上 diff --git a/docs/api/form/submit-form.md b/docs/api/form/submit-form.md index 73cd3648..30258340 100644 --- a/docs/api/form/submit-form.md +++ b/docs/api/form/submit-form.md @@ -37,6 +37,7 @@ function submitForm(options: SubmitFormOptions): Promise; | `keyProp` | `string` | `'__key'` | 配置项的唯一 key | | `popperClass` | `string` | — | 弹层 className | | `preventSubmitDefault` | `boolean` | — | 是否阻止表单原生 submit | +| `useFieldTextInError` | `boolean` | `true` | 校验失败时错误提示前缀是否使用字段的 `text` 文案;`false` 时直接使用字段 `name` | | `extendState` | `(state: FormState) => Record \| Promise>` | — | 扩展 `formState` | | `native` | `boolean` | `false` | 透传给 `Form.submitForm`。`true` 时返回内部响应式 `values`,否则返回 `cloneDeep(toRaw(values))` | | `returnChangeRecords` | `boolean` | `false` | `true` 时 resolve 结果为 `{ values, changeRecords }`,携带表单变更记录;否则仅 resolve `values` | diff --git a/packages/form/src/Form.vue b/packages/form/src/Form.vue index a9b9e0c8..89a1193e 100644 --- a/packages/form/src/Form.vue +++ b/packages/form/src/Form.vue @@ -79,6 +79,13 @@ const props = withDefaults( keyProp?: string; popperClass?: string; preventSubmitDefault?: boolean; + /** + * 表单校验失败时,错误提示前缀是否使用字段的 text 文案(通过 `getTextByName` 从 config 中查找)。 + * + * - `true`(默认):错误提示形如 `字段文案 -> 错误信息`,找不到 text 时回退为字段 name; + * - `false`:跳过查找,直接使用字段 name 作为错误提示前缀(形如 `字段name -> 错误信息`)。 + */ + useFieldTextInError?: boolean; extendState?: (_state: FormState) => Record | Promise>; /** * 自定义"是否展示对比内容"的判断函数(仅在 `isCompare === true` 时生效)。 @@ -121,6 +128,7 @@ const props = withDefaults( inline: false, labelPosition: 'right', keyProp: '__key', + useFieldTextInError: true, }, ); @@ -384,7 +392,7 @@ defineExpose({ Object.entries(invalidFields).forEach(([prop, ValidateError]) => { (ValidateError as ValidateError[]).forEach(({ field, message }) => { const name = field || prop; - const text = getTextByName(name, props.config) || name; + const text = (props.useFieldTextInError ? getTextByName(name, props.config) : undefined) || name; error.push(`${text} -> ${message}`); }); diff --git a/packages/form/src/FormBox.vue b/packages/form/src/FormBox.vue index e9439d30..50f9cdfd 100644 --- a/packages/form/src/FormBox.vue +++ b/packages/form/src/FormBox.vue @@ -13,6 +13,7 @@ :label-position="labelPosition" :inline="inline" :prevent-submit-default="preventSubmitDefault" + :use-field-text-in-error="useFieldTextInError" :extend-state="extendState" @change="changeHandler" > @@ -61,12 +62,15 @@ const props = withDefaults( inline?: boolean; labelPosition?: string; preventSubmitDefault?: boolean; + /** 透传给内部 `MForm`,控制表单校验失败时错误提示前缀是否使用字段的 text 文案 */ + useFieldTextInError?: boolean; extendState?: (_state: FormState) => Record | Promise>; }>(), { config: () => [], values: () => ({}), confirmText: '确定', + useFieldTextInError: true, }, ); diff --git a/packages/form/src/FormDialog.vue b/packages/form/src/FormDialog.vue index 6bb81215..848f3c55 100644 --- a/packages/form/src/FormDialog.vue +++ b/packages/form/src/FormDialog.vue @@ -31,6 +31,7 @@ :label-position="labelPosition" :inline="inline" :prevent-submit-default="preventSubmitDefault" + :use-field-text-in-error="useFieldTextInError" :extend-state="extendState" @change="changeHandler" > @@ -96,6 +97,8 @@ const props = withDefaults( destroyOnClose?: boolean; showClose?: boolean; showCancel?: boolean; + /** 透传给内部 `MForm`,控制表单校验失败时错误提示前缀是否使用字段的 text 文案 */ + useFieldTextInError?: boolean; /** 透传给内部 `MForm`,用于扩展 `formState`(如注入 `$message` / `$store` 等) */ extendState?: (_state: FormState) => Record | Promise>; }>(), @@ -108,6 +111,7 @@ const props = withDefaults( destroyOnClose: false, showClose: true, showCancel: true, + useFieldTextInError: true, }, ); diff --git a/packages/form/src/FormDrawer.vue b/packages/form/src/FormDrawer.vue index 43b71bf6..71405061 100644 --- a/packages/form/src/FormDrawer.vue +++ b/packages/form/src/FormDrawer.vue @@ -28,6 +28,7 @@ :label-position="labelPosition" :inline="inline" :prevent-submit-default="preventSubmitDefault" + :use-field-text-in-error="useFieldTextInError" :extend-state="extendState" @change="changeHandler" > @@ -82,6 +83,8 @@ withDefaults( inline?: boolean; labelPosition?: string; preventSubmitDefault?: boolean; + /** 透传给内部 `MForm`,控制表单校验失败时错误提示前缀是否使用字段的 text 文案 */ + useFieldTextInError?: boolean; /** 关闭前的回调,会暂停 Drawer 的关闭; done 是个 function type 接受一个 boolean 参数, 执行 done 使用 true 参数或不提供参数将会终止关闭 */ beforeClose?: (_done: (_cancel?: boolean) => void) => void; /** 透传给内部 `MForm`,用于扩展 `formState`(如注入 `$message` / `$store` 等) */ @@ -92,6 +95,7 @@ withDefaults( config: () => [], values: () => ({}), confirmText: '确定', + useFieldTextInError: true, }, ); diff --git a/packages/form/src/submitForm.ts b/packages/form/src/submitForm.ts index 5ace5485..cafecc92 100644 --- a/packages/form/src/submitForm.ts +++ b/packages/form/src/submitForm.ts @@ -45,6 +45,11 @@ export interface SubmitFormOptions { keyProp?: string; popperClass?: string; preventSubmitDefault?: boolean; + /** + * 表单校验失败时,错误提示前缀是否使用字段的 text 文案(通过 `getTextByName` 从 config 中查找)。 + * 默认 `true`,置为 `false` 时直接使用字段 name。 + */ + useFieldTextInError?: boolean; extendState?: (_state: FormState) => Record | Promise>; /** 透传给 Form.submitForm 的参数:是否直接返回原始响应式 values */ native?: boolean; diff --git a/packages/form/tests/unit/Form.extra.spec.ts b/packages/form/tests/unit/Form.extra.spec.ts index b20223e9..0f617c63 100644 --- a/packages/form/tests/unit/Form.extra.spec.ts +++ b/packages/form/tests/unit/Form.extra.spec.ts @@ -340,6 +340,69 @@ describe('Form.vue —— submitForm 实例方法', () => { }); }); +describe('Form.vue —— useFieldTextInError', () => { + const mountAndMockValidate = async ( + props: Record, + invalidFields: Record, + ) => { + const wrapper = mountForm(props); + await nextTick(); + + const tmForm = wrapper.findComponent({ name: 'TMForm' }); + const { exposed } = (tmForm.vm as any).$; + exposed.validate = vi.fn().mockRejectedValue(invalidFields); + + let caught: Error | null = null; + try { + await wrapper.vm.submitForm(); + } catch (e: any) { + caught = e; + } + + return { wrapper, caught }; + }; + + test('默认(useFieldTextInError 未传)时错误信息使用 config 中的 text', async () => { + const { caught } = await mountAndMockValidate( + { + config: [{ text: '名称', type: 'text', name: 'name' }], + initValues: { name: '' }, + }, + { name: [{ field: 'name', message: '必填' }] }, + ); + + expect(caught!.message).toContain('名称'); + expect(caught!.message).not.toContain('name -> '); + }); + + test('useFieldTextInError=true 时错误信息使用 config 中的 text', async () => { + const { caught } = await mountAndMockValidate( + { + config: [{ text: '名称', type: 'text', name: 'name' }], + initValues: { name: '' }, + useFieldTextInError: true, + }, + { name: [{ field: 'name', message: '必填' }] }, + ); + + expect(caught!.message).toContain('名称 -> 必填'); + }); + + test('useFieldTextInError=false 时跳过查找,直接使用字段 name', async () => { + const { caught } = await mountAndMockValidate( + { + config: [{ text: '名称', type: 'text', name: 'name' }], + initValues: { name: '' }, + useFieldTextInError: false, + }, + { name: [{ field: 'name', message: '必填' }] }, + ); + + expect(caught!.message).toContain('name -> 必填'); + expect(caught!.message).not.toContain('名称'); + }); +}); + describe('Form.vue —— getTextByName', () => { let wrapper: ReturnType;