refactor(playground): 整理代码

This commit is contained in:
roymondchen 2025-06-20 15:05:47 +08:00
parent bdd59cff9b
commit 3b1e41b217
4 changed files with 220 additions and 181 deletions

View File

@ -40,62 +40,35 @@
</template>
<script lang="ts" setup>
import { computed, markRaw, nextTick, onBeforeUnmount, Ref, ref, toRaw } from 'vue';
import { useRouter } from 'vue-router';
import { Coin, Connection, CopyDocument, Document, DocumentCopy } from '@element-plus/icons-vue';
import { cloneDeep } from 'lodash-es';
import { computed, onBeforeUnmount, ref, shallowRef, toRaw } from 'vue';
import serialize from 'serialize-javascript';
import type { MApp, MContainer, MNode } from '@tmagic/core';
import { NodeType } from '@tmagic/core';
import type {
CustomizeMoveableOptionsCallbackConfig,
DatasourceTypeOption,
MenuBarData,
MenuButton,
MoveableOptions,
Services,
} from '@tmagic/editor';
import {
asyncLoadJs,
calcValueByFontsize,
ContentMenu,
COPY_STORAGE_KEY,
editorService,
propsService,
TMagicDialog,
TMagicEditor,
tMagicMessage,
tMagicMessageBox,
} from '@tmagic/editor';
import type { CustomizeMoveableOptionsCallbackConfig, DatasourceTypeOption, MoveableOptions } from '@tmagic/editor';
import { editorService, propsService, TMagicDialog, TMagicEditor, tMagicMessage } from '@tmagic/editor';
import DeviceGroup from '../components/DeviceGroup.vue';
import componentGroupList from '../configs/componentGroupList';
import dsl from '../configs/dsl';
import { uaMap } from '../const';
const { VITE_RUNTIME_PATH, VITE_ENTRY_PATH } = import.meta.env;
import { useEditorContentMenuData } from './composables/use-editor-content-menu-data';
import { useEditorMenu } from './composables/use-editor-menu';
import { useEditorRes } from './composables/use-editor-res';
const { VITE_RUNTIME_PATH } = import.meta.env;
const datasourceList: DatasourceTypeOption[] = [];
const runtimeUrl = `${VITE_RUNTIME_PATH}/playground/index.html`;
const router = useRouter();
const editor = ref<InstanceType<typeof TMagicEditor>>();
const deviceGroup = ref<InstanceType<typeof DeviceGroup>>();
const iframe = ref<HTMLIFrameElement>();
const previewVisible = ref(false);
const { propsValues, propsConfigs, eventMethodList, datasourceConfigs, datasourceValues, datasourceEventMethodList } =
useEditorRes();
const { contentMenuData } = useEditorContentMenuData();
const editor = shallowRef<InstanceType<typeof TMagicEditor>>();
const value = ref<MApp>(dsl);
const defaultSelected = ref(dsl.items[0].id);
const propsValues = ref<Record<string, any>>({});
const propsConfigs = ref<Record<string, any>>({});
const eventMethodList = ref<Record<string, any>>({});
const datasourceEventMethodList = ref<Record<string, any>>({
base: {
events: [],
methods: [],
},
});
const datasourceConfigs = ref<Record<string, any>>({});
const datasourceValues = ref<Record<string, any>>({});
const stageRect = ref({
width: 375,
height: 817,
@ -105,130 +78,6 @@ const previewUrl = computed(
() => `${VITE_RUNTIME_PATH}/page/index.html?localPreview=1&page=${editor.value?.editorService.get('page')?.id}`,
);
const collectorOptions = {
id: '',
name: '蒙层',
isTarget: (key: string | number, value: any) =>
typeof key === 'string' && typeof value === 'string' && key.includes('events') && value.startsWith('overlay_'),
isCollectByDefault: false,
};
const usePasteMenu = (menu?: Ref<InstanceType<typeof ContentMenu> | undefined>): MenuButton => ({
type: 'button',
text: '粘贴(带关联信息)',
icon: markRaw(DocumentCopy),
display: (services) => !!services?.storageService?.getItem(COPY_STORAGE_KEY),
handler: (services) => {
const nodes = services?.editorService?.get('nodes');
if (!nodes || nodes.length === 0) return;
if (menu?.value?.$el) {
const stage = services?.editorService?.get('stage');
const rect = menu.value.$el.getBoundingClientRect();
const parentRect = stage?.container?.getBoundingClientRect();
const initialLeft =
calcValueByFontsize(stage?.renderer?.getDocument(), (rect.left || 0) - (parentRect?.left || 0)) /
services.uiService.get('zoom');
const initialTop =
calcValueByFontsize(stage?.renderer?.getDocument(), (rect.top || 0) - (parentRect?.top || 0)) /
services.uiService.get('zoom');
services?.editorService?.paste({ left: initialLeft, top: initialTop }, collectorOptions);
} else {
services?.editorService?.paste({}, collectorOptions);
services?.codeBlockService?.paste();
services?.dataSourceService?.paste();
}
},
});
const contentMenuData = computed<MenuButton[]>(() => [
{
type: 'button',
text: '复制(带关联信息)',
icon: markRaw(CopyDocument),
handler: (services: Services) => {
const nodes = services?.editorService?.get('nodes');
nodes && services?.editorService?.copyWithRelated(cloneDeep(nodes), collectorOptions);
nodes && services?.codeBlockService?.copyWithRelated(cloneDeep(nodes));
nodes && services?.dataSourceService?.copyWithRelated(cloneDeep(nodes));
},
},
usePasteMenu(),
]);
const menu: MenuBarData = {
left: [
{
type: 'text',
text: '魔方',
},
],
center: ['delete', 'undo', 'redo', 'guides', 'rule', 'zoom'],
right: [
{
type: 'button',
text: 'Form Playground',
handler: () => router.push('form'),
},
{
type: 'button',
text: 'Form Editor Playground',
handler: () => router.push('form-editor'),
},
{
type: 'button',
text: 'Table Playground',
handler: () => router.push('table'),
},
'/',
{
type: 'button',
text: '预览',
icon: Connection,
handler: async (services) => {
if (services?.editorService.get('modifiedNodeIds').size > 0) {
try {
await tMagicMessageBox.confirm('有修改未保存,是否先保存再预览', '提示', {
confirmButtonText: '保存并预览',
cancelButtonText: '预览',
type: 'warning',
});
save();
tMagicMessage.success('保存成功');
} catch (e) {
console.error(e);
}
}
previewVisible.value = true;
await nextTick();
if (!iframe.value?.contentWindow || !deviceGroup.value?.viewerDevice) return;
Object.defineProperty(iframe.value.contentWindow.navigator, 'userAgent', {
value: uaMap[deviceGroup.value.viewerDevice],
writable: true,
});
},
},
{
type: 'button',
text: '保存',
icon: Coin,
handler: () => {
save();
tMagicMessage.success('保存成功');
},
},
'/',
{
type: 'button',
icon: Document,
tooltip: '源码',
handler: (service) => service?.uiService.set('showSrc', !service?.uiService.get('showSrc')),
},
],
};
const moveableOptions = (config?: CustomizeMoveableOptionsCallbackConfig): MoveableOptions => {
const options: MoveableOptions = {};
@ -272,21 +121,7 @@ const save = () => {
editor.value?.editorService.resetModifiedNodeId();
};
asyncLoadJs(`${VITE_ENTRY_PATH}/config/index.umd.cjs`).then(() => {
propsConfigs.value = (globalThis as any).magicPresetConfigs;
});
asyncLoadJs(`${VITE_ENTRY_PATH}/value/index.umd.cjs`).then(() => {
propsValues.value = (globalThis as any).magicPresetValues;
});
asyncLoadJs(`${VITE_ENTRY_PATH}/event/index.umd.cjs`).then(() => {
eventMethodList.value = (globalThis as any).magicPresetEvents;
});
asyncLoadJs(`${VITE_ENTRY_PATH}/ds-config/index.umd.cjs`).then(() => {
datasourceConfigs.value = (globalThis as any).magicPresetDsConfigs;
});
asyncLoadJs(`${VITE_ENTRY_PATH}/ds-value/index.umd.cjs`).then(() => {
datasourceValues.value = (globalThis as any).magicPresetDsValues;
});
const { menu, deviceGroup, iframe, previewVisible } = useEditorMenu(value, save);
try {
// eslint-disable-next-line no-eval

View File

@ -0,0 +1,61 @@
import { computed, markRaw, type Ref } from 'vue';
import { CopyDocument, DocumentCopy } from '@element-plus/icons-vue';
import { cloneDeep } from '@tmagic/core';
import { calcValueByFontsize, ContentMenu, COPY_STORAGE_KEY, type MenuButton, Services } from '@tmagic/editor';
export const useEditorContentMenuData = () => {
const collectorOptions = {
id: '',
name: '蒙层',
isTarget: (key: string | number, value: any) =>
typeof key === 'string' && typeof value === 'string' && key.includes('events') && value.startsWith('overlay_'),
isCollectByDefault: false,
};
const usePasteMenu = (menu?: Ref<InstanceType<typeof ContentMenu> | undefined>): MenuButton => ({
type: 'button',
text: '粘贴(带关联信息)',
icon: markRaw(DocumentCopy),
display: (services) => !!services?.storageService?.getItem(COPY_STORAGE_KEY),
handler: (services) => {
const nodes = services?.editorService?.get('nodes');
if (!nodes || nodes.length === 0) return;
if (menu?.value?.$el) {
const stage = services?.editorService?.get('stage');
const rect = menu.value.$el.getBoundingClientRect();
const parentRect = stage?.container?.getBoundingClientRect();
const initialLeft =
calcValueByFontsize(stage?.renderer?.getDocument(), (rect.left || 0) - (parentRect?.left || 0)) /
services.uiService.get('zoom');
const initialTop =
calcValueByFontsize(stage?.renderer?.getDocument(), (rect.top || 0) - (parentRect?.top || 0)) /
services.uiService.get('zoom');
services?.editorService?.paste({ left: initialLeft, top: initialTop }, collectorOptions);
} else {
services?.editorService?.paste({}, collectorOptions);
services?.codeBlockService?.paste();
services?.dataSourceService?.paste();
}
},
});
const contentMenuData = computed<MenuButton[]>(() => [
{
type: 'button',
text: '复制(带关联信息)',
icon: markRaw(CopyDocument),
handler: (services: Services) => {
const nodes = services?.editorService?.get('nodes');
nodes && services?.editorService?.copyWithRelated(cloneDeep(nodes), collectorOptions);
nodes && services?.codeBlockService?.copyWithRelated(cloneDeep(nodes));
nodes && services?.dataSourceService?.copyWithRelated(cloneDeep(nodes));
},
},
usePasteMenu(),
]);
return {
contentMenuData,
};
};

View File

@ -0,0 +1,98 @@
import { nextTick, type Ref, ref, shallowRef } from 'vue';
import { useRouter } from 'vue-router';
import { Coin, Connection, Document } from '@element-plus/icons-vue';
import type { MApp } from '@tmagic/core';
import { type MenuBarData, tMagicMessage, tMagicMessageBox } from '@tmagic/editor';
import DeviceGroup from '../../components/DeviceGroup.vue';
import { uaMap } from '../../const';
export const useEditorMenu = (value: Ref<MApp>, save: () => void) => {
const router = useRouter();
const deviceGroup = shallowRef<InstanceType<typeof DeviceGroup>>();
const iframe = shallowRef<HTMLIFrameElement>();
const previewVisible = ref(false);
const menu: MenuBarData = {
left: [
{
type: 'text',
text: '魔方',
},
],
center: ['delete', 'undo', 'redo', 'guides', 'rule', 'zoom'],
right: [
{
type: 'button',
text: 'Form Playground',
handler: () => router.push('form'),
},
{
type: 'button',
text: 'Form Editor Playground',
handler: () => router.push('form-editor'),
},
{
type: 'button',
text: 'Table Playground',
handler: () => router.push('table'),
},
'/',
{
type: 'button',
text: '预览',
icon: Connection,
handler: async (services) => {
if (services?.editorService.get('modifiedNodeIds').size > 0) {
try {
await tMagicMessageBox.confirm('有修改未保存,是否先保存再预览', '提示', {
confirmButtonText: '保存并预览',
cancelButtonText: '预览',
type: 'warning',
});
save();
tMagicMessage.success('保存成功');
} catch (e) {
console.error(e);
}
}
previewVisible.value = true;
await nextTick();
if (!iframe.value?.contentWindow || !deviceGroup.value?.viewerDevice) return;
Object.defineProperty(iframe.value.contentWindow.navigator, 'userAgent', {
value: uaMap[deviceGroup.value.viewerDevice],
writable: true,
});
},
},
{
type: 'button',
text: '保存',
icon: Coin,
handler: () => {
save();
tMagicMessage.success('保存成功');
},
},
'/',
{
type: 'button',
icon: Document,
tooltip: '源码',
handler: (service) => service?.uiService.set('showSrc', !service?.uiService.get('showSrc')),
},
],
};
return {
menu,
deviceGroup,
iframe,
previewVisible,
save,
};
};

View File

@ -0,0 +1,45 @@
import { ref } from 'vue';
import { asyncLoadJs } from '@tmagic/editor';
const { VITE_ENTRY_PATH } = import.meta.env;
export const useEditorRes = () => {
const propsValues = ref<Record<string, any>>({});
const propsConfigs = ref<Record<string, any>>({});
const eventMethodList = ref<Record<string, any>>({});
const datasourceConfigs = ref<Record<string, any>>({});
const datasourceValues = ref<Record<string, any>>({});
const datasourceEventMethodList = ref<Record<string, any>>({
base: {
events: [],
methods: [],
},
});
asyncLoadJs(`${VITE_ENTRY_PATH}/config/index.umd.cjs`).then(() => {
propsConfigs.value = (globalThis as any).magicPresetConfigs;
});
asyncLoadJs(`${VITE_ENTRY_PATH}/value/index.umd.cjs`).then(() => {
propsValues.value = (globalThis as any).magicPresetValues;
});
asyncLoadJs(`${VITE_ENTRY_PATH}/event/index.umd.cjs`).then(() => {
eventMethodList.value = (globalThis as any).magicPresetEvents;
});
asyncLoadJs(`${VITE_ENTRY_PATH}/ds-config/index.umd.cjs`).then(() => {
datasourceConfigs.value = (globalThis as any).magicPresetDsConfigs;
});
asyncLoadJs(`${VITE_ENTRY_PATH}/ds-value/index.umd.cjs`).then(() => {
datasourceValues.value = (globalThis as any).magicPresetDsValues;
});
return {
propsValues,
propsConfigs,
eventMethodList,
datasourceConfigs,
datasourceValues,
datasourceEventMethodList,
};
};