fix(tmagic-form): 合并宿主 $MAGIC_FORM 与 formOptions,避免二次安装清空 request

This commit is contained in:
roymondchen 2026-09-14 21:17:39 +08:00
parent 5a3edacb96
commit 39ef49db7d
7 changed files with 191 additions and 6 deletions

26
pnpm-lock.yaml generated
View File

@ -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)

View File

@ -35,4 +35,29 @@ import {
} from '@tmagic/tmagic-form-runtime';
const { render } = useRuntime();
```
```
## 表单配置需要发请求时
`@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();
},
},
});
```

View File

@ -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",

View File

@ -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);

View File

@ -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 });

View File

@ -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({});
});
});

View File

@ -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<typeof import('vue')>()),
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 });
});
});