feat: improved formApi for component instance support

* 改进表单API以支持组件实例的获取,以及焦点字段的获取
This commit is contained in:
Netfan
2025-03-10 02:24:58 +08:00
parent cfa18c2b8e
commit 04dff33ac5
7 changed files with 119 additions and 24 deletions

View File

@@ -5,6 +5,8 @@ import type {
ValidationOptions,
} from 'vee-validate';
import type { ComponentPublicInstance } from 'vue';
import type { Recordable } from '@vben-core/typings';
import type { FormActions, FormSchema, VbenFormProps } from './types';
@@ -56,6 +58,11 @@ export class FormApi {
public store: Store<VbenFormProps>;
/**
* 组件实例映射
*/
private componentRefMap: Map<string, unknown> = new Map();
// 最后一次点击提交时的表单值
private latestSubmissionValues: null | Recordable<any> = null;
@@ -85,6 +92,46 @@ export class FormApi {
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() {
return this.latestSubmissionValues || {};
}
@@ -143,13 +190,14 @@ export class FormApi {
return proxy;
}
mount(formActions: FormActions) {
mount(formActions: FormActions, componentRefMap: Map<string, unknown>) {
if (!this.isMounted) {
Object.assign(this.form, formActions);
this.stateHandler.setConditionTrue();
this.setLatestSubmissionValues({
...toRaw(this.handleRangeTimeValue(this.form.values)),
});
this.componentRefMap = componentRefMap;
this.isMounted = true;
}
}

View File

@@ -3,7 +3,7 @@ import type { ZodType } from 'zod';
import type { FormSchema, MaybeComponentProps } from '../types';
import { computed, nextTick, useTemplateRef, watch } from 'vue';
import { computed, nextTick, onUnmounted, useTemplateRef, watch } from 'vue';
import {
FormControl,
@@ -18,6 +18,7 @@ import { cn, isFunction, isObject, isString } from '@vben-core/shared/utils';
import { toTypedSchema } from '@vee-validate/zod';
import { useFieldError, useFormValues } from 'vee-validate';
import { injectComponentRefMap } from '../use-form-context';
import { injectRenderFormProps, useFormContext } from './context';
import useDependencies from './dependencies';
import FormLabel from './form-label.vue';
@@ -267,6 +268,15 @@ function autofocus() {
fieldComponentRef.value?.focus?.();
}
}
const componentRefMap = injectComponentRefMap();
watch(fieldComponentRef, (componentRef) => {
componentRefMap?.set(fieldName, componentRef);
});
onUnmounted(() => {
if (componentRefMap?.has(fieldName)) {
componentRefMap.delete(fieldName);
}
});
</script>
<template>

View File

@@ -20,6 +20,9 @@ export const [injectFormProps, provideFormProps] =
'VbenFormProps',
);
export const [injectComponentRefMap, provideComponentRefMap] =
createContext<Map<string, unknown>>('ComponentRefMap');
export function useFormInitial(
props: ComputedRef<VbenFormProps> | VbenFormProps,
) {

View File

@@ -17,7 +17,11 @@ import {
DEFAULT_FORM_COMMON_CONFIG,
} from './config';
import { Form } from './form-render';
import { provideFormProps, useFormInitial } from './use-form-context';
import {
provideComponentRefMap,
provideFormProps,
useFormInitial,
} from './use-form-context';
// 通过 extends 会导致热更新卡死,所以重复写了一遍
interface Props extends VbenFormProps {
formApi: ExtendedFormApi;
@@ -29,11 +33,14 @@ const state = props.formApi?.useStore?.();
const forward = useForwardPriorityValues(props, state);
const componentRefMap = new Map<string, unknown>();
const { delegatedSlots, form } = useFormInitial(forward);
provideFormProps([forward, form]);
provideComponentRefMap(componentRefMap);
props.formApi?.mount?.(form);
props.formApi?.mount?.(form, componentRefMap);
const handleUpdateCollapsed = (value: boolean) => {
props.formApi?.setState({ collapsed: !!value });