From a7999f50d542e3c369db4a1f6f42339ec0056e04 Mon Sep 17 00:00:00 2001 From: roymondchen Date: Tue, 1 Sep 2026 14:48:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(form):=20=E7=94=A8=20FormContext=20?= =?UTF-8?q?=E4=B8=8E=20provide/inject=20=E6=9B=BF=E4=BB=A3=20extendState?= =?UTF-8?q?=20=E9=92=A9=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 统一表单业务上下文的注入方式,通过读穿 Proxy 保持 mForm 回调签名不变,并移除 extendFormState/extendState 等分散的扩展钩子。 --- docs/api/editor/props.md | 71 ++++-- docs/api/form/form-props.md | 23 +- docs/api/form/submit-form.md | 2 +- docs/guide/advanced/history-list.md | 3 +- packages/editor/src/Editor.vue | 28 +-- .../editor/src/components/CompareForm.vue | 4 +- packages/editor/src/components/ViewForm.vue | 5 +- packages/editor/src/editorProps.ts | 3 +- .../editor/src/fields/configs/displayConds.ts | 2 +- packages/editor/src/hooks/use-compare-form.ts | 32 +-- packages/editor/src/hooks/use-form-context.ts | 40 +++ .../history-list/HistoryDiffDialog.vue | 10 - .../layouts/history-list/HistoryListPanel.vue | 14 +- .../layouts/history-list/useHistoryRevert.ts | 20 +- .../src/layouts/props-panel/FormPanel.vue | 29 +-- .../src/layouts/props-panel/PropsPanel.vue | 5 +- packages/editor/src/type.ts | 39 +-- packages/editor/tests/unit/Editor.spec.ts | 48 +++- .../tests/unit/components/CompareForm.spec.ts | 4 +- .../tests/unit/components/ViewForm.spec.ts | 4 +- .../tests/unit/hooks/use-compare-form.spec.ts | 28 +-- .../history-list/HistoryDiffDialog.spec.ts | 2 +- .../layouts/props-panel/FormPanel.spec.ts | 12 +- .../layouts/props-panel/PropsPanel.spec.ts | 2 +- packages/form-schema/src/base.ts | 21 ++ packages/form/src/Form.vue | 101 +++----- packages/form/src/FormBox.vue | 7 +- packages/form/src/FormDialog.vue | 8 +- packages/form/src/FormDrawer.vue | 8 +- packages/form/src/schema.ts | 12 +- packages/form/src/submitForm.ts | 48 +--- packages/form/src/utils/form.ts | 50 +--- packages/form/src/utils/formStateProxy.ts | 104 ++++++++ packages/form/src/utils/submitHeadless.ts | 13 +- packages/form/src/utils/validateValues.ts | 26 +- packages/form/tests/unit/Form.extra.spec.ts | 233 ++++++++++-------- .../unit/containers/ActionsColumn.spec.ts | 78 ++++++ .../useScrollLastItemIntoView.spec.ts | 3 +- packages/form/tests/unit/fields/Link.spec.ts | 43 ++++ packages/form/tests/unit/submitForm.spec.ts | 26 +- packages/form/tests/unit/utils/form.spec.ts | 171 ++++--------- .../tests/unit/utils/formStateProxy.spec.ts | 181 ++++++++++++++ .../tests/unit/utils/validateValues.spec.ts | 72 ++++-- packages/form/tests/unit/validateForm.spec.ts | 17 +- 44 files changed, 955 insertions(+), 697 deletions(-) create mode 100644 packages/editor/src/hooks/use-form-context.ts create mode 100644 packages/form/src/utils/formStateProxy.ts create mode 100644 packages/form/tests/unit/containers/ActionsColumn.spec.ts create mode 100644 packages/form/tests/unit/utils/formStateProxy.spec.ts diff --git a/docs/api/editor/props.md b/docs/api/editor/props.md index 6cc48c27..ea3ce73a 100644 --- a/docs/api/editor/props.md +++ b/docs/api/editor/props.md @@ -1558,52 +1558,71 @@ const onLayerNodeDblclick = (event, data) => { - 返回 `false` 时,会同时阻断默认的"展开/收起"行为以及向上抛出的 [`layer-node-dblclick`](./events.md#layer-node-dblclick) 事件;返回其他值则继续触发默认行为并抛出事件。 ::: -## extendFormState +## 表单业务上下文 - **详情:** - - 扩展表单状态 - 用于在属性表单中注入自定义的状态数据,这些数据可以在表单配置的各个字段为函数时的第一个参数中获取 + 编辑器会把 `services` 与当前 `stage` 通过 `FORM_CONTEXT_KEY` provide 下去,编辑器内所有 `MForm`(属性面板、历史差异对比表单、侧边栏、以及 Link / FormBox 里的嵌套子表单)都自动继承。 -- **默认值:** `undefined` - -- **类型:** `(state: FormState) => Record | Promise>` - -- **示例:** + 业务方要往里追加自己的字段,在 `` 外层再 provide 一层即可,同名字段覆盖内置的: ```html ``` -:::tip -扩展的状态可以在表单配置中通过 `state` 访问,例如: + 补类型用模块增强: + +```ts +declare module '@tmagic/form-schema' { + interface FormContext { + currentUser?: { name: string; role: string }; + } +} +``` + + 表单配置里统一从第一个参数 `mForm` 读:formState 是读穿 Proxy,`mForm` 上没有的字段自动落到 context,所以回调签名不变,后端下发的存量配置无需改动。 ```js { name: 'title', text: '标题', - // 根据扩展的状态动态设置 - disabled: (state) => state.currentUser.role !== 'admin', + disabled: (mForm) => mForm.currentUser?.role !== 'admin', + display: (mForm) => mForm.env === 'prod', } ``` + +::: warning 从 extendFormState 迁移 +`extendFormState` prop 已移除,同时移除的还有 `PropsPanel` / `FormPanel` / `HistoryDiffDialog` / `CompareForm` / `ViewForm` 的 `extendState`、`CompareForm` 的 `baseFormState`、`useHistoryRevert` 的 `extendState` 与 `getPropsPanelFormState`。 + +改法是把「返回数据的函数」换成「数据本身」: + +```ts +// before +const extendFormState = (state) => ({ env: store.env }); +// + +// after +provide(FORM_CONTEXT_KEY, computed(() => ({ env: store.env }))); +``` + +原先返回 Promise 的写法,改由宿主自己决定挂载时机(`v-if="ready"`),或先 provide 一个空 context、数据到位后更新——后者不保证 `defaultValue` / `onInitValue` 首轮能读到。 + +配置里的 `mForm.xxx` 读法不受影响,读穿 Proxy 保留。 ::: ## historyListExtraTabs diff --git a/docs/api/form/form-props.md b/docs/api/form/form-props.md index 6cf1e6bb..858aa400 100644 --- a/docs/api/form/form-props.md +++ b/docs/api/form/form-props.md @@ -219,8 +219,25 @@ - **类型:** `boolean` -## extendState +## context -- **详情:** 扩展 formState 的钩子函数,返回的对象会被合并到 formState 上 +- **详情:** 宿主业务上下文。可用本 prop 直接传,也可由祖先 `provide(FORM_CONTEXT_KEY)` 下发;同名字段本 prop 优先。 + + 嵌套表单(Link 的子表单、`MFormBox`、`MFormDialog`)会自动继承最近祖先的 context,不需要层层透传。 + + 配置回调统一通过第一个参数 `mForm` 读取:formState 是一个读穿 Proxy,`mForm` 上找不到的字段会自动落到 context。回调签名因此保持不变,后端 eval 下发的存量配置无需改动。 + +- **类型:** `FormContext` + +- **示例:** + +```ts +// 模板: +const formContext = computed(() => ({ username: store.username })); + +// 配置回调:mForm.username 读穿到 context +{ + display: (mForm) => mForm.username === 'admin', +} +``` -- **类型:** `(state: FormState) => Record | Promise>` diff --git a/docs/api/form/submit-form.md b/docs/api/form/submit-form.md index 3227b0c6..d044f707 100644 --- a/docs/api/form/submit-form.md +++ b/docs/api/form/submit-form.md @@ -131,7 +131,7 @@ function submitForm(options: SubmitFormOptions): Promise; | `popperClass` | `string` | — | 弹层 className | | `preventSubmitDefault` | `boolean` | — | 是否阻止表单原生 submit | | `useFieldTextInError` | `boolean` | `true` | 校验失败时错误提示前缀是否使用字段的 `text` 文案;`false` 时直接使用字段 `name` | -| `extendState` | `(state: FormState) => Record \| Promise>` | — | 扩展 `formState` | +| `context` | `FormContext` | — | 宿主业务上下文,与 MForm 的 `context` 语义一致;配置回调通过 `mForm.xxx` 读穿取用 | | `native` | `boolean` | `false` | 透传给 `Form.submitForm`。`true` 时返回内部响应式 `values`,否则返回 `cloneDeep(toRaw(values))` | | `returnChangeRecords` | `boolean` | `false` | `true` 时 resolve 结果为 `{ values, changeRecords }`,携带表单变更记录;否则仅 resolve `values` | | `appContext` | `AppContext \| null` | `null` | 父级 Vue 应用上下文。仅 `dialog: true` 时生效,用于继承全局组件、指令、provide 等,常通过 `app._context` 或 `getCurrentInstance()?.appContext` 获取 | diff --git a/docs/guide/advanced/history-list.md b/docs/guide/advanced/history-list.md index 9ced53e2..afae8c70 100644 --- a/docs/guide/advanced/history-list.md +++ b/docs/guide/advanced/history-list.md @@ -101,7 +101,6 @@ onCodeBlockDiff(id, index); | 字段 | 必填 | 说明 | | --- | --- | --- | | `appContext` | 否 | 父级应用上下文,用于让动态挂载的差异确认弹窗继承全局组件 / 指令 / provide / 插件(Element Plus、`@tmagic/form` 字段组件等)。在组件 `setup` 中调用时会自动取当前组件的 `appContext`,无需手动传;仅当在组件 setup 之外调用时才需显式传入(如 `editorApp._context`)。 | -| `extendState` | 否 | 透传给差异确认弹窗的 `extendState`(同 Editor 的 [`extendFormState`](#自定义对比判断)),使对比表单中依赖业务上下文的 `display` / `disabled` 等 `filterFunction` 正常工作。 | | `dialogWidth` | 否 | 内置页面 / 数据源 / 代码块的差异 / 回滚确认弹窗默认宽度(透传给 `TMagicDialog` 的 `width`),如 `'1200px'` / `'80%'`。缺省时使用弹窗内置默认宽度(`900px`)。业务自有历史可在 `viewDiff` / `confirmAndRevert` 调用时通过各自入参的 `width` 单独覆盖。 | > 若只需要无确认、无校验的静默回滚,直接用上面的 `editorService.revertPageStep` 等即可,无需 `useHistoryRevert`。 @@ -164,7 +163,7 @@ const historyListExtraTabs = [ ## 自定义对比判断 -差异对话框中的「表单对比」最终透传到 `MForm`,你可以通过 Editor 顶层注入的 `extendFormState` 让对比表单拿到完整业务上下文,从而让依赖上下文的 `display` / `disabled` 等 `filterFunction` 正常工作。 +差异对话框中的「表单对比」最终透传到 `MForm`。Editor 会把 `services` / `stage` provide 为 `FORM_CONTEXT_KEY`,对比表单自动继承;业务字段在 `` 外层再 provide 一层即可,详见 [表单业务上下文](/api/editor/props.html#表单业务上下文)。配置回调通过 `mForm.xxx` 读穿取用。 若某些字段语义上相等但结构不同(例如 `code-select` 字段中 `''` 与 `{ hookType: 'code', hookData: [] }` 应视为相等),可借助 `@tmagic/form` 的 [`showDiff`](/api/form/form-props.html#showdiff) 自定义判断函数避免被误判为差异。 diff --git a/packages/editor/src/Editor.vue b/packages/editor/src/Editor.vue index 7c838400..79d6f423 100644 --- a/packages/editor/src/Editor.vue +++ b/packages/editor/src/Editor.vue @@ -101,7 +101,6 @@