feat: improved formApi for component instance support
* 改进表单API以支持组件实例的获取,以及焦点字段的获取
This commit is contained in:
parent
cfa18c2b8e
commit
04dff33ac5
@ -279,22 +279,24 @@ const [Form, formApi] = useVbenForm({
|
|||||||
|
|
||||||
useVbenForm 返回的第二个参数,是一个对象,包含了一些表单的方法。
|
useVbenForm 返回的第二个参数,是一个对象,包含了一些表单的方法。
|
||||||
|
|
||||||
| 方法名 | 描述 | 类型 |
|
| 方法名 | 描述 | 类型 | 版本号 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| submitForm | 提交表单 | `(e:Event)=>Promise<Record<string,any>>` |
|
| submitForm | 提交表单 | `(e:Event)=>Promise<Record<string,any>>` | - |
|
||||||
| validateAndSubmitForm | 提交并校验表单 | `(e:Event)=>Promise<Record<string,any>>` |
|
| validateAndSubmitForm | 提交并校验表单 | `(e:Event)=>Promise<Record<string,any>>` | - |
|
||||||
| resetForm | 重置表单 | `()=>Promise<void>` |
|
| resetForm | 重置表单 | `()=>Promise<void>` | - |
|
||||||
| setValues | 设置表单值, 默认会过滤不在schema中定义的field, 可通过filterFields形参关闭过滤 | `(fields: Record<string, any>, filterFields?: boolean, shouldValidate?: boolean) => Promise<void>` |
|
| setValues | 设置表单值, 默认会过滤不在schema中定义的field, 可通过filterFields形参关闭过滤 | `(fields: Record<string, any>, filterFields?: boolean, shouldValidate?: boolean) => Promise<void>` | - |
|
||||||
| getValues | 获取表单值 | `(fields:Record<string, any>,shouldValidate: boolean = false)=>Promise<void>` |
|
| getValues | 获取表单值 | `(fields:Record<string, any>,shouldValidate: boolean = false)=>Promise<void>` | - |
|
||||||
| validate | 表单校验 | `()=>Promise<void>` |
|
| validate | 表单校验 | `()=>Promise<void>` | - |
|
||||||
| validateField | 校验指定字段 | `(fieldName: string)=>Promise<ValidationResult<unknown>>` |
|
| validateField | 校验指定字段 | `(fieldName: string)=>Promise<ValidationResult<unknown>>` | - |
|
||||||
| isFieldValid | 检查某个字段是否已通过校验 | `(fieldName: string)=>Promise<boolean>` |
|
| isFieldValid | 检查某个字段是否已通过校验 | `(fieldName: string)=>Promise<boolean>` | - |
|
||||||
| resetValidate | 重置表单校验 | `()=>Promise<void>` |
|
| resetValidate | 重置表单校验 | `()=>Promise<void>` | - |
|
||||||
| updateSchema | 更新formSchema | `(schema:FormSchema[])=>void` |
|
| updateSchema | 更新formSchema | `(schema:FormSchema[])=>void` | - |
|
||||||
| setFieldValue | 设置字段值 | `(field: string, value: any, shouldValidate?: boolean)=>Promise<void>` |
|
| setFieldValue | 设置字段值 | `(field: string, value: any, shouldValidate?: boolean)=>Promise<void>` | - |
|
||||||
| setState | 设置组件状态(props) | `(stateOrFn:\| ((prev: VbenFormProps) => Partial<VbenFormProps>)\| Partial<VbenFormProps>)=>Promise<void>` |
|
| setState | 设置组件状态(props) | `(stateOrFn:\| ((prev: VbenFormProps) => Partial<VbenFormProps>)\| Partial<VbenFormProps>)=>Promise<void>` | - |
|
||||||
| getState | 获取组件状态(props) | `()=>Promise<VbenFormProps>` |
|
| getState | 获取组件状态(props) | `()=>Promise<VbenFormProps>` | - |
|
||||||
| form | 表单对象实例,可以操作表单,见 [useForm](https://vee-validate.logaretm.com/v4/api/use-form/) | - |
|
| form | 表单对象实例,可以操作表单,见 [useForm](https://vee-validate.logaretm.com/v4/api/use-form/) | - | - |
|
||||||
|
| getFieldComponentRef | 获取指定字段的组件实例 | `<T=unknown>(fieldName: string)=>T` | >5.5.3 |
|
||||||
|
| getFocusedField | 获取当前已获得焦点的字段 | `()=>string\|undefined` | >5.5.3 |
|
||||||
|
|
||||||
## Props
|
## Props
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ import type {
|
|||||||
ValidationOptions,
|
ValidationOptions,
|
||||||
} from 'vee-validate';
|
} from 'vee-validate';
|
||||||
|
|
||||||
|
import type { ComponentPublicInstance } from 'vue';
|
||||||
|
|
||||||
import type { Recordable } from '@vben-core/typings';
|
import type { Recordable } from '@vben-core/typings';
|
||||||
|
|
||||||
import type { FormActions, FormSchema, VbenFormProps } from './types';
|
import type { FormActions, FormSchema, VbenFormProps } from './types';
|
||||||
@ -56,6 +58,11 @@ export class FormApi {
|
|||||||
|
|
||||||
public store: Store<VbenFormProps>;
|
public store: Store<VbenFormProps>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件实例映射
|
||||||
|
*/
|
||||||
|
private componentRefMap: Map<string, unknown> = new Map();
|
||||||
|
|
||||||
// 最后一次点击提交时的表单值
|
// 最后一次点击提交时的表单值
|
||||||
private latestSubmissionValues: null | Recordable<any> = null;
|
private latestSubmissionValues: null | Recordable<any> = null;
|
||||||
|
|
||||||
@ -85,6 +92,46 @@ export class FormApi {
|
|||||||
bindMethods(this);
|
bindMethods(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字段组件实例
|
||||||
|
* @param fieldName 字段名
|
||||||
|
* @returns 组件实例
|
||||||
|
*/
|
||||||
|
getFieldComponentRef<T = ComponentPublicInstance>(
|
||||||
|
fieldName: string,
|
||||||
|
): T | undefined {
|
||||||
|
return this.componentRefMap.has(fieldName)
|
||||||
|
? (this.componentRefMap.get(fieldName) as T)
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前聚焦的字段,如果没有聚焦的字段则返回undefined
|
||||||
|
*/
|
||||||
|
getFocusedField() {
|
||||||
|
for (const fieldName of this.componentRefMap.keys()) {
|
||||||
|
const ref = this.getFieldComponentRef(fieldName);
|
||||||
|
if (ref) {
|
||||||
|
let el: HTMLElement | null = null;
|
||||||
|
if (ref instanceof HTMLElement) {
|
||||||
|
el = ref;
|
||||||
|
} else if (ref.$el instanceof HTMLElement) {
|
||||||
|
el = ref.$el;
|
||||||
|
}
|
||||||
|
if (!el) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
el === document.activeElement ||
|
||||||
|
el.contains(document.activeElement)
|
||||||
|
) {
|
||||||
|
return fieldName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
getLatestSubmissionValues() {
|
getLatestSubmissionValues() {
|
||||||
return this.latestSubmissionValues || {};
|
return this.latestSubmissionValues || {};
|
||||||
}
|
}
|
||||||
@ -143,13 +190,14 @@ export class FormApi {
|
|||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
mount(formActions: FormActions) {
|
mount(formActions: FormActions, componentRefMap: Map<string, unknown>) {
|
||||||
if (!this.isMounted) {
|
if (!this.isMounted) {
|
||||||
Object.assign(this.form, formActions);
|
Object.assign(this.form, formActions);
|
||||||
this.stateHandler.setConditionTrue();
|
this.stateHandler.setConditionTrue();
|
||||||
this.setLatestSubmissionValues({
|
this.setLatestSubmissionValues({
|
||||||
...toRaw(this.handleRangeTimeValue(this.form.values)),
|
...toRaw(this.handleRangeTimeValue(this.form.values)),
|
||||||
});
|
});
|
||||||
|
this.componentRefMap = componentRefMap;
|
||||||
this.isMounted = true;
|
this.isMounted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import type { ZodType } from 'zod';
|
|||||||
|
|
||||||
import type { FormSchema, MaybeComponentProps } from '../types';
|
import type { FormSchema, MaybeComponentProps } from '../types';
|
||||||
|
|
||||||
import { computed, nextTick, useTemplateRef, watch } from 'vue';
|
import { computed, nextTick, onUnmounted, useTemplateRef, watch } from 'vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
@ -18,6 +18,7 @@ import { cn, isFunction, isObject, isString } from '@vben-core/shared/utils';
|
|||||||
import { toTypedSchema } from '@vee-validate/zod';
|
import { toTypedSchema } from '@vee-validate/zod';
|
||||||
import { useFieldError, useFormValues } from 'vee-validate';
|
import { useFieldError, useFormValues } from 'vee-validate';
|
||||||
|
|
||||||
|
import { injectComponentRefMap } from '../use-form-context';
|
||||||
import { injectRenderFormProps, useFormContext } from './context';
|
import { injectRenderFormProps, useFormContext } from './context';
|
||||||
import useDependencies from './dependencies';
|
import useDependencies from './dependencies';
|
||||||
import FormLabel from './form-label.vue';
|
import FormLabel from './form-label.vue';
|
||||||
@ -267,6 +268,15 @@ function autofocus() {
|
|||||||
fieldComponentRef.value?.focus?.();
|
fieldComponentRef.value?.focus?.();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const componentRefMap = injectComponentRefMap();
|
||||||
|
watch(fieldComponentRef, (componentRef) => {
|
||||||
|
componentRefMap?.set(fieldName, componentRef);
|
||||||
|
});
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (componentRefMap?.has(fieldName)) {
|
||||||
|
componentRefMap.delete(fieldName);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -20,6 +20,9 @@ export const [injectFormProps, provideFormProps] =
|
|||||||
'VbenFormProps',
|
'VbenFormProps',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const [injectComponentRefMap, provideComponentRefMap] =
|
||||||
|
createContext<Map<string, unknown>>('ComponentRefMap');
|
||||||
|
|
||||||
export function useFormInitial(
|
export function useFormInitial(
|
||||||
props: ComputedRef<VbenFormProps> | VbenFormProps,
|
props: ComputedRef<VbenFormProps> | VbenFormProps,
|
||||||
) {
|
) {
|
||||||
|
@ -17,7 +17,11 @@ import {
|
|||||||
DEFAULT_FORM_COMMON_CONFIG,
|
DEFAULT_FORM_COMMON_CONFIG,
|
||||||
} from './config';
|
} from './config';
|
||||||
import { Form } from './form-render';
|
import { Form } from './form-render';
|
||||||
import { provideFormProps, useFormInitial } from './use-form-context';
|
import {
|
||||||
|
provideComponentRefMap,
|
||||||
|
provideFormProps,
|
||||||
|
useFormInitial,
|
||||||
|
} from './use-form-context';
|
||||||
// 通过 extends 会导致热更新卡死,所以重复写了一遍
|
// 通过 extends 会导致热更新卡死,所以重复写了一遍
|
||||||
interface Props extends VbenFormProps {
|
interface Props extends VbenFormProps {
|
||||||
formApi: ExtendedFormApi;
|
formApi: ExtendedFormApi;
|
||||||
@ -29,11 +33,14 @@ const state = props.formApi?.useStore?.();
|
|||||||
|
|
||||||
const forward = useForwardPriorityValues(props, state);
|
const forward = useForwardPriorityValues(props, state);
|
||||||
|
|
||||||
|
const componentRefMap = new Map<string, unknown>();
|
||||||
|
|
||||||
const { delegatedSlots, form } = useFormInitial(forward);
|
const { delegatedSlots, form } = useFormInitial(forward);
|
||||||
|
|
||||||
provideFormProps([forward, form]);
|
provideFormProps([forward, form]);
|
||||||
|
provideComponentRefMap(componentRefMap);
|
||||||
|
|
||||||
props.formApi?.mount?.(form);
|
props.formApi?.mount?.(form, componentRefMap);
|
||||||
|
|
||||||
const handleUpdateCollapsed = (value: boolean) => {
|
const handleUpdateCollapsed = (value: boolean) => {
|
||||||
props.formApi?.setState({ collapsed: !!value });
|
props.formApi?.setState({ collapsed: !!value });
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { VbenFormSchema } from '@vben/common-ui';
|
import type { VbenFormSchema } from '@vben/common-ui';
|
||||||
import type { BasicOption } from '@vben/types';
|
import type { BasicOption, Recordable } from '@vben/types';
|
||||||
|
|
||||||
import { computed, markRaw } from 'vue';
|
import { computed, markRaw, useTemplateRef } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';
|
import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
@ -104,12 +104,28 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const loginRef =
|
||||||
|
useTemplateRef<InstanceType<typeof AuthenticationLogin>>('loginRef');
|
||||||
|
|
||||||
|
async function onSubmit(params: Recordable<any>) {
|
||||||
|
authStore.authLogin(params).catch(() => {
|
||||||
|
// 登陆失败,刷新验证码的演示
|
||||||
|
|
||||||
|
// 使用表单API获取验证码组件实例,并调用其resume方法来重置验证码
|
||||||
|
loginRef.value
|
||||||
|
?.getFormApi()
|
||||||
|
?.getFieldComponentRef<InstanceType<typeof SliderCaptcha>>('captcha')
|
||||||
|
?.resume();
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationLogin
|
<AuthenticationLogin
|
||||||
|
ref="loginRef"
|
||||||
:form-schema="formSchema"
|
:form-schema="formSchema"
|
||||||
:loading="authStore.loginLoading"
|
:loading="authStore.loginLoading"
|
||||||
@submit="authStore.authLogin"
|
@submit="onSubmit"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { RefSelectProps } from 'ant-design-vue/es/select';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
@ -82,6 +84,7 @@ function handleClick(
|
|||||||
action:
|
action:
|
||||||
| 'batchAddSchema'
|
| 'batchAddSchema'
|
||||||
| 'batchDeleteSchema'
|
| 'batchDeleteSchema'
|
||||||
|
| 'componentRef'
|
||||||
| 'disabled'
|
| 'disabled'
|
||||||
| 'hiddenAction'
|
| 'hiddenAction'
|
||||||
| 'hiddenResetButton'
|
| 'hiddenResetButton'
|
||||||
@ -129,6 +132,11 @@ function handleClick(
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'componentRef': {
|
||||||
|
// 获取下拉组件的实例,并调用它的focus方法
|
||||||
|
formApi.getFieldComponentRef<RefSelectProps>('fieldOptions')?.focus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'disabled': {
|
case 'disabled': {
|
||||||
formApi.setState({ commonConfig: { disabled: true } });
|
formApi.setState({ commonConfig: { disabled: true } });
|
||||||
break;
|
break;
|
||||||
@ -182,6 +190,7 @@ function handleClick(
|
|||||||
formApi.setState({ submitButtonOptions: { show: true } });
|
formApi.setState({ submitButtonOptions: { show: true } });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'updateActionAlign': {
|
case 'updateActionAlign': {
|
||||||
formApi.setState({
|
formApi.setState({
|
||||||
// 可以自行调整class
|
// 可以自行调整class
|
||||||
@ -189,7 +198,6 @@ function handleClick(
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'updateResetButton': {
|
case 'updateResetButton': {
|
||||||
formApi.setState({
|
formApi.setState({
|
||||||
resetButtonOptions: { disabled: true },
|
resetButtonOptions: { disabled: true },
|
||||||
@ -257,6 +265,7 @@ function handleClick(
|
|||||||
<Button @click="handleClick('batchDeleteSchema')">
|
<Button @click="handleClick('batchDeleteSchema')">
|
||||||
批量删除表单项
|
批量删除表单项
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button @click="handleClick('componentRef')">下拉组件获取焦点</Button>
|
||||||
</Space>
|
</Space>
|
||||||
<Card title="操作示例">
|
<Card title="操作示例">
|
||||||
<BaseForm />
|
<BaseForm />
|
||||||
|
Loading…
Reference in New Issue
Block a user