右键菜单
文档:
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 / useDefaultItemscommand字段走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— 完全替换默认项