mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-07-15 07:42:13 +08:00
feat(editor): 扩展 customContentMenu 支持 getTarget 获取右键目标
为数据源与代码块面板的右键菜单传入 getTarget 回调,便于业务在自定义菜单 handler 中读取当前节点 id 与原始数据。
This commit is contained in:
parent
734e6a8ee6
commit
bbe73aae64
@ -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 = {
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const customContentMenu = (menus, { node }) => {
|
||||
// 为特定类型的组件添加自定义菜单
|
||||
if (node?.type === 'container') {
|
||||
const customContentMenu = (menus, type, getTarget) => {
|
||||
// 为数据源右键菜单追加自定义菜单
|
||||
if (type === 'data-source') {
|
||||
menus.push({
|
||||
type: 'button',
|
||||
text: '清空容器',
|
||||
text: '查看详情',
|
||||
handler: () => {
|
||||
// 清空容器的逻辑
|
||||
// getTarget 仅在数据源 / 代码块面板下传入
|
||||
const target = getTarget?.();
|
||||
if (target) {
|
||||
console.log(target.id, target.data);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 可以过滤掉某些菜单项
|
||||
return menus.filter(menu => menu.text !== '删除');
|
||||
return menus.filter((menu) => menu.text !== '删除');
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
@ -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),
|
||||
);
|
||||
</script>
|
||||
|
||||
@ -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>('eventBus');
|
||||
const menuRef = useTemplateRef<InstanceType<typeof ContentMenu>>('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,
|
||||
};
|
||||
};
|
||||
|
||||
@ -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),
|
||||
);
|
||||
</script>
|
||||
|
||||
@ -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>('eventBus');
|
||||
const menuRef = useTemplateRef<InstanceType<typeof ContentMenu>>('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,
|
||||
};
|
||||
};
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -48,6 +48,7 @@ vi.mock('@editor/layouts/sidebar/code-block/useContentMenu', () => ({
|
||||
return menuDataState.items;
|
||||
},
|
||||
contentMenuHideHandler,
|
||||
getTarget: () => null,
|
||||
}),
|
||||
}));
|
||||
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@ -38,12 +38,14 @@ vi.mock('@editor/hooks/use-data-source-edit', () => ({
|
||||
|
||||
const nodeContentMenuHandler = vi.fn();
|
||||
const contentMenuHideHandler = vi.fn();
|
||||
const menuData = ref<any[]>([{ 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,
|
||||
}),
|
||||
}));
|
||||
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user