Browse Source

Merge ab12002d11 into d4b2b02c54

pull/8351/merge
xueyitt 4 weeks ago
committed by GitHub
parent
commit
9b418d5dfc
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 3
      packages/@core/ui-kit/form-ui/CHANGELOG.md
  2. 36
      packages/@core/ui-kit/form-ui/__tests__/form-api.test.ts
  3. 13
      packages/@core/ui-kit/form-ui/src/form-api.ts
  4. 73
      packages/@core/ui-kit/form-ui/src/form-default-values.ts
  5. 58
      packages/@core/ui-kit/form-ui/src/use-form-context.ts

3
packages/@core/ui-kit/form-ui/CHANGELOG.md

@ -4,6 +4,9 @@
### Patch Changes
- fix(@vben-core/form-ui): 修复动态表单定义下重置时恢复到挂载时固化的旧默认值的问题。表单定义动态变化后(如抽屉中先编辑再新增),部分依赖定义默认值的字段初始值会丢失;现在 `reset()` 未显式指定重置值时,会按当前表单定义动态计算默认值,并同步校正底层默认值快照
- refactor(@vben-core/form-ui): 将表单定义默认值的计算逻辑抽取为公共函数,挂载初始化与重置共用
- [#7978](https://github.com/vbenjs/vue-vben-admin/pull/7978) [`9ffd42f`](https://github.com/vbenjs/vue-vben-admin/commit/9ffd42f013825f94278165027bc210a5314d3998) Thanks [@SaleriHQ](https://github.com/SaleriHQ)! - feat(@core/form-ui): 新增 useVbenForm 数组编辑器 VbenFormFieldArray
- Updated dependencies [[`142b544`](https://github.com/vbenjs/vue-vben-admin/commit/142b5442c2270090720a92671a0573cfe6974fa3)]:

36
packages/@core/ui-kit/form-ui/__tests__/form-api.test.ts

@ -50,6 +50,42 @@ describe('formApi', () => {
expect(formApi.getFieldComponentRef('name')).toBeUndefined();
});
it('should reset to current schema default values', async () => {
const reset = vi.fn();
const formActions: any = {
meta: {},
reset,
values: {},
};
await formApi.mount(formActions, new Map());
// 动态切换表单定义后重置,应按当前定义的默认值恢复(而非挂载时固化的旧默认值)
formApi.setState({
schema: [
{ component: 'input', defaultValue: '默认名称', fieldName: 'name' },
],
});
await formApi.reset();
expect(reset).toHaveBeenCalledWith(
{ values: { name: '默认名称' } },
{ force: true },
);
// 未声明默认值的定义重置为空值
formApi.setState({
schema: [{ component: 'input', fieldName: 'remark' }],
});
await formApi.reset();
expect(reset).toHaveBeenLastCalledWith({ values: {} }, { force: true });
// 显式指定重置值时按调用方参数原样执行
await formApi.reset({ values: { name: '自定义' } });
expect(reset).toHaveBeenLastCalledWith(
{ values: { name: '自定义' } },
undefined,
);
});
it('should get values from form', async () => {
const formActions: any = {
meta: {},

13
packages/@core/ui-kit/form-ui/src/form-api.ts

@ -28,6 +28,7 @@ import {
import { warnDeprecatedOnce } from './deprecation';
import { resolveFieldNamePath } from './field-name';
import { decodeFormValues, encodeFormValues } from './form-codec';
import { generateSchemaDefaultValues } from './form-default-values';
import { updateFormSchemaList } from './form-render/schema';
import { formatFormValues } from './form-value-transform';
@ -374,6 +375,18 @@ export class FormApi<
*/
async reset(state?: FormResetState<TFormValues>, opts?: FormResetOptions) {
const form = await this.getForm();
// 未显式指定重置值时,按当前表单定义重新计算默认值,
// 避免动态定义下恢复到挂载时固化的旧默认值
if (!state?.values) {
return form.reset(
{
values: generateSchemaDefaultValues(
this.state?.schema ?? [],
) as Partial<TFormValues>,
},
{ ...opts, force: true },
);
}
return form.reset(state, opts);
}

73
packages/@core/ui-kit/form-ui/src/form-default-values.ts

@ -0,0 +1,73 @@
import type { ZodType } from 'zod';
import type { FormSchemaRuleType } from './types';
import { toRaw } from 'vue';
import { isString, mergeWithArrayOverride, set } from '@vben-core/shared/utils';
import { object, ZodIntersection, ZodNumber, ZodObject, ZodString } from 'zod';
import { getDefaultsForSchema } from 'zod-defaults';
/** 仅依赖计算默认值所需的最小字段结构,兼容任意泛型表单定义 */
interface SchemaLike {
defaultValue?: any;
fieldName: string;
rules?: FormSchemaRuleType;
}
/**
* 根据表单定义计算默认值。
*
* 优先取字段显式声明的默认值;未声明时,尝试从校验规则中推断。
* 动态表单在定义变化后需重新计算,否则重置时会恢复到挂载时的旧默认值
*/
export function generateSchemaDefaultValues(
schema: readonly SchemaLike[] = [],
): Record<string, any> {
const initialValues: Record<string, any> = {};
const zodObject: Record<string, ZodType> = {};
(schema || []).forEach((item) => {
if (Reflect.has(item, 'defaultValue')) {
set(initialValues, item.fieldName, item.defaultValue);
} else if (item.rules && !isString(item.rules)) {
// 检查规则是否适合提取默认值
const rawRules = toRaw(item.rules);
const customDefaultValue = getCustomDefaultValue(rawRules);
zodObject[item.fieldName] = rawRules;
if (customDefaultValue !== undefined) {
initialValues[item.fieldName] = customDefaultValue;
}
}
});
const schemaInitialValues = getDefaultsForSchema(object(zodObject));
const zodDefaults: Record<string, any> = {};
for (const key in schemaInitialValues) {
set(zodDefaults, key, schemaInitialValues[key]);
}
return mergeWithArrayOverride(initialValues, zodDefaults);
}
/** 从校验规则中推断默认值 */
function getCustomDefaultValue(rule: any): any {
rule = toRaw(rule);
if (rule instanceof ZodString) {
return ''; // 默认为空字符串
} else if (rule instanceof ZodNumber) {
return null; // 默认为 null(避免显示 0)
} else if (rule instanceof ZodObject) {
// 递归提取嵌套对象的默认值
const defaultValues: Record<string, any> = {};
for (const [key, valueSchema] of Object.entries(rule.shape)) {
defaultValues[key] = getCustomDefaultValue(valueSchema);
}
return defaultValues;
} else if (rule instanceof ZodIntersection) {
return getDefaultsForSchema(rule);
} else {
return undefined; // 其他类型不提供默认值
}
}

58
packages/@core/ui-kit/form-ui/src/use-form-context.ts

@ -1,17 +1,12 @@
import type { ZodType } from 'zod';
import type { ComputedRef } from 'vue';
import type { ExtendedFormApi, FormActions, VbenFormProps } from './types';
import { computed, toRaw, unref, useSlots } from 'vue';
import { computed, unref, useSlots } from 'vue';
import { createContext } from '@vben-core/shadcn-ui';
import { isString, mergeWithArrayOverride, set } from '@vben-core/shared/utils';
import { object, ZodIntersection, ZodNumber, ZodObject, ZodString } from 'zod';
import { getDefaultsForSchema } from 'zod-defaults';
import { generateSchemaDefaultValues } from './form-default-values';
import { useFormRuntime } from './form-runtime';
type ExtendFormProps = VbenFormProps & {
@ -30,7 +25,7 @@ export function useFormInitial(
props: ComputedRef<VbenFormProps> | VbenFormProps,
) {
const slots = useSlots();
const initialValues = generateInitialValues();
const initialValues = generateSchemaDefaultValues(unref(props).schema);
const form = useFormRuntime(initialValues);
@ -45,53 +40,6 @@ export function useFormInitial(
return resultSlots;
});
function generateInitialValues() {
const initialValues: Record<string, any> = {};
const zodObject: Record<string, ZodType> = {};
(unref(props).schema || []).forEach((item) => {
if (Reflect.has(item, 'defaultValue')) {
set(initialValues, item.fieldName, item.defaultValue);
} else if (item.rules && !isString(item.rules)) {
// 检查规则是否适合提取默认值
const rawRules = toRaw(item.rules);
const customDefaultValue = getCustomDefaultValue(rawRules);
zodObject[item.fieldName] = rawRules;
if (customDefaultValue !== undefined) {
initialValues[item.fieldName] = customDefaultValue;
}
}
});
const schemaInitialValues = getDefaultsForSchema(object(zodObject));
const zodDefaults: Record<string, any> = {};
for (const key in schemaInitialValues) {
set(zodDefaults, key, schemaInitialValues[key]);
}
return mergeWithArrayOverride(initialValues, zodDefaults);
}
// 自定义默认值提取逻辑
function getCustomDefaultValue(rule: any): any {
rule = toRaw(rule);
if (rule instanceof ZodString) {
return ''; // 默认为空字符串
} else if (rule instanceof ZodNumber) {
return null; // 默认为 null(避免显示 0)
} else if (rule instanceof ZodObject) {
// 递归提取嵌套对象的默认值
const defaultValues: Record<string, any> = {};
for (const [key, valueSchema] of Object.entries(rule.shape)) {
defaultValues[key] = getCustomDefaultValue(valueSchema);
}
return defaultValues;
} else if (rule instanceof ZodIntersection) {
return getDefaultsForSchema(rule);
} else {
return undefined; // 其他类型不提供默认值
}
}
return {
delegatedSlots,
form,

Loading…
Cancel
Save