From bb5930e8b719a0d3d0c20f71fdbf970ea357ca4b Mon Sep 17 00:00:00 2001 From: xingyixiang <452282988@qq.com> Date: Thu, 12 Jun 2025 22:33:29 +0800 Subject: [PATCH] feat(components): [form] export getField (#20981) * feat(components): [form] export getField * fix(components): [form] test * feat(components): [form] export getField * Update packages/components/form/src/utils.ts Co-authored-by: btea <2356281422@qq.com> * feat(components): [form] export getField * feat(components): [form] export getField --------- Co-authored-by: btea <2356281422@qq.com> --- docs/en-US/component/form.md | 19 +++--- .../components/form/__tests__/form.test.tsx | 58 +++++++++++++++++++ packages/components/form/src/form-item.vue | 5 +- packages/components/form/src/form.vue | 8 ++- packages/components/form/src/types.ts | 4 +- packages/components/form/src/utils.ts | 10 +++- 6 files changed, 88 insertions(+), 16 deletions(-) diff --git a/docs/en-US/component/form.md b/docs/en-US/component/form.md index 61dfde4094..db0067d35c 100644 --- a/docs/en-US/component/form.md +++ b/docs/en-US/component/form.md @@ -160,14 +160,15 @@ form/accessibility ### Form Exposes -| Name | Description | Type | -| --------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | -| validate | Validate the whole form. Receives a callback or returns `Promise`. | ^[Function]`(callback?: FormValidateCallback) => Promise` | -| validateField | Validate specified fields. | ^[Function]`(props?: Arrayable \| undefined, callback?: FormValidateCallback \| undefined) => FormValidationResult` | -| resetFields | Reset specified fields and remove validation result. | ^[Function]`(props?: Arrayable \| undefined) => void` | -| scrollToField | Scroll to the specified fields. | ^[Function]`(prop: FormItemProp) => void` | -| clearValidate | Clear validation messages for all or specified fields. | ^[Function]`(props?: Arrayable \| undefined) => void` | -| fields ^(2.7.3) | Get all fields context. | ^[array]`FormItemContext[]` | +| Name | Description | Type | +| ------------------ | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | +| validate | Validate the whole form. Receives a callback or returns `Promise`. | ^[Function]`(callback?: FormValidateCallback) => Promise` | +| validateField | Validate specified fields. | ^[Function]`(props?: Arrayable \| undefined, callback?: FormValidateCallback \| undefined) => FormValidationResult` | +| resetFields | Reset specified fields and remove validation result. | ^[Function]`(props?: Arrayable \| undefined) => void` | +| scrollToField | Scroll to the specified fields. | ^[Function]`(prop: FormItemProp) => void` | +| clearValidate | Clear validation messages for all or specified fields. | ^[Function]`(props?: Arrayable \| undefined) => void` | +| fields ^(2.7.3) | Get all fields context. | ^[array]`FormItemContext[]` | +| getField ^(2.10.2) | Get a field context. | ^[Function]`(prop: FormItemProp) => FormItemContext \| undefined` | ## FormItem API @@ -279,12 +280,14 @@ type FormItemProps = ExtractPropTypes type FormItemContext = FormItemProps & { $el: HTMLDivElement | undefined size: ComponentSize + validateMessage: string validateState: FormItemValidateState isGroup: boolean labelId: string inputIds: string[] hasLabel: boolean fieldValue: any + propString: string addInputId: (id: string) => void removeInputId: (id: string) => void validate: ( diff --git a/packages/components/form/__tests__/form.test.tsx b/packages/components/form/__tests__/form.test.tsx index d5eaf28848..7727df1d6b 100644 --- a/packages/components/form/__tests__/form.test.tsx +++ b/packages/components/form/__tests__/form.test.tsx @@ -850,6 +850,64 @@ describe('Form', () => { expect(fn).toHaveBeenCalledTimes(1) }) + it('prop is array', async () => { + vi.useFakeTimers() + const wrapper = mount({ + setup() { + const form = reactive({ + obj: { name: '' }, + }) + const rules = { + required: true, + message: 'Please input name', + trigger: 'blur', + } + return () => ( +
+ + + +
+ ) + }, + }) + + await nextTick() + + const formRef = wrapper.vm.$refs.formRef as FormInstance + const valid = await formRef + .validateField([['obj', 'name']]) + .then(() => true) + .catch(() => false) + expect(valid).toEqual(false) + vi.runAllTimers() + await nextTick() + expect(wrapper.find('.is-error').exists()).toBe(true) + expect(wrapper.find('.el-form-item__error').text()).toBe( + 'Please input name' + ) + + formRef.resetFields([['obj', 'name']]) + await nextTick() + vi.runAllTimers() + await nextTick() + expect(wrapper.find('.is-error').exists()).toBe(false) + + const field = formRef.getField(['obj', 'name']) + field.validateState = 'error' + field.validateMessage = 'name is error' + vi.runAllTimers() + await nextTick() + expect(wrapper.find('.is-error').exists()).toBe(true) + expect(wrapper.find('.el-form-item__error').text()).toBe('name is error') + vi.useRealTimers() + }) + describe('FormItem', () => { const onSuccess = vi.fn() const onError = vi.fn() diff --git a/packages/components/form/src/form-item.vue b/packages/components/form/src/form-item.vue index 4bf524ef6c..dcd5bc1028 100644 --- a/packages/components/form/src/form-item.vue +++ b/packages/components/form/src/form-item.vue @@ -60,7 +60,6 @@ import { isArray, isBoolean, isFunction, - isString, } from '@element-plus/utils' import { useId, useNamespace } from '@element-plus/hooks' import { useFormSize } from './hooks' @@ -159,7 +158,7 @@ const validateClasses = computed(() => [ const propString = computed(() => { if (!props.prop) return '' - return isString(props.prop) ? props.prop : props.prop.join('.') + return isArray(props.prop) ? props.prop.join('.') : props.prop }) const hasLabel = computed(() => { @@ -382,6 +381,7 @@ const context: FormItemContext = reactive({ ...toRefs(props), $el: formItemRef, size: _size, + validateMessage, validateState, labelId, inputIds, @@ -393,6 +393,7 @@ const context: FormItemContext = reactive({ resetField, clearValidate, validate, + propString, }) provide(formItemContextKey, context) diff --git a/packages/components/form/src/form.vue b/packages/components/form/src/form.vue index e3ee645a17..3c3ab6e7f4 100644 --- a/packages/components/form/src/form.vue +++ b/packages/components/form/src/form.vue @@ -48,7 +48,7 @@ const formClasses = computed(() => { }) const getField: FormContext['getField'] = (prop) => { - return fields.find((field) => field.prop === prop) + return filterFields(fields, [prop])[0] } const addField: FormContext['addField'] = (field) => { @@ -155,7 +155,7 @@ const validateField: FormContext['validateField'] = async ( } const scrollToField = (prop: FormItemProp) => { - const field = filterFields(fields, prop)[0] + const field = getField(prop) if (field) { field.$el?.scrollIntoView(props.scrollIntoViewOptions) } @@ -209,6 +209,10 @@ defineExpose({ * @description Scroll to the specified fields. */ scrollToField, + /** + * @description Get a field context. + */ + getField, /** * @description All fields context. */ diff --git a/packages/components/form/src/types.ts b/packages/components/form/src/types.ts index c1c45e9e86..7000722cb2 100644 --- a/packages/components/form/src/types.ts +++ b/packages/components/form/src/types.ts @@ -113,7 +113,7 @@ export interface FormValidateFailure { export type FormContext = FormProps & UnwrapRef & { emit: SetupContext['emit'] - getField: (prop: string) => FormItemContext | undefined + getField: (prop: FormItemProp) => FormItemContext | undefined addField: (field: FormItemContext) => void removeField: (field: FormItemContext) => void resetFields: (props?: Arrayable) => void @@ -127,12 +127,14 @@ export type FormContext = FormProps & export interface FormItemContext extends FormItemProps { $el: HTMLDivElement | undefined size: ComponentSize + validateMessage: string validateState: FormItemValidateState isGroup: boolean labelId: string inputIds: string[] hasLabel: boolean fieldValue: any + propString: string addInputId: (id: string) => void removeInputId: (id: string) => void validate: ( diff --git a/packages/components/form/src/utils.ts b/packages/components/form/src/utils.ts index 4f0e8a60d3..ce55ef714c 100644 --- a/packages/components/form/src/utils.ts +++ b/packages/components/form/src/utils.ts @@ -1,5 +1,5 @@ import { computed, ref } from 'vue' -import { debugWarn, ensureArray } from '@element-plus/utils' +import { debugWarn, ensureArray, isArray } from '@element-plus/utils' import type { Arrayable } from '@element-plus/utils' import type { FormItemContext } from './types' import type { FormItemProp } from './form-item' @@ -50,8 +50,12 @@ export const filterFields = ( fields: FormItemContext[], props: Arrayable ) => { - const normalized = ensureArray(props) + const normalized = ensureArray(props).map((prop) => + isArray(prop) ? prop.join('.') : prop + ) return normalized.length > 0 - ? fields.filter((field) => field.prop && normalized.includes(field.prop)) + ? fields.filter( + (field) => field.propString && normalized.includes(field.propString) + ) : fields }