feat(@vben-core/form-ui): add typed form value codecs (#8189)
* feat(@vben-core/form-ui): add typed form value codecs * test(@vben-core/form-ui): cover codec value boundaries * refactor(project): propagate form codec generics * feat(@vben/plugins): propagate form codecs through vxe grids * refactor(@vben/playground): migrate form codec examples * refactor(@vben/playground): migrate query forms to codecs * docs(@vben/docs): document form value codecs
This commit is contained in:
@@ -244,13 +244,28 @@ export { initComponentAdapter };
|
||||
|
||||
<DemoPreview dir="demos/vben-form/query" />
|
||||
|
||||
## 值格式化
|
||||
## 表单值编解码
|
||||
|
||||
当组件的展示值与后端真正需要的 payload 不一致时,可以在 schema 上使用 `valueFormat`。它会在 `getValues()`、提交、以及依赖这些输出的方法中生效。
|
||||
当组件值与后端 payload 不一致时,使用表单级 `codec` 统一定义双向转换。`encode` 接收完整 `TFormValues` 并返回完整 `TSubmitValues`;`decode` 执行反向转换。多字段拆分、合并和删除都在一个纯函数边界完成,不依赖 schema 顺序或字符串路径写入。
|
||||
|
||||
- `return xxx`:回写当前字段
|
||||
- `setValue('startTime', xxx)`:写入其他字段
|
||||
- `return undefined`:保持当前字段已被移除,适合把一个字段拆成多个字段
|
||||
`codec` 直接写在 `useVbenForm` 选项中即可。只需标注 `encode` 的表单值入参,`TSubmitValues` 会从返回对象自动推导,并传递给 `decode`、`getValues()` 和提交回调:
|
||||
|
||||
```ts
|
||||
const [Form, formApi] = useVbenForm({
|
||||
codec: {
|
||||
decode(values) {
|
||||
return { period: [values.startTime, values.endTime] };
|
||||
},
|
||||
encode(values: Readonly<FormValues>) {
|
||||
return {
|
||||
endTime: values.period[1],
|
||||
startTime: values.period[0],
|
||||
};
|
||||
},
|
||||
},
|
||||
schema,
|
||||
});
|
||||
```
|
||||
|
||||
<DemoPreview dir="demos/vben-form/value-format" />
|
||||
|
||||
@@ -305,7 +320,7 @@ const [Form, formApi] = useVbenForm({
|
||||
|
||||
### 类型传递与插槽
|
||||
|
||||
通过 `useVbenForm<TValues>` 定义一次表单值类型后,`getValues`、`setValues`、`setFieldValue`、`handleSubmit`、`handleValuesChange`、`formApi.form.values` 和 selector 都会沿用该类型,可直接作为 API 请求参数:
|
||||
使用 `useVbenForm<TFormValues, TSubmitValues>` 分别声明组件表单值和提交值。schema、slots、`setValues`、`formApi.form.values` 使用 `TFormValues`;`getValues`、submit 和 `handleSubmit` 第一参数使用 `TSubmitValues`。两种结构相同时只传一个泛型即可。
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
@@ -362,15 +377,16 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
|
||||
|
||||
| 方法名 | 描述 | 类型 | 版本号 |
|
||||
| --- | --- | --- | --- |
|
||||
| submit | 提交表单 | `(e?: Event) => Promise<TValues>` | - |
|
||||
| validateAndSubmit | 校验通过后提交表单 | `() => Promise<TValues \| undefined>` | - |
|
||||
| reset | 重置表单 | `(state?: FormResetState<TValues>, options?: FormResetOptions) => Promise<void>` | - |
|
||||
| clearValidation | 清空指定字段或全部校验,并取消进行中的异步校验 | `(fieldNames?: FormFieldName<TValues> \| FormFieldName<TValues>[]) => Promise<void>` | - |
|
||||
| setValues | 设置表单值,默认会过滤不在 schema 中定义的字段 | `(fields: Partial<TValues>, filterFields?: boolean, shouldValidate?: boolean) => Promise<void>` | - |
|
||||
| getValues | 获取经过字段映射和 valueFormat 的值 | `() => Promise<TValues>` | - |
|
||||
| getRawValues | 获取未格式化的独立值快照 | `() => Promise<TValues>` | - |
|
||||
| getValueSnapshot | 一次获取原始值和格式化值 | `() => Promise<FormValueSnapshot<TValues>>` | - |
|
||||
| formatValues | 格式化指定的原始值快照 | `(rawValues: Readonly<TValues>) => TValues` | - |
|
||||
| submit | 提交表单 | `(e?: Event) => Promise<TSubmitValues>` | - |
|
||||
| validateAndSubmit | 校验通过后提交表单 | `() => Promise<TSubmitValues \| undefined>` | - |
|
||||
| reset | 重置表单 | `(state?: FormResetState<TFormValues>, options?: FormResetOptions) => Promise<void>` | - |
|
||||
| clearValidation | 清空指定字段或全部校验,并取消进行中的异步校验 | `(fieldNames?: FormFieldName<TFormValues> \| FormFieldName<TFormValues>[]) => Promise<void>` | - |
|
||||
| setValues | 设置表单组件值,默认会过滤不在 schema 中定义的字段 | `(fields: Partial<TFormValues>, filterFields?: boolean, shouldValidate?: boolean) => Promise<void>` | - |
|
||||
| setSubmitValues | 通过 codec.decode 回填完整提交值 | `(values: TSubmitValues, filterFields?: boolean, shouldValidate?: boolean) => Promise<void>` | - |
|
||||
| getValues | 获取经过 codec.encode 或旧格式化管道的提交值 | `() => Promise<TSubmitValues>` | - |
|
||||
| getRawValues | 获取未格式化的独立表单值快照 | `() => Promise<TFormValues>` | - |
|
||||
| getValueSnapshot | 一次获取表单值和提交值 | `() => Promise<FormValueSnapshot<TFormValues, TSubmitValues>>` | - |
|
||||
| formatValues | 编码指定的表单值快照 | `(rawValues: Readonly<TFormValues>) => TSubmitValues` | - |
|
||||
| validate | 表单校验 | `() => Promise<FormValidationResult>` | - |
|
||||
| validateField | 校验指定字段 | `(fieldName: string) => Promise<FormValidationResult>` | - |
|
||||
| isFieldValid | 检查某个字段是否已通过校验 | `(fieldName: string)=>Promise<boolean>` | - |
|
||||
@@ -415,8 +431,9 @@ const submitting = formApi.form.useSelector((state) => state.meta.submitting);
|
||||
| actionLayout | 表单操作按钮位置 | `'newLine' \| 'rowEnd' \| 'inline'` | `rowEnd` |
|
||||
| actionPosition | 表单操作按钮对齐方式 | `'left' \| 'center' \| 'right'` | `right` |
|
||||
| handleReset | 表单重置回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
|
||||
| handleSubmit | 表单提交回调 | `(values: TValues, rawValues: Readonly<TValues>) => Promise<void> \| void` | - |
|
||||
| handleValuesChange | 表单值变化回调 | `(rawValues: Readonly<TValues>, fieldsChanged: string[], getFormattedValues: () => TValues) => void` | - |
|
||||
| codec | 表单值与提交值的双向编解码器 | `FormCodec<TFormValues, TSubmitValues>` | - |
|
||||
| handleSubmit | 表单提交回调 | `(values: TSubmitValues, rawValues: Readonly<TFormValues>) => Promise<void> \| void` | - |
|
||||
| handleValuesChange | 表单值变化回调 | `(rawValues: Readonly<TFormValues>, fieldsChanged: string[], getFormattedValues: () => TSubmitValues) => void` | - |
|
||||
| handleCollapsedChange | 表单收起展开状态变化回调 | `(collapsed: boolean) => void` | - |
|
||||
| actionButtonsReverse | 调换操作按钮位置 | `boolean` | `false` |
|
||||
| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - |
|
||||
@@ -435,41 +452,15 @@ const submitting = formApi.form.useSelector((state) => state.meta.submitting);
|
||||
|
||||
::: tip handleValuesChange
|
||||
|
||||
`handleValuesChange` 的第一个参数是未经过 `valueFormat`、`fieldMappingTime` 或 array-to-string 转换的只读当前值,第二个参数是本次发生变化的 schema 字段名。第三个参数 `getFormattedValues` 是惰性函数:不调用就不会执行深拷贝和格式化,适合只在少数变化场景读取提交结构。字段映射生成的目标字段不会出现在 `fieldsChanged` 中。
|
||||
`handleValuesChange` 的第一个参数是未编码的只读 `TFormValues`,第二个参数是本次发生变化的 schema 字段名。第三个参数 `getFormattedValues` 是惰性函数:不调用就不会执行 codec 或旧格式化管道。
|
||||
|
||||
`getRawValues()` 和 `getValues()` 分别只生成一份目标快照;确实需要同时比较两种结构时再调用 `getValueSnapshot()`。`handleSubmit(values, rawValues)` 会在提交边界同时提供格式化结果和对应的原始快照。
|
||||
|
||||
:::
|
||||
|
||||
::: tip fieldMappingTime
|
||||
::: tip 旧格式化 API
|
||||
|
||||
此属性用于将表单内的数组值映射成 2 个字段,它应当传入一个数组,数组的每一项是一个映射规则,规则的第一个成员是一个字符串,表示需要映射的字段名,第二个成员是一个数组,表示映射后的字段名,第三个成员是一个可选的格式掩码,用于格式化日期时间字段;也可以提供一个格式化函数(参数分别为当前值和当前字段名,返回格式化后的值)。如果明确地将格式掩码设为null,则原值映射而不进行格式化(适用于非日期时间字段)。例如:`[['timeRange', ['startTime', 'endTime'], 'YYYY-MM-DD']]`,`timeRange`应当是一个至少具有2个成员的数组类型的值。Form会将`timeRange`的值前两个值分别按照格式掩码`YYYY-MM-DD`格式化后映射到`startTime`和`endTime`字段上。每一项的第三个参数是一个可选的格式掩码,
|
||||
|
||||
:::
|
||||
|
||||
::: tip valueFormat
|
||||
|
||||
`valueFormat` 适合处理“组件值”和“提交值”不一致的场景。例如:
|
||||
|
||||
- `RangePicker` 返回 `[dayjs, dayjs]`,但后端需要 `{ startTime, endTime }`
|
||||
- `DatePicker` 返回 `dayjs`,但后端只需要时间戳
|
||||
|
||||
`valueFormat` 会在 `getValues()` 过程中执行:
|
||||
|
||||
- 返回 `undefined`:当前字段保持删除状态
|
||||
- 返回其他值:回写当前字段
|
||||
- 调用 `setValue(key, nextValue)`:写入一个或多个新字段
|
||||
|
||||
```ts
|
||||
{
|
||||
component: 'RangePicker',
|
||||
fieldName: 'reportRange',
|
||||
valueFormat(value, setValue) {
|
||||
setValue('startTime', value?.[0]?.valueOf());
|
||||
setValue('endTime', value?.[1]?.valueOf());
|
||||
},
|
||||
}
|
||||
```
|
||||
`schema.valueFormat`、`fieldMappingTime` 和 `arrayToStringFields` 仍保持原运行时行为,但已经标记为 `@deprecated`,开发环境首次使用时会提示迁移。配置 codec 后只执行 codec;同时存在的旧配置会被忽略,避免重复转换。
|
||||
|
||||
:::
|
||||
|
||||
@@ -604,7 +595,7 @@ export interface FormSchema<
|
||||
rules?: FormSchemaRuleType;
|
||||
/** 后缀 */
|
||||
suffix?: CustomRenderType;
|
||||
/** 获取 getValues() 输出时格式化当前字段 */
|
||||
/** @deprecated 使用表单级 codec */
|
||||
valueFormat?: FormValueFormat;
|
||||
}
|
||||
```
|
||||
@@ -615,6 +606,8 @@ export interface FormSchema<
|
||||
|
||||
::: details FormValueFormat
|
||||
|
||||
`FormValueFormat` 是兼容类型,已标记为 `@deprecated`。新代码应使用 `FormCodec<TFormValues, TSubmitValues>`。
|
||||
|
||||
```ts
|
||||
type FormValueFormat = (
|
||||
value: any,
|
||||
|
||||
Reference in New Issue
Block a user