diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45797579..b2fb78da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -583,7 +583,7 @@ importers: version: link:../packages/tdesign-vue-next-adapter '@tmagic/tmagic-form-runtime': specifier: 1.1.6 - version: link:../runtime/tmagic-form + version: 1.1.6(@tmagic/core@packages+core)(@tmagic/editor@packages+editor)(element-plus@2.14.4(vue@3.5.41(typescript@6.0.3)))(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)) element-plus: specifier: ^2.14.4 version: 2.14.4(vue@3.5.41(typescript@6.0.3)) @@ -2902,6 +2902,21 @@ packages: typescript: optional: true + '@tmagic/tmagic-form-runtime@1.1.6': + resolution: {integrity: sha512-iRvsrNxrY2KsUh5qiDo+v0bvNzfk41+viQGvuTCdF3lbD4J1ylZ/PNbjAnkFWLMx7Rc6UHcbvRfyx6r22TYHiQ==} + engines: {node: '>=18'} + peerDependencies: + '@tmagic/core': '>=1.7.0' + '@tmagic/editor': '>=1.7.0' + element-plus: ^2.14.4 + typescript: ^5.9.3 + vue: ^3.5.41 + peerDependenciesMeta: + '@tmagic/core': + optional: true + typescript: + optional: true + '@tmagic/utils@1.7.0': resolution: {integrity: sha512-s4K66sgg/K4191vw/D2y7t4RpoS1qZGUaonn2Pkq1t8nKXtRuBVzKfp2eycVlVAVQavyDRxmuB5XQGYHlZCZTw==} engines: {node: '>=18'} @@ -8571,6 +8586,15 @@ snapshots: optionalDependencies: typescript: 6.0.3 + '@tmagic/tmagic-form-runtime@1.1.6(@tmagic/core@packages+core)(@tmagic/editor@packages+editor)(element-plus@2.14.4(vue@3.5.41(typescript@6.0.3)))(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3))': + dependencies: + '@tmagic/editor': link:packages/editor + element-plus: 2.14.4(vue@3.5.41(typescript@6.0.3)) + vue: 3.5.41(typescript@6.0.3) + optionalDependencies: + '@tmagic/core': link:packages/core + typescript: 6.0.3 + '@tmagic/utils@1.7.0(@tmagic/schema@1.7.0(typescript@6.0.3))(typescript@6.0.3)': dependencies: '@tmagic/schema': 1.7.0(typescript@6.0.3) diff --git a/runtime/tmagic-form/README.md b/runtime/tmagic-form/README.md index a562f960..b215d450 100644 --- a/runtime/tmagic-form/README.md +++ b/runtime/tmagic-form/README.md @@ -35,4 +35,29 @@ import { } from '@tmagic/tmagic-form-runtime'; const { render } = useRuntime(); -``` \ No newline at end of file +``` + +## 表单配置需要发请求时 + +`@tmagic/form` 的 `request` 是模块级配置,`setConfig` 会整对象替换。请在宿主 Vue +组件的 setup 中调用 `useRuntime()`:此时会读取宿主 `$MAGIC_FORM`,与 `formOptions` +浅合并后再安装 formPlugin,默认不会清空宿主已有的 `request` / `flat` 等。 + +若宿主未配 `request`,或画布侧需要覆盖,通过 `formOptions` 传入。`request` 的语义与 +宿主 `editorPlugin` 一致:入参为 `{ method, url, data, ... }`,**resolve 出接口 body** +(不要返回整个 Response / axios response)。 + +```ts +const { render } = useRuntime({ + formOptions: { + request: async ({ url, method = 'GET', data }) => { + const response = await fetch(url, { + method, + headers: { 'Content-Type': 'application/json' }, + body: method === 'GET' ? undefined : JSON.stringify(data), + }); + return response.json(); + }, + }, +}); +``` diff --git a/runtime/tmagic-form/package.json b/runtime/tmagic-form/package.json index 19c87379..9b9e852b 100644 --- a/runtime/tmagic-form/package.json +++ b/runtime/tmagic-form/package.json @@ -1,5 +1,5 @@ { - "version": "1.1.6", + "version": "1.1.8", "name": "@tmagic/tmagic-form-runtime", "type": "module", "main": "dist/tmagic-tmagic-form.umd.cjs", diff --git a/runtime/tmagic-form/src/index.ts b/runtime/tmagic-form/src/index.ts index 81ce08b2..b697f759 100644 --- a/runtime/tmagic-form/src/index.ts +++ b/runtime/tmagic-form/src/index.ts @@ -1,12 +1,13 @@ -import { createApp, onBeforeUnmount, Plugin } from 'vue'; +import { createApp, getCurrentInstance, onBeforeUnmount, Plugin } from 'vue'; import cssStyle from 'element-plus/dist/index.css?raw'; -import type { FormConfig, StageCore } from '@tmagic/editor'; +import type { FormConfig, FormInstallOptions, StageCore } from '@tmagic/editor'; import { editorService, formPlugin, injectStyle, Layout, propsService, uiService } from '@tmagic/editor'; import commonConfig from './form-config/common'; import App from './App.vue'; import formConfigs from './form-config'; +import { mergeFormInstallOptions } from './mergeFormOptions'; export * from './component-group-list'; @@ -15,10 +16,22 @@ export const propsConfigs = formConfigs; export const useRuntime = ({ plugins = [], fillConfig = (config) => config, + formOptions = {}, }: { plugins?: Plugin[]; fillConfig?: (config: FormConfig, mForm: any) => FormConfig; + /** + * 透传给 `@tmagic/form` 的安装选项(如 `request`)。 + * + * formPlugin 的 `setConfig` 是整对象替换。`useRuntime` 在调用时读取宿主 + * `$MAGIC_FORM`,再与 `formOptions` 浅合并后安装,避免画布二次安装清空 + * 宿主的 `request` / `flat` 等。画布侧要覆盖或补齐时再传入。 + */ + formOptions?: FormInstallOptions; } = {}) => { + const hostFormOptions = getCurrentInstance()?.appContext.config.globalProperties.$MAGIC_FORM as + FormInstallOptions | undefined; + const render = (stage: StageCore) => { const doc = stage.renderer?.getDocument(); @@ -48,7 +61,7 @@ export const useRuntime = ({ stage, fillConfig, }); - vueApp.use(formPlugin); + vueApp.use(formPlugin, mergeFormInstallOptions(hostFormOptions, formOptions)); plugins.forEach((plugin) => vueApp.use(plugin)); vueApp.mount(el); diff --git a/runtime/tmagic-form/src/mergeFormOptions.ts b/runtime/tmagic-form/src/mergeFormOptions.ts new file mode 100644 index 00000000..e3e2501f --- /dev/null +++ b/runtime/tmagic-form/src/mergeFormOptions.ts @@ -0,0 +1,10 @@ +import type { FormInstallOptions } from '@tmagic/editor'; + +/** + * formPlugin.install 会 setConfig 整对象替换。 + * 把宿主 `$MAGIC_FORM` 与画布 `formOptions` 浅合并后再装,避免清空 request / flat 等。 + */ +export const mergeFormInstallOptions = ( + host: FormInstallOptions | undefined, + formOptions: FormInstallOptions = {}, +): FormInstallOptions => ({ ...(host ?? {}), ...formOptions }); diff --git a/runtime/tmagic-form/tests/merge-form-options.test.ts b/runtime/tmagic-form/tests/merge-form-options.test.ts new file mode 100644 index 00000000..623968b1 --- /dev/null +++ b/runtime/tmagic-form/tests/merge-form-options.test.ts @@ -0,0 +1,35 @@ +/** + * formPlugin.install 会 setConfig 整对象替换。 + * mergeFormInstallOptions 把宿主 $MAGIC_FORM 与 formOptions 浅合并。 + */ +import { describe, expect, it, vi } from 'vitest'; + +import { mergeFormInstallOptions } from '../src/mergeFormOptions'; + +describe('mergeFormInstallOptions', () => { + const hostRequest = vi.fn(); + const hostFields = { select: { component: { name: 'HostSelect' } } }; + const host = { request: hostRequest, flat: true, fields: hostFields }; + + it('无 formOptions 时原样带回宿主配置,避免二次安装清空', () => { + expect(mergeFormInstallOptions(host)).toEqual(host); + expect(mergeFormInstallOptions(host, {})).toEqual(host); + }); + + it('formOptions 只覆盖传入的键,保留宿主其余配置', () => { + const request = vi.fn(); + + expect(mergeFormInstallOptions(host, { request })).toEqual({ + request, + flat: true, + fields: hostFields, + }); + }); + + it('没有宿主配置时只使用 formOptions', () => { + const request = vi.fn(); + + expect(mergeFormInstallOptions(undefined, { request })).toEqual({ request }); + expect(mergeFormInstallOptions(undefined)).toEqual({}); + }); +}); diff --git a/runtime/tmagic-form/tests/use-runtime.test.ts b/runtime/tmagic-form/tests/use-runtime.test.ts new file mode 100644 index 00000000..d8ebbbdd --- /dev/null +++ b/runtime/tmagic-form/tests/use-runtime.test.ts @@ -0,0 +1,78 @@ +/** + * `useRuntime` 读取宿主 `$MAGIC_FORM`,合并后再交给 formPlugin。 + */ +import { describe, expect, it, vi } from 'vitest'; + +import { useRuntime } from '../src/index'; + +const { formPluginMock, createAppMock, appUseMock, getCurrentInstanceMock } = vi.hoisted(() => { + const appUseMock = vi.fn(); + return { + formPluginMock: { install: vi.fn() }, + createAppMock: vi.fn(() => ({ use: appUseMock, mount: vi.fn() })), + appUseMock, + getCurrentInstanceMock: vi.fn(), + }; +}); + +vi.mock('vue', async (importOriginal) => ({ + ...(await importOriginal()), + createApp: createAppMock, + onBeforeUnmount: vi.fn(), + getCurrentInstance: getCurrentInstanceMock, +})); + +vi.mock('@tmagic/editor', () => ({ + formPlugin: formPluginMock, + createForm: (config: unknown) => config, + editorService: { usePlugin: vi.fn(), removeAllPlugins: vi.fn() }, + propsService: { usePlugin: vi.fn(), removeAllPlugins: vi.fn() }, + uiService: { set: vi.fn(), get: vi.fn() }, + injectStyle: vi.fn(), + Layout: { RELATIVE: 'relative' }, +})); + +vi.mock('../src/App.vue', () => ({ default: { name: 'App' } })); + +const stage = { renderer: { getDocument: () => undefined } } as any; + +const hostRequest = vi.fn(); +const hostFormOptions = { request: hostRequest, flat: true, extra: 'keep' }; + +const withHostForm = () => { + getCurrentInstanceMock.mockReturnValue({ + appContext: { config: { globalProperties: { $MAGIC_FORM: hostFormOptions } } }, + }); +}; + +describe('useRuntime — formOptions', () => { + it('setup 中能读到宿主 $MAGIC_FORM 时,默认安装不会清空 request / flat', () => { + withHostForm(); + + useRuntime().render(stage); + + expect(appUseMock).toHaveBeenCalledWith(formPluginMock, hostFormOptions); + }); + + it('formOptions 覆盖 request,保留宿主 flat 等其余键', () => { + withHostForm(); + const request = vi.fn(); + + useRuntime({ formOptions: { request } }).render(stage); + + expect(appUseMock).toHaveBeenCalledWith(formPluginMock, { + request, + flat: true, + extra: 'keep', + }); + }); + + it('不在 setup 中调用时,只透传 formOptions', () => { + getCurrentInstanceMock.mockReturnValue(null); + const request = vi.fn(); + + useRuntime({ formOptions: { request } }).render(stage); + + expect(appUseMock).toHaveBeenCalledWith(formPluginMock, { request }); + }); +});