mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-06-06 07:30:16 +00:00
feat(form): submitForm 支持返回 changeRecords
新增 returnChangeRecords 选项,开启后 resolve { values, changeRecords },
便于命令式调用时获取表单变更记录,并同步更新文档与单测。
This commit is contained in:
parent
1b66ab1b88
commit
12069e0937
@ -18,7 +18,7 @@ function submitForm(options: SubmitFormOptions): Promise<any>;
|
||||
|
||||
## 参数
|
||||
|
||||
`options` 与 `MForm` 组件的 props 基本对齐,额外提供了 `native`、`appContext`、`timeout` 三个参数。
|
||||
`options` 与 `MForm` 组件的 props 基本对齐,额外提供了 `native`、`returnChangeRecords`、`appContext`、`timeout` 等参数。
|
||||
|
||||
| 名称 | 类型 | 默认值 | 说明 |
|
||||
| ---------------------- | ------------------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------- |
|
||||
@ -39,17 +39,22 @@ function submitForm(options: SubmitFormOptions): Promise<any>;
|
||||
| `preventSubmitDefault` | `boolean` | — | 是否阻止表单原生 submit |
|
||||
| `extendState` | `(state: FormState) => Record<string, any> \| Promise<Record<string, any>>` | — | 扩展 `formState` |
|
||||
| `native` | `boolean` | `false` | 透传给 `Form.submitForm`。`true` 时返回内部响应式 `values`,否则返回 `cloneDeep(toRaw(values))` |
|
||||
| `returnChangeRecords` | `boolean` | `false` | `true` 时 resolve 结果为 `{ values, changeRecords }`,携带表单变更记录;否则仅 resolve `values` |
|
||||
| `appContext` | `AppContext \| null` | `null` | 父级 Vue 应用上下文。需要继承全局组件、指令、provide 等时传入,常通过 `app._context` 或 `getCurrentInstance()?.appContext` 获取 |
|
||||
| `timeout` | `number` | `10000` | 等待表单初始化的最长时间(毫秒)。超时将以错误 reject。设为 `<= 0` 时关闭超时兜底 |
|
||||
|
||||
## 返回值
|
||||
|
||||
- `校验通过` — `Promise<any>` resolve 当前表单值(`native` 决定是否克隆)
|
||||
- `校验通过` — `Promise<any>` resolve 当前表单值(`native` 决定是否克隆);当 `returnChangeRecords` 为 `true` 时,resolve `{ values, changeRecords }`
|
||||
- `校验失败` — `Promise<any>` reject 一个 `Error`,`message` 中包含逐条字段错误信息(格式 `${text} -> ${message}`,多条用 `<br>` 分隔)
|
||||
- `初始化超时` — `Promise<any>` reject `Error('submitForm timeout after ${timeout}ms: form is not initialized.')`
|
||||
|
||||
无论成功或失败,函数都会在最后自动 `unmount` 内部 app 并移除挂载用的 DOM 容器,无需调用方手动清理。
|
||||
|
||||
::: tip 关于 changeRecords
|
||||
`changeRecords` 记录的是表单挂载后发生的字段变更(由各字段的 `change` 事件累积而来)。在 `submitForm` 这种命令式、无用户交互的场景下,通常为空数组;只有在 `extendState` 或字段联动等逻辑中触发了变更时才会有内容。`MForm` 内部的 `submitForm` 在校验通过后会清空变更记录,因此本函数会在调用前先对其做快照再返回。
|
||||
:::
|
||||
|
||||
## 基础用法
|
||||
|
||||
```ts
|
||||
@ -73,6 +78,23 @@ try {
|
||||
}
|
||||
```
|
||||
|
||||
## 同时获取变更记录(changeRecords)
|
||||
|
||||
设置 `returnChangeRecords: true` 后,resolve 的结果会从单纯的 `values` 变为 `{ values, changeRecords }`:
|
||||
|
||||
```ts
|
||||
import { submitForm } from '@tmagic/form';
|
||||
|
||||
const { values, changeRecords } = await submitForm({
|
||||
config: [{ type: 'text', name: 'username', text: '用户名' }],
|
||||
initValues: { username: 'foo' },
|
||||
returnChangeRecords: true,
|
||||
});
|
||||
|
||||
console.log(values); // { username: 'foo' }
|
||||
console.log(changeRecords); // ChangeRecord[]
|
||||
```
|
||||
|
||||
## 在组件中继承父级应用上下文
|
||||
|
||||
`MForm` 内部使用 `@tmagic/design` 的组件(背后可能是 `element-plus` 或 `tdesign`),需要宿主应用先完成相应的 `app.use(...)` 安装。在 `submitForm` 这种脱离常规组件树的命令式调用中,可通过 `appContext` 把父级应用上下文带过去:
|
||||
@ -190,3 +212,7 @@ console.log(values);
|
||||
::: details 查看 `SubmitFormOptions` 类型定义
|
||||
<<< @/../packages/form/src/submitForm.ts#SubmitFormOptions{ts}
|
||||
:::
|
||||
|
||||
::: details 查看 `SubmitFormResult` 类型定义
|
||||
<<< @/../packages/form/src/submitForm.ts#SubmitFormResult{ts}
|
||||
:::
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
import { type AppContext, type Component, createApp, defineComponent, h, nextTick, ref, watch } from 'vue';
|
||||
|
||||
import Form from './Form.vue';
|
||||
import type { FormConfig, FormState } from './schema';
|
||||
import type { ChangeRecord, FormConfig, FormState } from './schema';
|
||||
|
||||
// #region SubmitFormOptions
|
||||
/**
|
||||
@ -48,6 +48,11 @@ export interface SubmitFormOptions {
|
||||
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
||||
/** 透传给 Form.submitForm 的参数:是否直接返回原始响应式 values */
|
||||
native?: boolean;
|
||||
/**
|
||||
* 是否在 resolve 结果中携带 changeRecords(变更记录)。
|
||||
* 开启后 resolve 的结果为 `{ values, changeRecords }`,否则仅 resolve values。
|
||||
*/
|
||||
returnChangeRecords?: boolean;
|
||||
/**
|
||||
* 父级应用上下文,用于继承全局组件、指令、provide 等。
|
||||
* 通常通过 `app._context` 或 `getCurrentInstance()?.appContext` 获取。
|
||||
@ -58,6 +63,18 @@ export interface SubmitFormOptions {
|
||||
}
|
||||
// #endregion SubmitFormOptions
|
||||
|
||||
// #region SubmitFormResult
|
||||
/**
|
||||
* 开启 `returnChangeRecords` 时 submitForm 的返回结果
|
||||
*/
|
||||
export interface SubmitFormResult {
|
||||
/** 校验通过后的表单值 */
|
||||
values: any;
|
||||
/** 表单变更记录 */
|
||||
changeRecords: ChangeRecord[];
|
||||
}
|
||||
// #endregion SubmitFormResult
|
||||
|
||||
/**
|
||||
* 以命令式方式调用 Form.vue 完成一次表单校验/提交。
|
||||
*
|
||||
@ -78,10 +95,17 @@ export interface SubmitFormOptions {
|
||||
* } catch (e) {
|
||||
* console.error(e);
|
||||
* }
|
||||
*
|
||||
* // 需要同时获取变更记录时:
|
||||
* const { values, changeRecords } = await submitForm({
|
||||
* config: [...],
|
||||
* initValues: { name: 'foo' },
|
||||
* returnChangeRecords: true,
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export const submitForm = (options: SubmitFormOptions): Promise<any> => {
|
||||
const { native, appContext, timeout = 10000, ...formProps } = options;
|
||||
const { native, appContext, timeout = 10000, returnChangeRecords, ...formProps } = options;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const container = document.createElement('div');
|
||||
@ -105,8 +129,10 @@ export const submitForm = (options: SubmitFormOptions): Promise<any> => {
|
||||
try {
|
||||
// 等待子组件(FormItem 等)完成首次渲染,确保 validate 能拿到所有字段
|
||||
await nextTick();
|
||||
// submitForm 校验通过后会清空 changeRecords,需在调用前先做快照
|
||||
const changeRecords: ChangeRecord[] = [...(formRef.value.changeRecords ?? [])];
|
||||
const result = await formRef.value.submitForm(native);
|
||||
resolve(result);
|
||||
resolve(returnChangeRecords ? { values: result, changeRecords } : result);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
} finally {
|
||||
|
||||
@ -101,6 +101,31 @@ describe('submitForm', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('returnChangeRecords=true 时返回 { values, changeRecords }', async () => {
|
||||
const result = await submitForm({
|
||||
config: [{ type: 'text', name: 'text', text: 'text' }],
|
||||
initValues: { text: 'hello' },
|
||||
returnChangeRecords: true,
|
||||
appContext,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('values');
|
||||
expect(result).toHaveProperty('changeRecords');
|
||||
expect(result.values).toEqual({ text: 'hello' });
|
||||
expect(Array.isArray(result.changeRecords)).toBe(true);
|
||||
});
|
||||
|
||||
test('未设置 returnChangeRecords 时仅返回 values(不包裹)', async () => {
|
||||
const result = await submitForm({
|
||||
config: [{ type: 'text', name: 'text', text: 'text' }],
|
||||
initValues: { text: 'hello' },
|
||||
appContext,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ text: 'hello' });
|
||||
expect(result).not.toHaveProperty('changeRecords');
|
||||
});
|
||||
|
||||
test('多次连续调用不会相互干扰', async () => {
|
||||
const [v1, v2] = await Promise.all([
|
||||
submitForm({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user