唯杰地图 logo唯杰地图
VJMAP
EN

右键菜单

vjcad/references/ui/contextmenu.md 7.5 KB179 行
原文

文档:vjcad/doc/ui/contextmenu.md 示例:

  • vjcad/example/ui/06contextmenu.js — 加自定义项
  • vjcad/example/ui/06contextmenudisable.js — 完全禁用
  • vjcad/example/ui/06contextmenucommand.js — 按命令状态定制
  • vjcad/example/ui/06contextmenucustom.js — 完全替换默认项

1. 入口:监听 ContextMenuOpening

vjcad 不提供"硬注册"右键项的 API,而是统一通过事件 ContextMenuOpening 在打开前修改菜单。

const events = CadEventManager.getInstance();

events.on(CadEvents.ContextMenuOpening, args => {
    // args.menuItems     默认菜单项数组(直接修改也可以)
    // args.prependItems  追加在最前
    // args.appendItems   追加在最后
    // args.useDefaultItems  是否显示默认项(默认 true)
    // args.cancel        是否取消显示菜单
});

事件参数(关键字段):

字段 含义
screenX / screenY 屏幕坐标
canvasX / canvasY 画布坐标
isCommandActive 当前是否有命令在执行
activeCommandName 当前命令名(如 LINE)
useDefaultItems 控制是否显示默认菜单项
cancel 设为 true 完全不弹

2. 菜单项配置

interface ContextMenuItemInfo {
    label?: string;                       // 文本(分隔线时可省)
    icon?: string;                        // 图标名,不带 .svg;只在 IconRegistry 的 Commands 分类里查
    shortcut?: string;                    // ⚠️ 类型里有,但渲染时被忽略
    command?: string;                     // 直接执行 vjcad 命令
    callback?: () => void;                // 自定义点击回调
    submenu?: ContextMenuItemInfo[];      // 二级菜单
    disabled?: boolean;                   // ⚠️ 被忽略(不会置灰,照样可点)
    checked?: boolean;                    // ⚠️ 被忽略(不显示勾选)
    isSeparator?: boolean;                // 不是真分隔线:渲染成一行文字 "---"(可点击,点了等于执行不存在的命令 "---",静默无效)
}

command 与 callback 二选一:填了 command,点击会自动 Engine.editor.executerWithOp(command);有 callback 时优先调 callback。转换成菜单行时只读取 label / command / icon / submenu / callback / isSeparator——要"禁用"某项请干脆不 push,或在 callback 里自己判断后 return。

icon 会被拼成 <icon>.svg 再剥掉扩展名,按小写在 IconCategory.Commands 里查找:写 'tool.svg' 会查 'tool.svg' 而落空;自定义图标要用 IconRegistry.registerCommandIcon(name, svg)(或插件 context.registerIcon)注册到 Commands 分类。

3. 四种典型用法

3.1 在默认菜单前后追加

events.on(CadEvents.ContextMenuOpening, args => {
    // 前置:含子菜单
    args.prependItems.push({
        label: '我的工具', icon: 'tool',   // 内置 Commands 分类没有 'tool',不先注册就不显示图标
        submenu: [
            { label: '画圆', callback: () => {
                const c = new CircleEnt([50, 50], 15); c.setDefaults();
                Engine.addEntities(c);
            } },
            { label: '画线', callback: () => {
                const l = new LineEnt([0, 0], [100, 100]); l.setDefaults();
                Engine.addEntities(l);
            } }
        ]
    });
    args.prependItems.push({ isSeparator: true });

    // 后置:分隔 + 缩放全图
    args.appendItems.push({ isSeparator: true });
    args.appendItems.push({
        label: '缩放全图  Z+E',             // shortcut 字段不渲染,要提示就写进 label
        callback: () => Engine.zoomExtents()
    });
});

3.2 完全禁用系统菜单

events.on(CadEvents.ContextMenuOpening, args => {
    args.cancel = true;
});

3.3 按命令状态定制

events.on(CadEvents.ContextMenuOpening, args => {
    if (args.isCommandActive) {
        if (args.activeCommandName === 'LINE') {
            args.appendItems.push({ label: '闭合', callback: () => { /* close */ } });
        }
    } else {
        args.prependItems.push({
            label: '快捷命令',
            submenu: [
                { label: '画线', command: 'LINE' },
                { label: '画圆', command: 'CIRCLE' }
            ]
        });
    }
});

isCommandActive 让你区分"空闲右键"和"命令进行中右键"。在命令中右键通常用于补充选项(闭合 / 撤销最后一步 / 取消)。

3.4 完全替换默认项

events.on(CadEvents.ContextMenuOpening, args => {
    args.useDefaultItems = false;
    args.prependItems.push({ label: '自定义项 1', callback: () => {} });
    args.prependItems.push({ label: '自定义项 2', callback: () => {} });
    args.prependItems.push({ isSeparator: true });
    args.prependItems.push({ label: '缩放全图',  callback: () => Engine.zoomExtents() });
});

4. 相关事件

事件 说明
ContextMenuOpening 打开前(可改、可取消)
ContextMenuOpened 已打开(仅通知)
ContextMenuItemClicked 枚举里有,但从不派发(源码无 emit 点);要感知点击请在各项 callback 里处理

5. 实战模板:基于选中实体显示

events.on(CadEvents.ContextMenuOpening, args => {
    const sel = ssGetFirst() ?? [];
    if (sel.length === 0) return;            // 没选中就不动菜单

    args.prependItems.push({ isSeparator: true });
    args.prependItems.push({
        label: `属性 (${sel.length})`,
        callback: () => Engine.editor.executerWithOp('PROPERTIES')
    });
    if (sel.every(e => e.type === 'LINE')) {
        args.prependItems.push({
            label: '直线长度求和',
            callback: () => {
                const total = sel.reduce((s, e) => s + e.Length, 0);
                message.info(`总长 ${total.toFixed(2)}`);
            }
        });
    }
});

红旗

  • 监听 ContextMenuOpening 不会自动解绑;不需要时记得 events.off,否则插件多次激活会重复添加
  • args.menuItems 是 默认菜单项,直接 splice / push 也能改但不推荐;优先用 prependItems / appendItems / useDefaultItems
  • command 字段走 executerWithOp,它会先自动 ESC 掉当前命令(awaitDispatchEscapeEvent():派发 Escape 并等命令结束)再执行新命令,不会冲突;callback 里要手动取消当前命令可 await Engine.editor.awaitDispatchEscapeEvent()。默认菜单里的"取消"项即 ContextMenuAction.CANCEL_INPUT(等效 ESC)
  • 子菜单 submenu 内的项不能再嵌套 submenu(仅支持 1 级)
  • 画布上的浏览器原生右键菜单始终被屏蔽(contextmenu 回调第一步就 preventDefault()),cancel = true 只是不弹 vjcad 菜单,不存在"fallback 到浏览器原生菜单"
  • MainView 配置 enableContextMenu: false(或 viewer 模式未显式开启)时右键菜单被整体卸掉,ContextMenuOpening 根本不会触发,监听写了也不生效

参考(官方文档 MCP)

  • 文档:vjcad/doc/ui/contextmenu.md
  • 示例:
  • vjcad/example/ui/06contextmenu.js — 加自定义项
  • vjcad/example/ui/06contextmenudisable.js — 完全禁用
  • vjcad/example/ui/06contextmenucommand.js — 按命令状态定制
  • vjcad/example/ui/06contextmenucustom.js — 完全替换默认项