diff --git a/packages/data-source/src/utils.ts b/packages/data-source/src/utils.ts index 4fa3a303..38b41c5e 100644 --- a/packages/data-source/src/utils.ts +++ b/packages/data-source/src/utils.ts @@ -7,8 +7,7 @@ import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, dataSourceTemplateRegExp, getValueByKeyPath, - isPage, - isPageFragment, + isPageOrFragment, NODE_CONDS_KEY, replaceChildNode, } from '@tmagic/core'; @@ -68,7 +67,7 @@ export const compliedConditions = (node: { [NODE_CONDS_KEY]?: DisplayCond[] }, d }; export const updateNode = (node: MNode, dsl: MApp) => { - if (isPage(node) || isPageFragment(node)) { + if (isPageOrFragment(node)) { const index = dsl.items?.findIndex((child: MNode) => child.id === node.id); dsl.items.splice(index, 1, node as MPage | MPageFragment); } else { diff --git a/packages/editor/src/layouts/sidebar/layer/LayerMenu.vue b/packages/editor/src/layouts/sidebar/layer/LayerMenu.vue index 950ac81d..3805b6f1 100644 --- a/packages/editor/src/layouts/sidebar/layer/LayerMenu.vue +++ b/packages/editor/src/layouts/sidebar/layer/LayerMenu.vue @@ -6,7 +6,7 @@ import { computed, markRaw, useTemplateRef } from 'vue'; import { Files, Plus } from '@element-plus/icons-vue'; -import { isPage, isPageFragment } from '@tmagic/utils'; +import { isPageOrFragment } from '@tmagic/utils'; import ContentMenu from '@editor/components/ContentMenu.vue'; import { useServices } from '@editor/hooks/use-services'; @@ -101,7 +101,7 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() => type: 'button', text: '全部折叠', icon: FolderMinusIcon, - display: () => isPage(node.value) || isPageFragment(node.value), + display: () => isPageOrFragment(node.value), handler: () => { emit('collapse-all'); }, diff --git a/packages/editor/src/layouts/sidebar/layer/use-click.ts b/packages/editor/src/layouts/sidebar/layer/use-click.ts index 515352b0..a06cc7eb 100644 --- a/packages/editor/src/layouts/sidebar/layer/use-click.ts +++ b/packages/editor/src/layouts/sidebar/layer/use-click.ts @@ -2,7 +2,7 @@ import { computed, type ComputedRef, nextTick, type Ref, type ShallowRef } from import { throttle } from 'lodash-es'; import { Id, MNode } from '@tmagic/core'; -import { getElById, isPage, isPageFragment } from '@tmagic/utils'; +import { getElById, isPageOrFragment } from '@tmagic/utils'; import type { LayerNodeStatus, Services, TreeNodeData } from '@editor/type'; import { UI_SELECT_MODE_EVENT_NAME } from '@editor/utils/const'; @@ -36,7 +36,7 @@ export const useClick = ( }; const multiSelect = async (data: MNode) => { - if (isPage(data) || isPageFragment(data)) { + if (isPageOrFragment(data)) { return; } @@ -50,7 +50,7 @@ export const useClick = ( return; } - if (isPage(node) || isPageFragment(node)) { + if (isPageOrFragment(node)) { return; } diff --git a/packages/editor/src/layouts/sidebar/layer/use-node-status.ts b/packages/editor/src/layouts/sidebar/layer/use-node-status.ts index 0bd6c947..0ba6468c 100644 --- a/packages/editor/src/layouts/sidebar/layer/use-node-status.ts +++ b/packages/editor/src/layouts/sidebar/layer/use-node-status.ts @@ -1,7 +1,7 @@ import { computed, onBeforeUnmount, ref, watch } from 'vue'; import type { Id, MApp, MNode, MPage, MPageFragment } from '@tmagic/core'; -import { getNodePath, isPage, isPageFragment, traverseNode } from '@tmagic/utils'; +import { getNodePath, isPageOrFragment, traverseNode } from '@tmagic/utils'; import type { LayerNodeStatus, Services } from '@editor/type'; import { updateStatus } from '@editor/utils/tree'; @@ -136,7 +136,7 @@ export const useNodeStatus = ({ editorService }: Services) => { const addHandler = (newNodes: MNode[]) => { newNodes.forEach((node) => { - if (isPage(node) || isPageFragment(node)) return; + if (isPageOrFragment(node)) return; traverseNode(node, (node: MNode) => { nodeStatusMap.value?.set(node.id, { diff --git a/packages/editor/src/layouts/workspace/viewer/ViewerMenu.vue b/packages/editor/src/layouts/workspace/viewer/ViewerMenu.vue index 95fa0c03..2b1dc75b 100644 --- a/packages/editor/src/layouts/workspace/viewer/ViewerMenu.vue +++ b/packages/editor/src/layouts/workspace/viewer/ViewerMenu.vue @@ -7,7 +7,7 @@ import { computed, markRaw, ref, useTemplateRef, watch } from 'vue'; import { Bottom, Top } from '@element-plus/icons-vue'; import { NodeType } from '@tmagic/core'; -import { isPage, isPageFragment } from '@tmagic/utils'; +import { isPageOrFragment } from '@tmagic/utils'; import ContentMenu from '@editor/components/ContentMenu.vue'; import { useServices } from '@editor/hooks/use-services'; @@ -59,14 +59,14 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() => direction: 'horizontal', display: () => { if (!node.value) return false; - return !isPage(node.value) && !isPageFragment(node.value); + return !isPageOrFragment(node.value); }, }, { type: 'button', text: '上移一层', icon: markRaw(Top), - display: () => !isPage(node.value) && !isPageFragment(node.value) && !props.isMultiSelect, + display: () => !isPageOrFragment(node.value) && !props.isMultiSelect, handler: () => { editorService.moveLayer(1, { historySource: 'stage-contextmenu' }); }, @@ -75,7 +75,7 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() => type: 'button', text: '下移一层', icon: markRaw(Bottom), - display: () => !isPage(node.value) && !isPageFragment(node.value) && !props.isMultiSelect, + display: () => !isPageOrFragment(node.value) && !props.isMultiSelect, handler: () => { editorService.moveLayer(-1, { historySource: 'stage-contextmenu' }); }, @@ -84,7 +84,7 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() => type: 'button', text: '置顶', icon: markRaw(Top), - display: () => !isPage(node.value) && !isPageFragment(node.value) && !props.isMultiSelect, + display: () => !isPageOrFragment(node.value) && !props.isMultiSelect, handler: () => { editorService.moveLayer(LayerOffset.TOP, { historySource: 'stage-contextmenu' }); }, @@ -93,7 +93,7 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() => type: 'button', text: '置底', icon: markRaw(Bottom), - display: () => !isPage(node.value) && !isPageFragment(node.value) && !props.isMultiSelect, + display: () => !isPageOrFragment(node.value) && !props.isMultiSelect, handler: () => { editorService.moveLayer(LayerOffset.BOTTOM, { historySource: 'stage-contextmenu' }); }, @@ -102,7 +102,7 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() => { type: 'divider', direction: 'horizontal', - display: () => !isPage(node.value) && !isPageFragment(node.value) && !props.isMultiSelect, + display: () => !isPageOrFragment(node.value) && !props.isMultiSelect, }, useDeleteMenu('stage-contextmenu'), { diff --git a/packages/editor/src/services/history.ts b/packages/editor/src/services/history.ts index 401f465c..ba0c8136 100644 --- a/packages/editor/src/services/history.ts +++ b/packages/editor/src/services/history.ts @@ -357,7 +357,7 @@ class History extends BaseService { * 派发「页面 / 页面片结构变更」事件(`page-structure-change`)。 * * 常规 `editorService.add` / `remove` 页面节点不会写入 `page` 历史栈(见 editor.add / remove 中 - * 对 isPage / isPageFragment 的分支),因此不会产生任何 historyService 事件。该方法用于在这些 + * 对 isPageOrFragment 的分支),因此不会产生任何 historyService 事件。该方法用于在这些 * 场景(以及 setRoot 整体替换 DSL 增删页面)下,向外统一通知页面结构的增删变化,供业务方感知。 * * 一次操作涉及多个页面时,调用方应把本次增删的页面**合并为一个 change 一次性传入**, diff --git a/packages/editor/src/services/keybinding.ts b/packages/editor/src/services/keybinding.ts index 0c2b4433..e61a7928 100644 --- a/packages/editor/src/services/keybinding.ts +++ b/packages/editor/src/services/keybinding.ts @@ -1,6 +1,6 @@ import KeyController, { KeyControllerEvent } from 'keycon'; -import { isPage, isPageFragment } from '@tmagic/utils'; +import { isPageOrFragment } from '@tmagic/utils'; import { KeyBindingCacheItem, KeyBindingCommand, KeyBindingItem } from '@editor/type'; @@ -19,7 +19,7 @@ class Keybinding extends BaseService { [KeyBindingCommand.DELETE_NODE]: () => { const nodes = editorService.get('nodes'); - if (!nodes || isPage(nodes[0]) || isPageFragment(nodes[0])) return; + if (!nodes || isPageOrFragment(nodes[0])) return; editorService.remove(nodes, { historySource: 'shortcut' }); }, [KeyBindingCommand.COPY_NODE]: () => { @@ -29,7 +29,7 @@ class Keybinding extends BaseService { [KeyBindingCommand.CUT_NODE]: () => { const nodes = editorService.get('nodes'); - if (!nodes || isPage(nodes[0]) || isPageFragment(nodes[0])) return; + if (!nodes || isPageOrFragment(nodes[0])) return; editorService.copy(nodes); editorService.remove(nodes, { historySource: 'shortcut' }); }, diff --git a/packages/editor/src/utils/content-menu.ts b/packages/editor/src/utils/content-menu.ts index 3bfa2312..93ac6ea6 100644 --- a/packages/editor/src/utils/content-menu.ts +++ b/packages/editor/src/utils/content-menu.ts @@ -2,7 +2,7 @@ import { computed, markRaw, type ShallowRef } from 'vue'; import { CopyDocument, Delete, DocumentCopy } from '@element-plus/icons-vue'; import { cloneDeep, Id, MContainer, NodeType } from '@tmagic/core'; -import { calcValueByFontsize, isPage, isPageFragment } from '@tmagic/utils'; +import { calcValueByFontsize, isPage, isPageOrFragment } from '@tmagic/utils'; import ContentMenu from '@editor/components/ContentMenu.vue'; import type { HistoryOpSource, MenuButton, Services } from '@editor/type'; @@ -20,7 +20,7 @@ export const useDeleteMenu = (historySource?: HistoryOpSource): MenuButton => ({ icon: Delete, display: ({ editorService }) => { const node = editorService.get('node'); - return node?.type !== NodeType.ROOT && !isPage(node) && !isPageFragment(node); + return node?.type !== NodeType.ROOT && !isPageOrFragment(node); }, handler: ({ editorService }) => { const nodes = editorService.get('nodes'); diff --git a/packages/editor/src/utils/operator.ts b/packages/editor/src/utils/operator.ts index 61f39113..b45adf70 100644 --- a/packages/editor/src/utils/operator.ts +++ b/packages/editor/src/utils/operator.ts @@ -3,7 +3,7 @@ import { isEmpty } from 'lodash-es'; import type { Id, MContainer, MNode } from '@tmagic/core'; import { NodeType } from '@tmagic/core'; -import { calcValueByFontsize, getElById, isPage, isPageFragment } from '@tmagic/utils'; +import { calcValueByFontsize, getElById, isPage, isPageOrFragment } from '@tmagic/utils'; import editorService from '@editor/services/editor'; import propsService from '@editor/services/props'; @@ -58,7 +58,7 @@ export const beforePaste = (position: PastePosition, config: MNode[], doc?: Docu }; } const root = editorService.get('root'); - if ((isPage(pasteConfig) || isPageFragment(pasteConfig)) && root) { + if (isPageOrFragment(pasteConfig) && root) { pasteConfig.name = generatePageNameByApp(root, isPage(pasteConfig) ? NodeType.PAGE : NodeType.PAGE_FRAGMENT); } return pasteConfig as MNode; diff --git a/packages/editor/tests/unit/initService.spec.ts b/packages/editor/tests/unit/initService.spec.ts index 0f78f6fb..91a66950 100644 --- a/packages/editor/tests/unit/initService.spec.ts +++ b/packages/editor/tests/unit/initService.spec.ts @@ -144,7 +144,6 @@ vi.mock('@tmagic/utils', async () => { ...actual, getDepNodeIds: vi.fn(() => []), getNodes: vi.fn(() => []), - isPage: vi.fn((n: any) => n?.type === 'page'), isValueIncludeDataSource: vi.fn((v: any) => /\$\{/.test(String(v))), }; }); diff --git a/packages/editor/tests/unit/layouts/sidebar/layer/LayerMenu.spec.ts b/packages/editor/tests/unit/layouts/sidebar/layer/LayerMenu.spec.ts index 8b4f7978..7da000c3 100644 --- a/packages/editor/tests/unit/layouts/sidebar/layer/LayerMenu.spec.ts +++ b/packages/editor/tests/unit/layouts/sidebar/layer/LayerMenu.spec.ts @@ -28,15 +28,6 @@ vi.mock('@editor/utils/content-menu', () => ({ useMoveToMenu: () => ({ type: 'button', text: 'moveto' }), })); -vi.mock('@tmagic/utils', async () => { - const actual = await vi.importActual('@tmagic/utils'); - return { - ...actual, - isPage: (n: any) => n?.type === 'page', - isPageFragment: (n: any) => n?.type === 'page-fragment', - }; -}); - const showMock = vi.fn(); vi.mock('@editor/components/ContentMenu.vue', () => ({ default: defineComponent({ diff --git a/packages/editor/tests/unit/layouts/sidebar/layer/use-click.spec.ts b/packages/editor/tests/unit/layouts/sidebar/layer/use-click.spec.ts index 0f4a4d81..c6bd7885 100644 --- a/packages/editor/tests/unit/layouts/sidebar/layer/use-click.spec.ts +++ b/packages/editor/tests/unit/layouts/sidebar/layer/use-click.spec.ts @@ -17,8 +17,6 @@ vi.mock('@tmagic/utils', async () => { const actual = await vi.importActual('@tmagic/utils'); return { ...actual, - isPage: (n: any) => n.type === 'page', - isPageFragment: (n: any) => n.type === 'page-fragment', getElById: () => (_doc: any, id: any) => (id === 'no-el' ? null : { id }), }; }); diff --git a/packages/editor/tests/unit/layouts/sidebar/layer/use-node-status.spec.ts b/packages/editor/tests/unit/layouts/sidebar/layer/use-node-status.spec.ts index 83e6ba38..7d99afc2 100644 --- a/packages/editor/tests/unit/layouts/sidebar/layer/use-node-status.spec.ts +++ b/packages/editor/tests/unit/layouts/sidebar/layer/use-node-status.spec.ts @@ -12,13 +12,7 @@ vi.mock('@tmagic/utils', async () => { const actual = await vi.importActual('@tmagic/utils'); return { ...actual, - isPage: (n: any) => n.type === 'page', - isPageFragment: (n: any) => n.type === 'page-fragment', getNodePath: vi.fn(() => []), - traverseNode: (node: any, fn: any) => { - fn(node); - node.items?.forEach((c: any) => fn(c)); - }, }; }); diff --git a/packages/editor/tests/unit/layouts/workspace/viewer/ViewerMenu.spec.ts b/packages/editor/tests/unit/layouts/workspace/viewer/ViewerMenu.spec.ts index 98415b79..6ce7b2a4 100644 --- a/packages/editor/tests/unit/layouts/workspace/viewer/ViewerMenu.spec.ts +++ b/packages/editor/tests/unit/layouts/workspace/viewer/ViewerMenu.spec.ts @@ -24,11 +24,6 @@ vi.mock('@editor/hooks/use-services', () => ({ useServices: () => ({ editorService }), })); -vi.mock('@tmagic/utils', () => ({ - isPage: (n: any) => n?.type === 'page', - isPageFragment: (n: any) => n?.type === 'page-fragment', -})); - vi.mock('@editor/utils/content-menu', () => ({ useCopyMenu: () => ({ type: 'button', text: '复制', handler: vi.fn() }), usePasteMenu: () => ({ type: 'button', text: '粘贴', handler: vi.fn() }), diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 92b7b57e..bf248fe2 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -142,7 +142,7 @@ export const getNodeInfo = (id: Id, root: { id: Id; items?: MNode[] } | null, sk info.parent = path[path.length - 2] as MContainer; for (const item of path) { - if (isPage(item) || isPageFragment(item)) { + if (isPageOrFragment(item)) { info.page = item as MPage | MPageFragment; break; } @@ -279,6 +279,10 @@ export const isPageFragment = (node?: Pick | null): boolean return Boolean(node.type?.toLowerCase() === NodeType.PAGE_FRAGMENT); }; +/** 是否为页面或页面片 */ +export const isPageOrFragment = (node?: Pick | null): boolean => + isPage(node) || isPageFragment(node); + export const isNumber = (value: any) => (typeof value === 'number' && !isNaN(value)) || /^(-?\d+)(\.\d+)?$/.test(`${value}`); diff --git a/packages/utils/tests/unit/extras.spec.ts b/packages/utils/tests/unit/extras.spec.ts index 43722eb7..f6fb0c53 100644 --- a/packages/utils/tests/unit/extras.spec.ts +++ b/packages/utils/tests/unit/extras.spec.ts @@ -36,6 +36,7 @@ import { isNumber, isObject, isPageFragment, + isPageOrFragment, isPercentage, isPop, isValueIncludeDataSource, @@ -112,6 +113,14 @@ describe('node 类型判断', () => { expect(isPageFragment(null)).toBe(false); }); + test('isPageOrFragment 识别 page 与 page-fragment', () => { + expect(isPageOrFragment({ id: 1, type: NodeType.PAGE })).toBe(true); + expect(isPageOrFragment({ id: 1, type: NodeType.PAGE_FRAGMENT })).toBe(true); + expect(isPageOrFragment({ id: 1, type: 'text' })).toBe(false); + expect(isPageOrFragment(undefined)).toBe(false); + expect(isPageOrFragment(null)).toBe(false); + }); + test('isDslNode 默认为 true,手动 false 关闭', () => { expect(isDslNode({ type: 'text' } as any)).toBe(true); expect(isDslNode({ type: 'text', [IS_DSL_NODE_KEY]: false } as any)).toBe(false);