import { type InjectionKey, inject, provide, type Ref, computed, ref, watch, type ComputedRef } from "vue"; import type { FieldProps } from "./Field.vue"; import type { RuleItem } from "async-validator"; import { useCellContext } from "../basic/CellContext"; /** * 校验触发时机 * - blur: 失去焦点时触发校验 * - change: 值改变时触发校验 * - submit: 提交表单时触发校验 */ export type ValidTrigger = "blur" | "change" | "submit"; /** * 表单项上下文 */ export type FormItemContext = { errorState: ComputedRef, getFieldName: () => string, /** * 触发表单条目获得焦点事件 */ onFieldFocus: () => void; /** * 触发表单条目失去焦点事件 */ onFieldBlur: () => void; /** * 触发表单条目值改变事件 * @param newValue 新值 */ onFieldChange: (newValue: unknown) => void; /** * 清除表单条目校验状态 */ clearValidate: () => void; /** * 设置表单条目点击事件监听器,设置后表单项允许点击,点击后会触发点击事件。 * @param listener 点击事件监听器 */ setOnClickListener: (listener: (() => void)|undefined) => void; /** * 获取表单组件中的当前值 * @returns 表单组件中的当前值 */ getFormModelValue(): any; /** * 表单项是否禁用的状态 */ disabled: Ref, /** * 表单项是否只读的状态 */ readonly: Ref, }; export type FormItemInternalContext = { /** * 获取表单组件的实例引用,如果没有子组件,则返回 Field 自身引用。 * * 本函数专用于动态表单,直接使用的情况可以在模板中绑定ref获取实例引用。 * * 只有 Field 组件设置了 requireChildRef 回调才能返回子组件实例引用,否则只会返回 Field 组件实例引用。 * @returns */ getExpectedRef: () => any, getItemRules: () => RuleItem[], getFieldName: () => string, getValidateTrigger: () => ValidTrigger; getUniqueId: () => string, setErrorState: (errorMessage: string|null) => void; setBlurState(): void; }; export type FormContext = { //由表单项组件调用 onFieldFocus: (item: FormItemInternalContext) => void; onFieldBlur: (item: FormItemInternalContext) => void; onFieldChange: (item: FormItemInternalContext, newValue: unknown) => void; clearValidate: (item: FormItemInternalContext) => void; addFormItemField: (item: FormItemInternalContext) => number; removeFormItemField: (item: FormItemInternalContext) => void; //form props validateTrigger: Ref; addRequireMark: Ref; colon: Ref; labelWidth: Ref; labelAlign: Ref<"left"|"center"|"right"|undefined>; labelPosition: Ref<'top'|'left'|undefined>; labelFlex: Ref; inputFlex: Ref; showLabel: Ref; name: Ref; fieldProps: Ref, disabled: Ref, readonly: Ref, getItemValue: (item: FormItemInternalContext) => unknown; getItemRequieed: (item: FormItemInternalContext) => boolean; }; /** * 用于Props默认值回调中获取表单上下文 * @returns FormContext */ export function propGetFormContext() { return inject('formContext', null as any); } export const FormItemContextContextKey: InjectionKey = Symbol('FormItemContext'); /** * 用于注入表单项上下文 * @returns FormItemContext */ export function useInjectFormItemContext() : FormItemContext { const context = inject(FormItemContextContextKey, null as any); provide(FormItemContextContextKey, null as any as FormItemContext); return context as FormItemContext; } /** * 用于注入表单上下文 * @returns FormContext */ export function useInjectFormContext() : FormContext { return inject('formContext', null as any); } /** * 用于注入表单项子组件值,用于实现表单项值的双向绑定。 * * 组件可以通过返回的 `value` 属性获取当前值,通过 `updateValue` 方法更新值, * 即使外部未绑定 `modelValue` 属性,也可以正常工作。 * * ```ts * const { value, updateValue, } = useFieldChildValueInjector( toRef(props, 'modelValue'), (v) => emit('update:modelValue', v) ); * ``` * @param propsModelValue 组件外部传入的modelValue * @param emit 组件外部的emit * @param secondParentContext 二级父组件上下文,用于更新二级父组件的值。 * @param fieldClick 表单项点击事件监听器,设置后表单项允许点击,点击后会触发点击事件。 * @param initialValue 初始值 * @returns */ export function useFieldChildValueInjector( propsModelValue: Ref, emit: (v: T) => void, secondParentContext?: { getValue: () => T, updateValue: (v: T) => void, }, fieldClick?: () => void, initialValue?: T, ) { const cellContext = useCellContext(); const context = useInjectFormItemContext(); const formContext = useInjectFormContext(); const shadowRefValue = ref(propsModelValue.value ?? context.getFormModelValue() ?? initialValue) as Ref; const value = computed(() => { if (secondParentContext) shadowRefValue.value = secondParentContext.getValue(); return shadowRefValue.value }); watch(() => propsModelValue.value, (v) => { shadowRefValue.value = v; }) /** * 更新表单项值 * @param newValue 新值 */ function updateValue(newValue: T) { if (secondParentContext) secondParentContext.updateValue(newValue); else emit(newValue); shadowRefValue.value = newValue; context?.onFieldChange(newValue); } if (fieldClick) { if (context) context.setOnClickListener(fieldClick); else if (cellContext) cellContext.setOnClickListener(fieldClick); } const disabled = computed(() => formContext.disabled.value || context.disabled.value); const readonly = computed(() => formContext.readonly.value || context.readonly.value); return { /** * 临时值 */ value: value, updateValue, /** * 表单项上下文 */ context, /** * 表单上下文 */ formContext, /** * 指示顶层由表单和表单项设置的禁用状态 */ disabled, /** * 指示顶层由表单和表单项设置的只读状态 */ readonly, } }