diff --git a/docs/api/editor/props.md b/docs/api/editor/props.md
index 66dceec9..175ccc94 100644
--- a/docs/api/editor/props.md
+++ b/docs/api/editor/props.md
@@ -1365,13 +1365,34 @@ const guidesOptions = {
- **详情:**
- 用于自定义组件树与画布的右键菜单
+ 用于自定义组件树(图层)、画布、数据源、代码块的右键菜单
- 该函数会在显示右键菜单前被调用,接收默认菜单项作为参数,返回最终显示的菜单项
+ 该函数会在显示右键菜单前被调用,接收默认菜单项与菜单类型作为参数,返回最终显示的菜单项
+
+ 第二个参数 `type` 用于区分菜单来源:`'layer'`(图层)、`'viewer'`(画布)、`'data-source'`(数据源)、`'code-block'`(代码块)
+
+ 第三个参数 `getTarget` 仅在**数据源**与**代码块**面板下传入,用于读取当前右键目标(节点 `id` 与原始数据);图层 / 画布不会传入该参数
- **默认值:** `(menus) => menus`
-- **类型:** `(menus: (MenuButton | MenuComponent)[], data: { node?: MNode; page?: MPage; parent?: MContainer; stage?: StageCore }) => (MenuButton | MenuComponent)[]`
+- **类型:**
+
+```ts
+interface ContentMenuTarget {
+ /** 目标 ID */
+ id: string;
+ /** 原始节点数据(树节点等) */
+ data?: TreeNodeData;
+}
+
+type ContentMenuType = 'layer' | 'data-source' | 'viewer' | 'code-block';
+
+type CustomContentMenuFunction = (
+ menus: (MenuButton | MenuComponent)[],
+ type: ContentMenuType,
+ getTarget?: () => ContentMenuTarget | null,
+) => (MenuButton | MenuComponent)[];
+```
- **示例:**
@@ -1381,20 +1402,24 @@ const guidesOptions = {
```
diff --git a/packages/editor/src/layouts/sidebar/code-block/CodeBlockListPanel.vue b/packages/editor/src/layouts/sidebar/code-block/CodeBlockListPanel.vue
index 47f83419..27cdb60b 100644
--- a/packages/editor/src/layouts/sidebar/code-block/CodeBlockListPanel.vue
+++ b/packages/editor/src/layouts/sidebar/code-block/CodeBlockListPanel.vue
@@ -121,8 +121,11 @@ const {
nodeContentMenuHandler,
menuData: contentMenuData,
contentMenuHideHandler,
+ getTarget,
} = useContentMenu((id: string) => {
codeBlockListRef.value?.deleteCode(id, { historySource: 'tree-contextmenu' });
});
-const menuData = computed<(MenuButton | MenuComponent)[]>(() => props.customContentMenu(contentMenuData, 'code-block'));
+const menuData = computed<(MenuButton | MenuComponent)[]>(() =>
+ props.customContentMenu(contentMenuData, 'code-block', getTarget),
+);
diff --git a/packages/editor/src/layouts/sidebar/code-block/useContentMenu.ts b/packages/editor/src/layouts/sidebar/code-block/useContentMenu.ts
index 47808b3a..c0eed424 100644
--- a/packages/editor/src/layouts/sidebar/code-block/useContentMenu.ts
+++ b/packages/editor/src/layouts/sidebar/code-block/useContentMenu.ts
@@ -3,13 +3,21 @@ import { CopyDocument, Delete, Edit } from '@element-plus/icons-vue';
import { cloneDeep } from 'lodash-es';
import ContentMenu from '@editor/components/ContentMenu.vue';
-import type { EventBus, MenuButton, MenuComponent, TreeNodeData } from '@editor/type';
+import type { ContentMenuTarget, EventBus, MenuButton, MenuComponent, TreeNodeData } from '@editor/type';
export const useContentMenu = (deleteCode: (id: string) => void) => {
const eventBus = inject('eventBus');
const menuRef = useTemplateRef>('menu');
let selectId = '';
+ let selectData: TreeNodeData | null = null;
+
+ const getTarget = (): ContentMenuTarget | null => {
+ if (!selectId) {
+ return null;
+ }
+ return { id: selectId, data: selectData ?? undefined };
+ };
const menuData: (MenuButton | MenuComponent)[] = [
{
@@ -65,19 +73,23 @@ export const useContentMenu = (deleteCode: (id: string) => void) => {
menuRef.value?.show(event);
if (data.id) {
selectId = `${data.id}`;
+ selectData = data;
} else {
selectId = '';
+ selectData = null;
}
}
};
const contentMenuHideHandler = () => {
selectId = '';
+ selectData = null;
};
return {
menuData,
nodeContentMenuHandler,
contentMenuHideHandler,
+ getTarget,
};
};
diff --git a/packages/editor/src/layouts/sidebar/data-source/DataSourceListPanel.vue b/packages/editor/src/layouts/sidebar/data-source/DataSourceListPanel.vue
index 735d074f..b04964ce 100644
--- a/packages/editor/src/layouts/sidebar/data-source/DataSourceListPanel.vue
+++ b/packages/editor/src/layouts/sidebar/data-source/DataSourceListPanel.vue
@@ -171,8 +171,8 @@ eventBus?.on('remove-data-source', (id: string) => {
removeHandler(id);
});
-const { nodeContentMenuHandler, menuData: contentMenuData, contentMenuHideHandler } = useContentMenu();
+const { nodeContentMenuHandler, menuData: contentMenuData, contentMenuHideHandler, getTarget } = useContentMenu();
const menuData = computed<(MenuButton | MenuComponent)[]>(() =>
- props.customContentMenu(contentMenuData, 'data-source'),
+ props.customContentMenu(contentMenuData, 'data-source', getTarget),
);
diff --git a/packages/editor/src/layouts/sidebar/data-source/useContentMenu.ts b/packages/editor/src/layouts/sidebar/data-source/useContentMenu.ts
index 2a2e704b..5c1c650e 100644
--- a/packages/editor/src/layouts/sidebar/data-source/useContentMenu.ts
+++ b/packages/editor/src/layouts/sidebar/data-source/useContentMenu.ts
@@ -3,13 +3,21 @@ import { CopyDocument, Delete, Edit } from '@element-plus/icons-vue';
import { cloneDeep } from 'lodash-es';
import ContentMenu from '@editor/components/ContentMenu.vue';
-import type { EventBus, MenuButton, MenuComponent, TreeNodeData } from '@editor/type';
+import type { ContentMenuTarget, EventBus, MenuButton, MenuComponent, TreeNodeData } from '@editor/type';
export const useContentMenu = () => {
const eventBus = inject('eventBus');
const menuRef = useTemplateRef>('menu');
let selectId = '';
+ let selectData: TreeNodeData | null = null;
+
+ const getTarget = (): ContentMenuTarget | null => {
+ if (!selectId) {
+ return null;
+ }
+ return { id: selectId, data: selectData ?? undefined };
+ };
const menuData: (MenuButton | MenuComponent)[] = [
{
@@ -63,19 +71,23 @@ export const useContentMenu = () => {
menuRef.value?.show(event);
if (data.id) {
selectId = `${data.id}`;
+ selectData = data;
} else {
selectId = '';
+ selectData = null;
}
}
};
const contentMenuHideHandler = () => {
selectId = '';
+ selectData = null;
};
return {
menuData,
nodeContentMenuHandler,
contentMenuHideHandler,
+ getTarget,
};
};
diff --git a/packages/editor/src/type.ts b/packages/editor/src/type.ts
index 0d302bd7..a43d477c 100644
--- a/packages/editor/src/type.ts
+++ b/packages/editor/src/type.ts
@@ -1207,9 +1207,24 @@ export interface PageBarSortOptions extends PartSortableOptions {
}
// #endregion PageBarSortOptions
+/**
+ * 右键菜单当前目标(侧栏树节点等)。
+ * 数据源 / 代码块面板通过 `customContentMenu` 的 `getTarget` 暴露,业务在 handler 内自行读取。
+ */
+export interface ContentMenuTarget {
+ /** 目标 ID */
+ id: string;
+ /** 原始节点数据(树节点等) */
+ data?: TreeNodeData;
+}
+
+export type ContentMenuType = 'layer' | 'data-source' | 'viewer' | 'code-block';
+
export type CustomContentMenuFunction = (
menus: (MenuButton | MenuComponent)[],
- type: 'layer' | 'data-source' | 'viewer' | 'code-block',
+ type: ContentMenuType,
+ /** 读取当前右键目标;数据源 / 代码块面板会传入,图层 / 画布一般不需要 */
+ getTarget?: () => ContentMenuTarget | null,
) => (MenuButton | MenuComponent)[];
export interface EditorEvents {
diff --git a/packages/editor/tests/unit/layouts/sidebar/code-block/CodeBlockListPanel.spec.ts b/packages/editor/tests/unit/layouts/sidebar/code-block/CodeBlockListPanel.spec.ts
index ca653ee2..d9bb20be 100644
--- a/packages/editor/tests/unit/layouts/sidebar/code-block/CodeBlockListPanel.spec.ts
+++ b/packages/editor/tests/unit/layouts/sidebar/code-block/CodeBlockListPanel.spec.ts
@@ -48,6 +48,7 @@ vi.mock('@editor/layouts/sidebar/code-block/useContentMenu', () => ({
return menuDataState.items;
},
contentMenuHideHandler,
+ getTarget: () => null,
}),
}));
diff --git a/packages/editor/tests/unit/layouts/sidebar/code-block/useContentMenu.spec.ts b/packages/editor/tests/unit/layouts/sidebar/code-block/useContentMenu.spec.ts
index c153fca1..9a99feb3 100644
--- a/packages/editor/tests/unit/layouts/sidebar/code-block/useContentMenu.spec.ts
+++ b/packages/editor/tests/unit/layouts/sidebar/code-block/useContentMenu.spec.ts
@@ -133,4 +133,16 @@ describe('code-block useContentMenu', () => {
(result.menuData[2] as any).handler({});
expect(deleteCode).not.toHaveBeenCalled();
});
+
+ test('getTarget 返回当前右键目标', () => {
+ const deleteCode = vi.fn();
+ const { result } = mountHook(deleteCode);
+ expect(result.getTarget()).toBeNull();
+
+ result.nodeContentMenuHandler({ preventDefault: vi.fn() } as any, { type: 'code', id: 'c1', name: 'code1' } as any);
+ expect(result.getTarget()).toEqual({ id: 'c1', data: { type: 'code', id: 'c1', name: 'code1' } });
+
+ result.contentMenuHideHandler();
+ expect(result.getTarget()).toBeNull();
+ });
});
diff --git a/packages/editor/tests/unit/layouts/sidebar/data-source/DataSourceListPanel.spec.ts b/packages/editor/tests/unit/layouts/sidebar/data-source/DataSourceListPanel.spec.ts
index 335065fa..6b0d2244 100644
--- a/packages/editor/tests/unit/layouts/sidebar/data-source/DataSourceListPanel.spec.ts
+++ b/packages/editor/tests/unit/layouts/sidebar/data-source/DataSourceListPanel.spec.ts
@@ -38,12 +38,14 @@ vi.mock('@editor/hooks/use-data-source-edit', () => ({
const nodeContentMenuHandler = vi.fn();
const contentMenuHideHandler = vi.fn();
-const menuData = ref([{ type: 'button', text: 'Edit' }]);
+const menuData = [{ type: 'button', text: 'Edit' }];
+const getTarget = vi.fn(() => null);
vi.mock('@editor/layouts/sidebar/data-source/useContentMenu', () => ({
useContentMenu: () => ({
nodeContentMenuHandler,
menuData,
contentMenuHideHandler,
+ getTarget,
}),
}));
diff --git a/packages/editor/tests/unit/layouts/sidebar/data-source/useContentMenu.spec.ts b/packages/editor/tests/unit/layouts/sidebar/data-source/useContentMenu.spec.ts
index 45a09443..a1b35e51 100644
--- a/packages/editor/tests/unit/layouts/sidebar/data-source/useContentMenu.spec.ts
+++ b/packages/editor/tests/unit/layouts/sidebar/data-source/useContentMenu.spec.ts
@@ -126,4 +126,15 @@ describe('data-source useContentMenu', () => {
(result.menuData[2] as any).handler({});
expect(eventBus.emit).not.toHaveBeenCalled();
});
+
+ test('getTarget 返回当前右键目标', () => {
+ const { result } = mountHook();
+ expect(result.getTarget()).toBeNull();
+
+ result.nodeContentMenuHandler({ preventDefault: vi.fn() } as any, { type: 'ds', id: 'd1', name: 'ds1' } as any);
+ expect(result.getTarget()).toEqual({ id: 'd1', data: { type: 'ds', id: 'd1', name: 'ds1' } });
+
+ result.contentMenuHideHandler();
+ expect(result.getTarget()).toBeNull();
+ });
});