diff --git a/packages/editor/src/Editor.vue b/packages/editor/src/Editor.vue index 53ed643f..35158a37 100644 --- a/packages/editor/src/Editor.vue +++ b/packages/editor/src/Editor.vue @@ -228,7 +228,7 @@ export default defineComponent({ setup(props, { emit }) { const rootChangeHandler = (value: MApp, preValue?: MApp | null) => { - const nodeId = editorService.get('node')?.id || props.defaultSelected; + const nodeId = editorService.get('node')?.id || props.defaultSelected; let node; if (nodeId) { node = editorService.getNodeById(nodeId); diff --git a/packages/editor/src/fields/UISelect.vue b/packages/editor/src/fields/UISelect.vue index 8ae826cb..c1011061 100644 --- a/packages/editor/src/fields/UISelect.vue +++ b/packages/editor/src/fields/UISelect.vue @@ -37,7 +37,7 @@ const uiSelectMode = ref(false); const cancelHandler = () => { if (!services?.uiService) return; - services.uiService.set('uiSelectMode', false); + services.uiService.set('uiSelectMode', false); uiSelectMode.value = false; globalThis.document.removeEventListener('ui-select', clickHandler as EventListener); }; @@ -61,7 +61,7 @@ const toName = computed(() => { const startSelect = () => { if (!services?.uiService) return; - services.uiService.set('uiSelectMode', true); + services.uiService.set('uiSelectMode', true); uiSelectMode.value = true; globalThis.document.addEventListener('ui-select', clickHandler as EventListener); }; diff --git a/packages/editor/src/layouts/AddPageBox.vue b/packages/editor/src/layouts/AddPageBox.vue index d8bd7295..e3c8fd22 100644 --- a/packages/editor/src/layouts/AddPageBox.vue +++ b/packages/editor/src/layouts/AddPageBox.vue @@ -28,9 +28,12 @@ const clickHandler = () => { if (!editorService) return; + const root = toRaw(editorService.get('root')); + if (!root) throw new Error('root 不能为空'); + editorService.add({ type: NodeType.PAGE, - name: generatePageNameByApp(toRaw(editorService.get('root'))), + name: generatePageNameByApp(root), }); }; diff --git a/packages/editor/src/layouts/Framework.vue b/packages/editor/src/layouts/Framework.vue index dc80e820..d777a42a 100644 --- a/packages/editor/src/layouts/Framework.vue +++ b/packages/editor/src/layouts/Framework.vue @@ -46,7 +46,6 @@ import { computed, inject, ref, watch } from 'vue'; import { TMagicScrollbar } from '@tmagic/design'; -import type { MApp } from '@tmagic/schema'; import Layout from '../components/Layout.vue'; import { GetColumnWidth, Services } from '../type'; @@ -67,11 +66,11 @@ withDefaults( const { editorService, uiService } = inject('services') || {}; -const root = computed(() => editorService?.get('root')); -const nodes = computed(() => editorService?.get('nodes') || []); +const root = computed(() => editorService?.get('root')); +const nodes = computed(() => editorService?.get('nodes') || []); -const pageLength = computed(() => editorService?.get('pageLength') || 0); -const showSrc = computed(() => uiService?.get('showSrc')); +const pageLength = computed(() => editorService?.get('pageLength') || 0); +const showSrc = computed(() => uiService?.get('showSrc')); const LEFT_COLUMN_WIDTH_STORAGE_KEY = '$MagicEditorLeftColumnWidthData'; const RIGHT_COLUMN_WIDTH_STORAGE_KEY = '$MagicEditorRightColumnWidthData'; @@ -101,7 +100,7 @@ watch( columnWidth.value.center = globalThis.document.body.clientWidth - left - right; } - uiService?.set('columnWidth', columnWidth); + uiService?.set('columnWidth', columnWidth.value as GetColumnWidth); }, { immediate: true, diff --git a/packages/editor/src/layouts/NavMenu.vue b/packages/editor/src/layouts/NavMenu.vue index 2ab994ed..3cf13055 100644 --- a/packages/editor/src/layouts/NavMenu.vue +++ b/packages/editor/src/layouts/NavMenu.vue @@ -13,7 +13,7 @@ import { Back, Delete, FullScreen, Grid, Memo, Right, ScaleToOriginal, ZoomIn, Z import { NodeType } from '@tmagic/schema'; import ToolButton from '../components/ToolButton.vue'; -import { ColumnLayout, GetColumnWidth, MenuBarData, MenuButton, MenuComponent, MenuItem, Services } from '../type'; +import { ColumnLayout, MenuBarData, MenuButton, MenuComponent, MenuItem, Services } from '../type'; const props = withDefaults( defineProps<{ @@ -29,12 +29,12 @@ const props = withDefaults( const services = inject('services'); const uiService = services?.uiService; -const columnWidth = computed(() => services?.uiService.get('columnWidth')); +const columnWidth = computed(() => services?.uiService.get('columnWidth')); const keys = Object.values(ColumnLayout); -const showGuides = computed((): boolean => uiService?.get('showGuides') ?? true); -const showRule = computed((): boolean => uiService?.get('showRule') ?? true); -const zoom = computed((): number => uiService?.get('zoom') ?? 1); +const showGuides = computed((): boolean => uiService?.get('showGuides') ?? true); +const showRule = computed((): boolean => uiService?.get('showRule') ?? true); +const zoom = computed((): number => uiService?.get('zoom') ?? 1); const isMac = /mac os x/.test(navigator.userAgent.toLowerCase()); const ctrl = isMac ? 'Command' : 'Ctrl'; @@ -67,7 +67,10 @@ const getConfig = (item: MenuItem): (MenuButton | MenuComponent)[] => { icon: markRaw(Delete), tooltip: `刪除(Delete)`, disabled: () => services?.editorService.get('node')?.type === NodeType.PAGE, - handler: () => services?.editorService.remove(services?.editorService.get('node')), + handler: () => { + const node = services?.editorService.get('node'); + node && services?.editorService.remove(node); + }, }); break; case 'undo': diff --git a/packages/editor/src/layouts/PropsPanel.vue b/packages/editor/src/layouts/PropsPanel.vue index 75b12625..9ac2fe51 100644 --- a/packages/editor/src/layouts/PropsPanel.vue +++ b/packages/editor/src/layouts/PropsPanel.vue @@ -19,8 +19,6 @@ import { computed, getCurrentInstance, inject, onMounted, onUnmounted, ref, watc import { tMagicMessage } from '@tmagic/design'; import type { FormValue } from '@tmagic/form'; import { MForm } from '@tmagic/form'; -import type { MNode } from '@tmagic/schema'; -import type StageCore from '@tmagic/stage'; import type { Services } from '../type'; @@ -32,11 +30,9 @@ const configForm = ref>(); // ts类型应该是FormConfig, 但是打包时会出错,所以暂时用any const curFormConfig = ref([]); const services = inject('services'); -const node = computed(() => services?.editorService.get('node')); -const propsPanelSize = computed( - () => services?.uiService.get<'large' | 'default' | 'small'>('propsPanelSize') || 'small', -); -const stage = computed(() => services?.editorService.get('stage')); +const node = computed(() => services?.editorService.get('node')); +const propsPanelSize = computed(() => services?.uiService.get('propsPanelSize') || 'small'); +const stage = computed(() => services?.editorService.get('stage')); const init = async () => { if (!node.value) { diff --git a/packages/editor/src/layouts/sidebar/ComponentListPanel.vue b/packages/editor/src/layouts/sidebar/ComponentListPanel.vue index 89135017..d89e100a 100644 --- a/packages/editor/src/layouts/sidebar/ComponentListPanel.vue +++ b/packages/editor/src/layouts/sidebar/ComponentListPanel.vue @@ -44,7 +44,6 @@ import { Grid, Search } from '@element-plus/icons-vue'; import serialize from 'serialize-javascript'; import { TMagicCollapse, TMagicCollapseItem, TMagicInput, TMagicScrollbar, TMagicTooltip } from '@tmagic/design'; -import type StageCore from '@tmagic/stage'; import { removeClassNameByClassName } from '@tmagic/utils'; import MIcon from '../../components/Icon.vue'; @@ -54,7 +53,7 @@ const searchText = ref(''); const services = inject('services'); const stageOptions = inject('stageOptions'); -const stage = computed(() => services?.editorService.get('stage')); +const stage = computed(() => services?.editorService.get('stage')); const list = computed(() => services?.componentListService.getList().map((group: ComponentGroup) => ({ ...group, diff --git a/packages/editor/src/layouts/sidebar/LayerPanel.vue b/packages/editor/src/layouts/sidebar/LayerPanel.vue index 1b68b836..1514039e 100644 --- a/packages/editor/src/layouts/sidebar/LayerPanel.vue +++ b/packages/editor/src/layouts/sidebar/LayerPanel.vue @@ -65,7 +65,6 @@ import { difference, throttle, union } from 'lodash-es'; import { TMagicInput, TMagicScrollbar, TMagicTree } from '@tmagic/design'; import type { Id, MNode, MPage } from '@tmagic/schema'; import { MContainer, NodeType } from '@tmagic/schema'; -import StageCore from '@tmagic/stage'; import { getNodePath, isPage } from '@tmagic/utils'; import type { MenuButton, MenuComponent, Services } from '../../type'; @@ -108,8 +107,8 @@ const treeProps = { const isMultiSelect = computed(() => isCtrlKeyDown.value || checkedKeys.value.length > 1); -const nodes = computed(() => editorService?.get('nodes') || []); -const page = computed(() => editorService?.get('page')); +const nodes = computed(() => editorService?.get('nodes') || []); +const page = computed(() => editorService?.get('page')); const values = computed(() => (page.value ? [page.value] : [])); // 高亮的节点 const highlightNode = computed(() => editorService?.get('highlightNode')); @@ -121,13 +120,13 @@ const select = async (data: MNode) => { } await editorService?.select(data); - editorService?.get('stage')?.select(data.id); + editorService?.get('stage')?.select(data.id); }; // 触发画布多选 const multiSelect = async (data: Id[]) => { await editorService?.multiSelect(data); - editorService?.get('stage')?.multiSelect(data); + editorService?.get('stage')?.multiSelect(data); }; // 触发画布高亮 @@ -136,7 +135,7 @@ const highlight = (data: MNode) => { throw new Error('没有id'); } editorService?.highlight(data); - editorService?.get('stage')?.highlight(data.id); + editorService?.get('stage')?.highlight(data.id); }; // tree方法:拖拽时判定目标节点能否成为拖动目标位置 @@ -230,14 +229,14 @@ watch(nodes, (nodes) => { watch(isMultiSelect, (isMultiSelect) => { if (!isMultiSelect) { - currentNodeKey.value = editorService?.get('node').id; + currentNodeKey.value = editorService?.get('node')?.id; tree.value?.setCurrentKey(currentNodeKey.value); } }); const editorServiceRemoveHandler = () => { setTimeout(() => { - tree.value?.getNode(editorService?.get('node').id)?.updateChildren(); + tree.value?.getNode(editorService?.get('node')?.id)?.updateChildren(); }, 0); }; @@ -300,7 +299,7 @@ const clickHandler = (data: MNode): void => { if (isCtrlKeyDown.value) { return; } - if (services?.uiService.get('uiSelectMode')) { + if (services?.uiService.get('uiSelectMode')) { document.dispatchEvent(new CustomEvent('ui-select', { detail: data })); return; } diff --git a/packages/editor/src/layouts/sidebar/code-block/CodeBlockList.vue b/packages/editor/src/layouts/sidebar/code-block/CodeBlockList.vue index 8408601a..f70698da 100644 --- a/packages/editor/src/layouts/sidebar/code-block/CodeBlockList.vue +++ b/packages/editor/src/layouts/sidebar/code-block/CodeBlockList.vue @@ -93,7 +93,6 @@ import { cloneDeep, forIn, isEmpty } from 'lodash-es'; import { TMagicButton, TMagicInput, tMagicMessage, TMagicScrollbar, TMagicTooltip, TMagicTree } from '@tmagic/design'; import { CodeBlockContent, Id } from '@tmagic/schema'; -import StageCore from '@tmagic/stage'; import Icon from '../../../components/Icon.vue'; import type { CodeRelation, Services } from '../../../type'; @@ -228,7 +227,7 @@ const getCompName = (compId: Id): string => { // 选中组件 const selectComp = (compId: Id) => { - const stage = services?.editorService.get('stage'); + const stage = services?.editorService.get('stage'); services?.editorService.select(compId); stage?.select(compId); }; diff --git a/packages/editor/src/layouts/workspace/Breadcrumb.vue b/packages/editor/src/layouts/workspace/Breadcrumb.vue index d3ffbac1..de1d48e7 100644 --- a/packages/editor/src/layouts/workspace/Breadcrumb.vue +++ b/packages/editor/src/layouts/workspace/Breadcrumb.vue @@ -11,8 +11,7 @@ import { computed, inject } from 'vue'; import { TMagicButton } from '@tmagic/design'; -import type { MApp, MNode } from '@tmagic/schema'; -import type StageCore from '@tmagic/stage'; +import type { MNode } from '@tmagic/schema'; import { getNodePath } from '@tmagic/utils'; import type { Services } from '../../type'; @@ -20,13 +19,13 @@ import type { Services } from '../../type'; const services = inject('services'); const editorService = services?.editorService; -const node = computed(() => editorService?.get('node')); -const nodes = computed(() => editorService?.get('nodes') || []); -const root = computed(() => editorService?.get('root')); +const node = computed(() => editorService?.get('node')); +const nodes = computed(() => editorService?.get('nodes') || []); +const root = computed(() => editorService?.get('root')); const path = computed(() => getNodePath(node.value?.id || '', root.value?.items || [])); const select = async (node: MNode) => { await editorService?.select(node); - editorService?.get('stage')?.select(node.id); + editorService?.get('stage')?.select(node.id); }; diff --git a/packages/editor/src/layouts/workspace/PageBar.vue b/packages/editor/src/layouts/workspace/PageBar.vue index d9c9133f..66124d16 100644 --- a/packages/editor/src/layouts/workspace/PageBar.vue +++ b/packages/editor/src/layouts/workspace/PageBar.vue @@ -51,7 +51,7 @@ import { computed, inject } from 'vue'; import { CaretBottom, Delete, DocumentCopy } from '@element-plus/icons-vue'; import { TMagicIcon, TMagicPopover, TMagicTooltip } from '@tmagic/design'; -import type { MApp, MPage } from '@tmagic/schema'; +import type { MPage } from '@tmagic/schema'; import ToolButton from '../../components/ToolButton.vue'; import type { Services } from '../../type'; @@ -61,7 +61,7 @@ import PageBarScrollContainer from './PageBarScrollContainer.vue'; const services = inject('services'); const editorService = services?.editorService; -const root = computed(() => editorService?.get('root')); +const root = computed(() => editorService?.get('root')); const page = computed(() => editorService?.get('page')); diff --git a/packages/editor/src/layouts/workspace/PageBarScrollContainer.vue b/packages/editor/src/layouts/workspace/PageBarScrollContainer.vue index f055dc06..6670cfac 100644 --- a/packages/editor/src/layouts/workspace/PageBarScrollContainer.vue +++ b/packages/editor/src/layouts/workspace/PageBarScrollContainer.vue @@ -100,7 +100,7 @@ const scroll = (type: 'left' | 'right' | 'start' | 'end') => { itemsContainer.value.style.transform = `translate(${translateLeft}px, 0px)`; }; -const pageLength = computed(() => editorService?.get('pageLength')); +const pageLength = computed(() => editorService?.get('pageLength')); watch(pageLength, (length = 0, preLength = 0) => { setTimeout(() => { @@ -115,9 +115,11 @@ watch(pageLength, (length = 0, preLength = 0) => { const addPage = () => { if (!editorService) return; + const root = toRaw(editorService.get('root')); + if (!root) throw new Error('root 不能为空'); const pageConfig = { type: NodeType.PAGE, - name: generatePageNameByApp(toRaw(editorService.get('root'))), + name: generatePageNameByApp(root), }; editorService.add(pageConfig); }; diff --git a/packages/editor/src/layouts/workspace/Stage.vue b/packages/editor/src/layouts/workspace/Stage.vue index b391767b..8a1dc612 100644 --- a/packages/editor/src/layouts/workspace/Stage.vue +++ b/packages/editor/src/layouts/workspace/Stage.vue @@ -30,11 +30,11 @@ import { computed, inject, markRaw, nextTick, onMounted, onUnmounted, ref, toRaw, watch, watchEffect } from 'vue'; import { cloneDeep } from 'lodash-es'; -import type { MApp, MContainer, MNode, MPage } from '@tmagic/schema'; +import type { MContainer } from '@tmagic/schema'; import StageCore, { calcValueByFontsize, getOffset, Runtime } from '@tmagic/stage'; import ScrollViewer from '../../components/ScrollViewer.vue'; -import { Layout, MenuButton, MenuComponent, Services, StageOptions, StageRect } from '../../type'; +import { Layout, MenuButton, MenuComponent, Services, StageOptions } from '../../type'; import { useStage } from '../../utils/stage'; import ViewerMenu from './ViewerMenu.vue'; @@ -53,13 +53,14 @@ const stageWrap = ref>(); const stageContainer = ref(); const menu = ref>(); -const isMultiSelect = computed(() => services?.editorService.get('nodes')?.length > 1); -const stageRect = computed(() => services?.uiService.get('stageRect')); -const stageContainerRect = computed(() => services?.uiService.get('stageContainerRect')); -const root = computed(() => services?.editorService.get('root')); -const page = computed(() => services?.editorService.get('page')); -const zoom = computed(() => services?.uiService.get('zoom') || 1); -const node = computed(() => services?.editorService.get('node')); +const nodes = computed(() => services?.editorService.get('nodes') || []); +const isMultiSelect = computed(() => nodes.value.length > 1); +const stageRect = computed(() => services?.uiService.get('stageRect')); +const stageContainerRect = computed(() => services?.uiService.get('stageContainerRect')); +const root = computed(() => services?.editorService.get('root')); +const page = computed(() => services?.editorService.get('page')); +const zoom = computed(() => services?.uiService.get('zoom') || 1); +const node = computed(() => services?.editorService.get('node')); watchEffect(() => { if (stage || !page.value) return; @@ -142,7 +143,7 @@ const dropHandler = async (e: DragEvent) => { const doc = stage?.renderer.contentWindow?.document; const parentEl: HTMLElement | null | undefined = doc?.querySelector(`.${stageOptions?.containerHighlightClassName}`); - let parent: MContainer | undefined = page.value; + let parent: MContainer | undefined | null = page.value; if (parentEl) { parent = services?.editorService.getNodeById(parentEl.id, false) as MContainer; } diff --git a/packages/editor/src/layouts/workspace/ViewerMenu.vue b/packages/editor/src/layouts/workspace/ViewerMenu.vue index 331cc832..88e822ee 100644 --- a/packages/editor/src/layouts/workspace/ViewerMenu.vue +++ b/packages/editor/src/layouts/workspace/ViewerMenu.vue @@ -6,8 +6,7 @@ import { computed, inject, markRaw, ref, watch } from 'vue'; import { Bottom, CopyDocument, Delete, DocumentCopy, Top } from '@element-plus/icons-vue'; -import { MNode, NodeType } from '@tmagic/schema'; -import StageCore from '@tmagic/stage'; +import { NodeType } from '@tmagic/schema'; import { isPage } from '@tmagic/utils'; import ContentMenu from '../../components/ContentMenu.vue'; @@ -26,10 +25,10 @@ const menu = ref>(); const canPaste = ref(false); const canCenter = ref(false); -const node = computed(() => editorService?.get('node')); -const nodes = computed(() => editorService?.get('nodes')); +const node = computed(() => editorService?.get('node')); +const nodes = computed(() => editorService?.get('nodes')); const parent = computed(() => editorService?.get('parent')); -const stage = computed(() => editorService?.get('stage')); +const stage = computed(() => editorService?.get('stage')); const menuData = computed<(MenuButton | MenuComponent)[]>(() => [ { @@ -129,7 +128,7 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() => [ type: 'button', text: '清空参考线', handler: () => { - editorService?.get('stage').clearGuides(); + editorService?.get('stage')?.clearGuides(); }, }, ...props.stageContentMenu, diff --git a/packages/editor/src/layouts/workspace/Workspace.vue b/packages/editor/src/layouts/workspace/Workspace.vue index b897d1dd..b541e5d3 100644 --- a/packages/editor/src/layouts/workspace/Workspace.vue +++ b/packages/editor/src/layouts/workspace/Workspace.vue @@ -19,7 +19,6 @@ import { computed, inject, onMounted, onUnmounted, ref } from 'vue'; import KeyController from 'keycon'; -import type { MNode, MPage } from '@tmagic/schema'; import { isPage } from '@tmagic/utils'; import type { MenuButton, MenuComponent, Services } from '../../type'; @@ -34,8 +33,8 @@ defineProps<{ const services = inject('services'); const workspace = ref(); -const nodes = computed(() => services?.editorService.get('nodes')); -const page = computed(() => services?.editorService.get('page')); +const nodes = computed(() => services?.editorService.get('nodes')); +const page = computed(() => services?.editorService.get('page')); const mouseenterHandler = () => { workspace.value?.focus(); diff --git a/packages/editor/src/services/codeBlock.ts b/packages/editor/src/services/codeBlock.ts index deb21fe1..02fe6447 100644 --- a/packages/editor/src/services/codeBlock.ts +++ b/packages/editor/src/services/codeBlock.ts @@ -19,7 +19,7 @@ import { reactive } from 'vue'; import { cloneDeep, forIn, isEmpty, keys, omit, pick } from 'lodash-es'; -import { CodeBlockContent, CodeBlockDSL, HookType, Id, MApp, MNode } from '@tmagic/schema'; +import { CodeBlockContent, CodeBlockDSL, HookType, Id, MNode } from '@tmagic/schema'; import editorService from '../services/editor'; import type { CodeRelation, CodeState, HookData } from '../type'; @@ -253,7 +253,7 @@ class CodeBlock extends BaseService { * @returns {CodeRelation | null} */ public refreshCombineInfo(): CodeRelation | null { - const root = editorService.get('root'); + const root = editorService.get('root'); if (!root) return null; const relations = {}; this.recurseMNode(root, relations); diff --git a/packages/editor/src/services/editor.ts b/packages/editor/src/services/editor.ts index fdeed9aa..6d52e794 100644 --- a/packages/editor/src/services/editor.ts +++ b/packages/editor/src/services/editor.ts @@ -27,7 +27,7 @@ import { getNodePath, isNumber, isPage, isPop } from '@tmagic/utils'; import codeBlockService from '../services/codeBlock'; import historyService from '../services/history'; import storageService, { Protocol } from '../services/storage'; -import type { AddMNode, EditorNodeInfo, PastePosition, StepValue, StoreState } from '../type'; +import type { AddMNode, EditorNodeInfo, PastePosition, StepValue, StoreState, StoreStateKey } from '../type'; import { LayerOffset, Layout } from '../type'; import { change2Fixed, @@ -94,15 +94,26 @@ class Editor extends BaseService { * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'pageLength' * @param value MNode */ - public set(name: keyof StoreState, value: T) { + public set(name: K, value: T) { const preValue = this.state[name]; - this.state[name] = value as any; + this.state[name] = value; + // set nodes时将node设置为nodes第一个元素 - if (name === 'nodes') { - this.set('node', (value as unknown as MNode[])[0]); + if (name === 'nodes' && Array.isArray(value)) { + this.set('node', value[0]); } + if (name === 'root') { - this.state.pageLength = (value as unknown as MApp)?.items?.length || 0; + if (Array.isArray(value)) { + throw new Error('root 不能为数组'); + } + + if (isObject(value) && !(value instanceof StageCore) && !(value instanceof Map)) { + this.state.pageLength = value.items.length; + } else { + this.state.pageLength = 0; + } + this.emit('root-change', value, preValue); } } @@ -112,8 +123,8 @@ class Editor extends BaseService { * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'pageLength' * @returns MNode */ - public get(name: keyof StoreState): T { - return (this.state as any)[name]; + public get(name: K): StoreState[K] { + return this.state[name]; } /** @@ -123,22 +134,29 @@ class Editor extends BaseService { * @returns {EditorNodeInfo} */ public getNodeInfo(id: Id, raw = true): EditorNodeInfo { - let root = this.get('root'); + let root = this.get('root'); if (raw) { root = toRaw(root); } - if (!root) return {}; + + const info: EditorNodeInfo = { + node: null, + parent: null, + page: null, + }; + + if (!root) return info; if (id === root.id) { - return { node: root }; + info.node = root; + return info; } const path = getNodePath(id, root.items); - if (!path.length) return {}; + if (!path.length) return info; path.unshift(root); - const info: EditorNodeInfo = {}; info.node = path[path.length - 1] as MComponent; info.parent = path[path.length - 2] as MContainer; @@ -159,7 +177,7 @@ class Editor extends BaseService { * @param {boolean} raw 是否使用toRaw * @returns 组件节点配置 */ - public getNodeById(id: Id, raw = true): MNode | undefined { + public getNodeById(id: Id, raw = true): MNode | null { const { node } = this.getNodeInfo(id, raw); return node; } @@ -170,8 +188,7 @@ class Editor extends BaseService { * @param {boolean} raw 是否使用toRaw * @returns 指点组件的父节点配置 */ - public getParentById(id: Id, raw = true): MContainer | undefined { - if (!this.get('root')) return; + public getParentById(id: Id, raw = true): MContainer | null { const { parent } = this.getNodeInfo(id, raw); return parent; } @@ -201,9 +218,9 @@ class Editor extends BaseService { */ public async select(config: MNode | Id): Promise | never { const { node, page, parent } = this.selectedConfigExceptionHandler(config); - this.set('nodes', [node]); - this.set('page', page || null); - this.set('parent', parent || null); + this.set('nodes', node ? [node] : []); + this.set('page', page); + this.set('parent', parent); if (page) { historyService.changePage(toRaw(page)); @@ -212,7 +229,7 @@ class Editor extends BaseService { } if (node?.id) { - this.get('stage') + this.get('stage') ?.renderer.runtime?.getApp?.() ?.emit( 'editor:select', @@ -221,7 +238,7 @@ class Editor extends BaseService { page, parent, }, - getNodePath(node.id, this.get('root').items), + getNodePath(node.id, this.get('root')?.items), ); } @@ -230,7 +247,7 @@ class Editor extends BaseService { return node!; } - public async selectNextNode(): Promise | never { + public async selectNextNode(): Promise | never { const node = toRaw(this.get('node')); if (!node || isPage(node) || node.type === NodeType.ROOT) return node; @@ -244,21 +261,24 @@ class Editor extends BaseService { const nextNode = parent.items[index + 1] || parent.items[0]; await this.select(nextNode); - this.get('stage')?.select(nextNode.id); + this.get('stage')?.select(nextNode.id); return nextNode; } public async selectNextPage(): Promise | never { - const root = toRaw(this.get('root')); + const root = toRaw(this.get('root')); const page = toRaw(this.get('page')); + if (!page) throw new Error('page不能为空'); + if (!root) throw new Error('root不能为空'); + const index = getNodeIndex(page, root); const nextPage = root.items[index + 1] || root.items[0]; await this.select(nextPage); - this.get('stage')?.select(nextPage.id); + this.get('stage')?.select(nextPage.id); return nextPage; } @@ -292,20 +312,25 @@ class Editor extends BaseService { } public async doAdd(node: MNode, parent: MContainer): Promise { - const root = this.get('root'); - const curNode = this.get('node'); - const stage = this.get('stage'); + const root = this.get('root'); - if ((parent?.type === NodeType.ROOT || curNode.type === NodeType.ROOT) && node.type !== NodeType.PAGE) { + if (!root) throw new Error('root为空'); + + const curNode = this.get('node'); + const stage = this.get('stage'); + + if (!curNode) throw new Error('当前选中节点为空'); + + if ((parent.type === NodeType.ROOT || curNode?.type === NodeType.ROOT) && node.type !== NodeType.PAGE) { throw new Error('app下不能添加组件'); } if (parent.id !== curNode.id && node.type !== NodeType.PAGE) { const index = parent.items.indexOf(curNode); - parent?.items?.splice(index + 1, 0, node); + parent.items?.splice(index + 1, 0, node); } else { // 新增节点添加到配置中 - parent?.items?.push(node); + parent.items?.push(node); } const layout = await this.getLayout(toRaw(parent), node as MNode); @@ -337,7 +362,7 @@ class Editor extends BaseService { * @returns 添加后的节点 */ public async add(addNode: AddMNode | MNode[], parent?: MContainer | null): Promise { - const stage = this.get('stage'); + const stage = this.get('stage'); // 新增多个组件只存在于粘贴多个组件,粘贴的是一个完整的config,所以不再需要getPropsValue const addNodes = []; @@ -353,8 +378,9 @@ class Editor extends BaseService { const newNodes = await Promise.all( addNodes.map((node) => { - if (isPage(node)) { - return this.doAdd(node, this.get('root')); + const root = this.get('root'); + if (isPage(node) && root) { + return this.doAdd(node, root); } const parentNode = parent && typeof parent !== 'function' ? parent : getAddParent(node); if (!parentNode) throw new Error('未找到父元素'); @@ -388,9 +414,8 @@ class Editor extends BaseService { } public async doRemove(node: MNode): Promise { - const root = this.get('root'); - - if (!root) throw new Error('没有root'); + const root = this.get('root'); + if (!root) throw new Error('root不能为空'); const { parent, node: curNode } = this.getNodeInfo(node.id); @@ -401,7 +426,7 @@ class Editor extends BaseService { if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点'); parent.items?.splice(index, 1); - const stage = this.get('stage'); + const stage = this.get('stage'); stage?.remove({ id: node.id, parentId: parent.id, root: cloneDeep(root) }); if (node.type === NodeType.PAGE) { @@ -450,6 +475,9 @@ class Editor extends BaseService { } public async doUpdate(config: MNode) { + const root = this.get('root'); + if (!root) throw new Error('root为空'); + if (!config?.id) throw new Error('没有配置或者配置缺少id值'); const info = this.getNodeInfo(config.id, false); @@ -458,7 +486,7 @@ class Editor extends BaseService { const node = cloneDeep(toRaw(info.node)); - let newConfig = await this.toggleFixedPosition(toRaw(config), node, this.get('root')); + let newConfig = await this.toggleFixedPosition(toRaw(config), node, root); newConfig = mergeWith(cloneDeep(node), newConfig, (objValue, srcValue) => { if (isObject(srcValue) && Array.isArray(objValue)) { @@ -473,7 +501,7 @@ class Editor extends BaseService { if (!newConfig.type) throw new Error('配置缺少type值'); if (newConfig.type === NodeType.ROOT) { - this.set('root', newConfig); + this.set('root', newConfig as MApp); return newConfig; } @@ -494,19 +522,19 @@ class Editor extends BaseService { parentNodeItems[index] = newConfig; // 将update后的配置更新到nodes中 - const nodes = this.get('nodes') || []; + const nodes = this.get('nodes'); const targetIndex = nodes.findIndex((nodeItem: MNode) => `${nodeItem.id}` === `${newConfig.id}`); nodes.splice(targetIndex, 1, newConfig); this.set('nodes', [...nodes]); - this.get('stage')?.update({ + this.get('stage')?.update({ config: cloneDeep(newConfig), parentId: parent.id, - root: cloneDeep(this.get('root')), + root: cloneDeep(root), }); if (newConfig.type === NodeType.PAGE) { - this.set('page', newConfig); + this.set('page', newConfig as MPage); } this.addModifiedNodeId(newConfig.id); @@ -538,8 +566,15 @@ class Editor extends BaseService { * @returns void */ public async sort(id1: Id, id2: Id): Promise { - const node = this.get('node'); - const parent = cloneDeep(toRaw(this.get('parent'))); + const root = this.get('root'); + if (!root) throw new Error('root为空'); + + const node = this.get('node'); + if (!node) throw new Error('当前节点为空'); + + const parent = cloneDeep(toRaw(this.get('parent'))); + if (!parent) throw new Error('父节点为空'); + const index2 = parent.items.findIndex((node: MNode) => `${node.id}` === `${id2}`); // 在 id1 的兄弟组件中若无 id2 则直接 return if (index2 < 0) return; @@ -550,10 +585,10 @@ class Editor extends BaseService { await this.update(parent); await this.select(node); - this.get('stage')?.update({ + this.get('stage')?.update({ config: cloneDeep(node), parentId: parent.id, - root: cloneDeep(this.get('root')), + root: cloneDeep(root), }); this.addModifiedNodeId(parent.id); @@ -581,14 +616,14 @@ class Editor extends BaseService { if (!Array.isArray(config)) return; - const node = this.get('node'); + const node = this.get('node'); - let parent: MContainer | undefined = undefined; + let parent: MContainer | null = null; // 粘贴的组件为当前选中组件的副本时,则添加到当前选中组件的父组件中 - if (config.length === 1 && config[0].id === node.id) { - parent = this.get('parent'); - if (parent.type === NodeType.ROOT) { - parent = this.get('page'); + if (config.length === 1 && config[0].id === node?.id) { + parent = this.get('parent'); + if (parent?.type === NodeType.ROOT) { + parent = this.get('page'); } } @@ -615,7 +650,7 @@ class Editor extends BaseService { if (!node.style) return config; - const stage = this.get('stage'); + const stage = this.get('stage'); const doc = stage?.renderer.contentWindow?.document; if (doc) { @@ -638,7 +673,7 @@ class Editor extends BaseService { */ public async alignCenter(config: MNode | MNode[]): Promise { const nodes = Array.isArray(config) ? config : [config]; - const stage = this.get('stage'); + const stage = this.get('stage'); const newNodes = await Promise.all(nodes.map((node) => this.doAlignCenter(node))); @@ -658,9 +693,16 @@ class Editor extends BaseService { * @param offset 偏移量 */ public async moveLayer(offset: number | LayerOffset): Promise { - const parent = this.get('parent'); - const node = this.get('node'); - const brothers: MNode[] = parent?.items || []; + const root = this.get('root'); + if (!root) throw new Error('root为空'); + + const parent = this.get('parent'); + if (!parent) throw new Error('父节点为空'); + + const node = this.get('node'); + if (!node) throw new Error('当前节点为空'); + + const brothers: MNode[] = parent.items || []; const index = brothers.findIndex((item) => `${item.id}` === `${node?.id}`); // 流式布局与绝对定位布局操作的相反的 @@ -678,10 +720,10 @@ class Editor extends BaseService { } const grandparent = this.getParentById(parent.id); - this.get('stage')?.update({ + this.get('stage')?.update({ config: cloneDeep(toRaw(parent)), parentId: grandparent?.id, - root: cloneDeep(this.get('root')), + root: cloneDeep(root), }); this.addModifiedNodeId(parent.id); @@ -694,13 +736,12 @@ class Editor extends BaseService { * @param targetId 容器ID */ public async moveToContainer(config: MNode, targetId: Id): Promise { + const root = cloneDeep(this.get('root')); const { node, parent } = this.getNodeInfo(config.id, false); const target = this.getNodeById(targetId, false) as MContainer; - const stage = this.get('stage'); - - if (node && parent && stage) { - const root = cloneDeep(this.get('root')); + const stage = this.get('stage'); + if (root && node && parent && stage) { const index = getNodeIndex(node, parent); parent.items?.splice(index, 1); @@ -783,7 +824,7 @@ class Editor extends BaseService { this.set('stage', null); this.set('highlightNode', null); this.set('modifiedNodeIds', new Map()); - this.set('pageLength', new Map()); + this.set('pageLength', 0); } public destroy() { @@ -793,7 +834,7 @@ class Editor extends BaseService { } public resetModifiedNodeId() { - this.get>('modifiedNodeIds').clear(); + this.get('modifiedNodeIds').clear(); } /** @@ -801,15 +842,13 @@ class Editor extends BaseService { * @returns {CodeBlockDSL | null} */ public async getCodeDsl(): Promise { - const root = this.get('root'); - if (!root) return null; - return root.codeBlocks || null; + const root = this.get('root'); + return root?.codeBlocks || null; } public getCodeDslSync(): CodeBlockDSL | null { - const root = this.get('root'); - if (!root) return null; - return root.codeBlocks || null; + const root = this.get('root'); + return root?.codeBlocks || null; } /** @@ -824,16 +863,17 @@ class Editor extends BaseService { private addModifiedNodeId(id: Id) { if (!this.isHistoryStateChange) { - this.get>('modifiedNodeIds').set(id, id); + this.get('modifiedNodeIds').set(id, id); } } private pushHistoryState() { const curNode = cloneDeep(toRaw(this.get('node'))); - if (!this.isHistoryStateChange) { + const page = this.get('page'); + if (!this.isHistoryStateChange && curNode && page) { historyService.push({ - data: cloneDeep(toRaw(this.get('page'))), - modifiedNodeIds: this.get>('modifiedNodeIds'), + data: cloneDeep(toRaw(page)), + modifiedNodeIds: this.get('modifiedNodeIds'), nodeId: curNode.id, }); } @@ -849,7 +889,7 @@ class Editor extends BaseService { setTimeout(async () => { if (!value.nodeId) return; await this.select(value.nodeId); - this.get('stage')?.select(value.nodeId); + this.get('stage')?.select(value.nodeId); }, 0); } diff --git a/packages/editor/src/services/ui.ts b/packages/editor/src/services/ui.ts index 5e97c6c8..dcf22374 100644 --- a/packages/editor/src/services/ui.ts +++ b/packages/editor/src/services/ui.ts @@ -18,8 +18,6 @@ import { reactive } from 'vue'; -import type StageCore from '@tmagic/stage'; - import editorService from '../services/editor'; import type { StageRect, UiState } from '../type'; @@ -53,8 +51,8 @@ class Ui extends BaseService { super(['zoom', 'calcZoom']); } - public set(name: keyof UiState, value: T) { - const mask = editorService.get('stage')?.mask; + public set(name: K, value: T) { + const mask = editorService.get('stage')?.mask; if (name === 'stageRect') { this.setStageRect(value as unknown as StageRect); @@ -69,16 +67,16 @@ class Ui extends BaseService { mask?.showRule(value as unknown as boolean); } - (state as any)[name] = value; + state[name] = value; } - public get(name: keyof typeof state): T { - return (state as any)[name]; + public get(name: K) { + return state[name]; } public async zoom(zoom: number) { - this.set('zoom', (this.get('zoom') * 100 + zoom * 100) / 100); - if (this.get('zoom') < 0.1) this.set('zoom', 0.1); + this.set('zoom', (this.get('zoom') * 100 + zoom * 100) / 100); + if (this.get('zoom') < 0.1) this.set('zoom', 0.1); } public async calcZoom() { diff --git a/packages/editor/src/type.ts b/packages/editor/src/type.ts index e1d109b1..e82634f5 100644 --- a/packages/editor/src/type.ts +++ b/packages/editor/src/type.ts @@ -82,6 +82,8 @@ export interface StoreState { pageLength: number; } +export type StoreStateKey = keyof StoreState; + export interface PropsState { propsConfigMap: Record; propsValueMap: Record; @@ -138,9 +140,9 @@ export interface UiState { } export interface EditorNodeInfo { - node?: MNode; - parent?: MContainer; - page?: MPage; + node: MNode | null; + parent: MContainer | null; + page: MPage | null; } export interface AddMNode { diff --git a/packages/editor/src/utils/operator.ts b/packages/editor/src/utils/operator.ts index bcd409f3..4d8d866d 100644 --- a/packages/editor/src/utils/operator.ts +++ b/packages/editor/src/utils/operator.ts @@ -1,8 +1,7 @@ import { toRaw } from 'vue'; import { isEmpty } from 'lodash-es'; -import { Id, MApp, MContainer, MNode } from '@tmagic/schema'; -import StageCore from '@tmagic/stage'; +import { Id, MContainer, MNode } from '@tmagic/schema'; import { isPage } from '@tmagic/utils'; import editorService from '../services/editor'; @@ -18,7 +17,7 @@ import { generatePageNameByApp, getInitPositionStyle } from '../utils/editor'; */ export const beforePaste = async (position: PastePosition, config: MNode[]): Promise => { if (!config[0]?.style) return config; - const curNode = editorService.get('node'); + const curNode = editorService.get('node'); // 将数组中第一个元素的坐标作为参照点 const { left: referenceLeft, top: referenceTop } = config[0].style; // 坐标校准后的粘贴数据 @@ -28,7 +27,7 @@ export const beforePaste = async (position: PastePosition, config: MNode[]): Pro const { offsetX = 0, offsetY = 0, ...positionClone } = position; let pastePosition = positionClone; - if (!isEmpty(pastePosition) && curNode.items) { + if (!isEmpty(pastePosition) && curNode?.items) { // 如果没有传入粘贴坐标则可能为键盘操作,不再转换 // 如果粘贴时选中了容器,则将元素粘贴到容器内,坐标需要转换为相对于容器的坐标 pastePosition = getPositionInContainer(pastePosition, curNode.id); @@ -59,8 +58,9 @@ export const beforePaste = async (position: PastePosition, config: MNode[]): Pro ...pastePosition, }; } - if (isPage(pasteConfig)) { - pasteConfig.name = generatePageNameByApp(editorService.get('root')); + const root = editorService.get('root'); + if (isPage(pasteConfig) && root) { + pasteConfig.name = generatePageNameByApp(root); } return pasteConfig as MNode; }), @@ -76,7 +76,7 @@ export const beforePaste = async (position: PastePosition, config: MNode[]): Pro */ export const getPositionInContainer = (position: PastePosition = {}, id: Id) => { let { left = 0, top = 0 } = position; - const parentEl = editorService.get('stage')?.renderer?.contentWindow?.document.getElementById(`${id}`); + const parentEl = editorService.get('stage')?.renderer?.contentWindow?.document.getElementById(`${id}`); const parentElRect = parentEl?.getBoundingClientRect(); left = left - (parentElRect?.left || 0); top = top - (parentElRect?.top || 0); @@ -87,14 +87,14 @@ export const getPositionInContainer = (position: PastePosition = {}, id: Id) => }; export const getAddParent = (node: MNode) => { - const curNode = editorService.get('node'); + const curNode = editorService.get('node'); let parentNode; if (isPage(node)) { - parentNode = editorService.get('root'); - } else if (curNode.items) { - parentNode = curNode; - } else { + parentNode = editorService.get('root'); + } else if (curNode?.items) { + parentNode = curNode as MContainer; + } else if (curNode?.id) { parentNode = editorService.getParentById(curNode.id, false); } return parentNode; diff --git a/packages/editor/src/utils/stage.ts b/packages/editor/src/utils/stage.ts index 20a12b44..c30e8ac0 100644 --- a/packages/editor/src/utils/stage.ts +++ b/packages/editor/src/utils/stage.ts @@ -1,6 +1,5 @@ import { computed } from 'vue'; -import { MApp, MPage } from '@tmagic/schema'; import StageCore, { GuidesType, SortEventData, UpdateEventData } from '@tmagic/stage'; import editorService from '../services/editor'; @@ -9,10 +8,10 @@ import { H_GUIDE_LINE_STORAGE_KEY, StageOptions, V_GUIDE_LINE_STORAGE_KEY } from import { getGuideLineFromCache } from './editor'; -const root = computed(() => editorService.get('root')); -const page = computed(() => editorService.get('page')); -const zoom = computed(() => uiService.get('zoom') || 1); -const uiSelectMode = computed(() => uiService.get('uiSelectMode')); +const root = computed(() => editorService.get('root')); +const page = computed(() => editorService.get('page')); +const zoom = computed(() => uiService.get('zoom') || 1); +const uiSelectMode = computed(() => uiService.get('uiSelectMode')); const getGuideLineKey = (key: string) => `${key}_${root.value?.id}_${page.value?.id}`; @@ -76,8 +75,9 @@ export const useStage = (stageOptions: StageOptions) => { stage.on('select-parent', () => { const parent = editorService.get('parent'); + if (!parent) throw new Error('父节点为空'); editorService.select(parent); - editorService.get('stage').select(parent.id); + editorService.get('stage')?.select(parent.id); }); stage.on('change-guides', (e) => { diff --git a/packages/editor/tests/unit/services/editor.spec.ts b/packages/editor/tests/unit/services/editor.spec.ts index 41fa4d90..5e99606f 100644 --- a/packages/editor/tests/unit/services/editor.spec.ts +++ b/packages/editor/tests/unit/services/editor.spec.ts @@ -19,7 +19,7 @@ import { beforeAll, describe, expect, test } from 'vitest'; import { cloneDeep } from 'lodash-es'; -import type { MApp, MContainer, MNode } from '@tmagic/schema'; +import type { MApp } from '@tmagic/schema'; import { NodeType } from '@tmagic/schema'; import editorService from '@editor/services/editor'; @@ -74,7 +74,7 @@ enum NodeId { } // mock 页面数据,包含一个页面,两个组件 -const root: MNode = { +const root: MApp = { id: NodeId.ROOT_ID, type: NodeType.ROOT, items: [ @@ -109,7 +109,7 @@ describe('get', () => { test('get', () => { const root = editorService.get('root'); - expect(root.id).toBe(NodeId.ROOT_ID); + expect(root?.id).toBe(NodeId.ROOT_ID); }); test('get undefined', () => { @@ -131,9 +131,9 @@ describe('getNodeInfo', () => { test('异常', () => { const info = editorService.getNodeInfo(NodeId.ERROR_NODE_ID); - expect(info?.node).toBeUndefined(); + expect(info?.node).toBeNull(); expect(info?.parent?.id).toBeUndefined(); - expect(info?.page).toBeUndefined(); + expect(info?.page).toBeNull(); }); }); @@ -147,7 +147,7 @@ describe('getNodeById', () => { test('异常', () => { const node = editorService.getNodeById(NodeId.ERROR_NODE_ID); - expect(node).toBeUndefined(); + expect(node).toBeNull(); }); }); @@ -210,9 +210,9 @@ describe('add', () => { const node = editorService.get('node'); const parent = editorService.get('parent'); if (!Array.isArray(newNode)) { - expect(node.id).toBe(newNode.id); + expect(node?.id).toBe(newNode.id); } - expect(parent.items).toHaveLength(3); + expect(parent?.items).toHaveLength(3); }); test('正常, 当前不是容器', async () => { @@ -226,15 +226,15 @@ describe('add', () => { const node = editorService.get('node'); const parent = editorService.get('parent'); if (!Array.isArray(newNode)) { - expect(node.id).toBe(newNode.id); + expect(node?.id).toBe(newNode.id); } - expect(parent.items).toHaveLength(3); + expect(parent?.items).toHaveLength(3); }); test('往root下添加page', async () => { editorService.set('root', cloneDeep(root)); await editorService.select(NodeId.PAGE_ID); - const rootNode = editorService.get('root'); + const rootNode = editorService.get('root'); const newNode = await editorService.add( { type: NodeType.PAGE, @@ -243,15 +243,15 @@ describe('add', () => { ); const node = editorService.get('node'); if (!Array.isArray(newNode)) { - expect(node.id).toBe(newNode.id); + expect(node?.id).toBe(newNode.id); } - expect(rootNode.items.length).toBe(2); + expect(rootNode?.items.length).toBe(2); }); test.skip('往root下添加普通节点', () => { editorService.set('root', cloneDeep(root)); // 根节点下只能加页面 - const rootNode = editorService.get('root'); + const rootNode = editorService.get('root'); expect(() => editorService.add( { @@ -269,13 +269,13 @@ describe('remove', () => { test('正常', async () => { editorService.remove({ id: NodeId.NODE_ID, type: 'text' }); const node = editorService.getNodeById(NodeId.NODE_ID); - expect(node).toBeUndefined(); + expect(node).toBeNull(); }); test('remove page', async () => { editorService.set('root', cloneDeep(root)); editorService.select(NodeId.PAGE_ID); - const rootNode = editorService.get('root'); + const rootNode = editorService.get('root'); // 先加一个页面 const newPage = await editorService.add( { @@ -283,9 +283,9 @@ describe('remove', () => { }, rootNode, ); - expect(rootNode.items.length).toBe(2); + expect(rootNode?.items.length).toBe(2); await editorService.remove(newPage); - expect(rootNode.items.length).toBe(1); + expect(rootNode?.items.length).toBe(1); }); test.skip('undefine', async () => { @@ -347,11 +347,11 @@ describe('sort', () => { test('正常', async () => { await editorService.select(NodeId.NODE_ID2); - let parent = editorService.get('parent'); - expect(parent.items[0].id).toBe(NodeId.NODE_ID); + let parent = editorService.get('parent'); + expect(parent?.items[0].id).toBe(NodeId.NODE_ID); await editorService.sort(NodeId.NODE_ID2, NodeId.NODE_ID); - parent = editorService.get('parent'); - expect(parent.items[0].id).toBe(NodeId.NODE_ID2); + parent = editorService.get('parent'); + expect(parent?.items[0].id).toBe(NodeId.NODE_ID2); }); }); @@ -371,9 +371,9 @@ describe('moveLayer', () => { test('正常', async () => { // 设置当前编辑的组件 await editorService.select(NodeId.NODE_ID); - const parent = editorService.get('parent'); + const parent = editorService.get('parent'); await editorService.moveLayer(1); - expect(parent.items[0].id).toBe(NodeId.NODE_ID2); + expect(parent?.items[0].id).toBe(NodeId.NODE_ID2); }); }); @@ -385,9 +385,10 @@ describe('undo redo', () => { // 设置当前编辑的组件 await editorService.select(NodeId.NODE_ID); const node = editorService.get('node'); + if (!node) throw new Error('未选中节点'); await editorService.remove(node); const removedNode = editorService.getNodeById(NodeId.NODE_ID); - expect(removedNode).toBeUndefined(); + expect(removedNode).toBeNull(); await editorService.undo(); const undoNode = editorService.getNodeById(NodeId.NODE_ID); expect(undoNode?.id).toBe(NodeId.NODE_ID); diff --git a/playground/src/App.vue b/playground/src/App.vue index 5ec93127..8a25bb00 100644 --- a/playground/src/App.vue +++ b/playground/src/App.vue @@ -2,7 +2,7 @@ -