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>
This commit is contained in:
xingyixiang
2025-06-12 22:33:29 +08:00
committed by GitHub
parent 168b5f34ca
commit bb5930e8b7
6 changed files with 88 additions and 16 deletions

View File

@@ -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<void>` |
| validateField | Validate specified fields. | ^[Function]`(props?: Arrayable<FormItemProp> \| undefined, callback?: FormValidateCallback \| undefined) => FormValidationResult` |
| resetFields | Reset specified fields and remove validation result. | ^[Function]`(props?: Arrayable<FormItemProp> \| undefined) => void` |
| scrollToField | Scroll to the specified fields. | ^[Function]`(prop: FormItemProp) => void` |
| clearValidate | Clear validation messages for all or specified fields. | ^[Function]`(props?: Arrayable<FormItemProp> \| 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<void>` |
| validateField | Validate specified fields. | ^[Function]`(props?: Arrayable<FormItemProp> \| undefined, callback?: FormValidateCallback \| undefined) => FormValidationResult` |
| resetFields | Reset specified fields and remove validation result. | ^[Function]`(props?: Arrayable<FormItemProp> \| undefined) => void` |
| scrollToField | Scroll to the specified fields. | ^[Function]`(prop: FormItemProp) => void` |
| clearValidate | Clear validation messages for all or specified fields. | ^[Function]`(props?: Arrayable<FormItemProp> \| 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<typeof formItemProps>
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: (

View File

@@ -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 () => (
<Form ref="formRef" model={form}>
<FormItem
label="Name"
ref="name"
prop={['obj', 'name']}
rules={rules}
>
<Input v-model={form.obj.name} />
</FormItem>
</Form>
)
},
})
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()

View File

@@ -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<boolean>(() => {
@@ -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)

View File

@@ -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.
*/

View File

@@ -113,7 +113,7 @@ export interface FormValidateFailure {
export type FormContext = FormProps &
UnwrapRef<FormLabelWidthContext> & {
emit: SetupContext<FormEmits>['emit']
getField: (prop: string) => FormItemContext | undefined
getField: (prop: FormItemProp) => FormItemContext | undefined
addField: (field: FormItemContext) => void
removeField: (field: FormItemContext) => void
resetFields: (props?: Arrayable<FormItemProp>) => 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: (

View File

@@ -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<FormItemProp>
) => {
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
}