菜单与 Ribbon 扩展
文档:
vjcad/doc/plugin/plugin-menu-ribbon.md示例:vjcad/example/plugin/04pluginmanager.js、vjcad/example/ui/16menucustom.js
1. 默认菜单 ID
| ID | 名称 | 说明 |
|---|---|---|
file |
文件 | 新建、打开、保存、导出 |
create |
创建 | 直线、圆、多段线等绘图命令 |
edit |
编辑 | 移动、复制、旋转等 |
view |
显示 | 视图控制、显示顺序、图层 |
insert |
插入 | 块、图像 |
dim |
标注 | 尺寸标注 |
property |
属性 | 实体属性 |
tool |
工具 | 测量、统计、脚本 |
appearance |
外观 | 界面外观 |
help |
帮助 | 帮助、关于 |
2. 默认 Ribbon 标签 / 组
| Tab ID | Group ID | 名称 |
|---|---|---|
default(默认) |
draw / modify / edit / annotation / layer / navigation |
绘图、修改、编辑、注释、图层特性、导航 |
tools(工具) |
quick-tools / group / script / clip-region / purge / properties / measure / stats / help |
快捷工具、组、脚本、裁剪、清理、特性、测量、统计、帮助 |
insert(插入) |
block / image / symbol / template / import |
块、图像、符号、模板、导入 |
plugins(插件) |
plugin-manager |
插件管理(组标题为空) |
recent(最近命令) |
recent-commands |
最近命令(动态填充,组标题为空) |
以上取自
defaultRibbonConfig(vjcad@1.0.16);运行时以RibbonConfigManager.getConfig().tabs为准。
3. 三套写法
3.1 插件运行时(PluginContext)
context.addMenu({ id: 'my-tools', label: '我的工具', after: 'tool' });
context.addMenuItem('my-tools', { command: 'TOOL1' });
context.addRibbonTab({ id: 'my', label: '我的', groups: [] }, 'tools');
context.addRibbonGroup('my', {
id: 'main', label: '主要',
primaryButtons: [{ icon: 'tool1', cmd: 'TOOL1', prompt: '工具 1', type: 'large' }]
});
3.2 MainView 一次性配置(增量)
new MainView({
// ...其他 MainViewConfig 字段
menuBar: {
removeMenus: ['help', 'appearance'],
addMenus: [{
id: 'custom-menu', label: '自定义菜单', after: 'insert',
items: [
{ type: 'command', command: 'DRAW_STAR' },
{ type: 'separator' },
{ type: 'command', command: 'REGEN' }
]
}],
modifyMenus: {
file: {
removeItems: ['SWITCHWORKSPACE'],
addItems: [{ type: 'command', command: 'DRAW_STAR', after: 'EXPORTPNG' }]
}
}
},
ribbon: {
removeTabs: ['recent'],
addTabs: [{
tab: {
id: 'custom-tab', label: '自定义',
groups: [{
id: 'custom-draw', label: '自定义绘图',
primaryButtons: [
{ icon: 'line', cmd: 'DRAW_STAR', prompt: '五角星', label: '五角星' }
]
}]
},
after: 'default'
}],
modifyTabs: {
default: {
modifyGroups: {
draw: {
addPrimaryButtons: [
{ icon: 'circle', cmd: 'DRAW_STAR', prompt: '五角星',
label: '★', after: 'CIRCLE' }
]
}
}
}
}
}
});
完整代码:
vjcad/example/ui/16menucustom.js
3.3 MainView 全量替换
new MainView({
menuBar: {
menus: [
{ id: 'my-file', label: '文件', items: [
{ type: 'command', command: 'NEW' },
{ type: 'command', command: 'OPEN' },
{ type: 'separator' },
{ type: 'command', command: 'SAVELOCAL', shortcut: 'Ctrl+S' }
]},
{ id: 'my-draw', label: '绘图', items: [
{ type: 'command', command: 'LINE' },
{ type: 'command', command: 'CIRCLE' }
]}
]
},
ribbon: {
config: { tabs: [ /* RibbonConfig 完整定义 */ ] }
}
});
menus 与 config 二选一;提供它们就 忽略所有默认菜单 / Ribbon。
4. 数据结构
菜单项
type MenuItemDef =
| { type: 'command'; command: string; label?: string; icon?: string; shortcut?: string; column?: number }
| { type: 'separator'; column?: number }
| { type: 'submenu'; label: string; icon?: string; column?: number; children: MenuItemDef[] };
Ribbon 按钮
type RibbonButtonType = 'large' | 'small' | 'icon-only' | 'list';
interface RibbonButtonConfig {
icon: string; // 图标 ID(registerIcon 名)
cmd: string; // 命令名
prompt?: string; // 工具提示
label?: string; // 紧凑模式显示文本
type?: RibbonButtonType;
items?: RibbonButtonConfig[]; // type='list' 时下拉子项
}
Ribbon 组
type RibbonGroupDisplayMode = 'large' | 'compact' | 'small-icons' | 'layer' | 'recent';
interface RibbonGroupConfig {
id: string;
label: string;
pinnable?: boolean;
displayMode?: RibbonGroupDisplayMode;
primaryButtons: RibbonButtonConfig[];
moreButtons?: RibbonButtonConfig[];
}
Ribbon 标签
interface RibbonTabConfig {
id: string;
label: string;
groups: RibbonGroupConfig[];
}
5. 实战:完整插件示例(MYQSELECT)
const ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="18" height="18" rx="2" stroke-width="2"/>
<path d="M9 12l2 2 4-4" stroke-width="2"/>
</svg>`;
// 源码写法:按 URL 加载需打包成 IIFE(见 overview §3);直接用这段字符串则走 pm.loadFromContent
export default {
manifest: { id: 'my-quick-select', name: '我的快速选择', version: '1.0.0' },
async onActivate(context) {
context.registerCommand('MYQSELECT', '我的快速选择', MyQuickSelectCommand);
context.registerIcon('MYQSELECT', ICON); // 菜单与 Ribbon 共用(键名大小写不敏感)
context.addMenu({ id: 'plugins', label: '插件', after: 'tool' });
context.addMenuItem('plugins', { command: 'MYQSELECT' });
context.addRibbonTab({ id: 'plugins', label: '插件', groups: [] });
context.addRibbonGroup('plugins', {
id: 'my-quick-select', label: '我的快速选择', pinnable: true,
primaryButtons: [{ icon: 'myqselect', cmd: 'MYQSELECT', prompt: '快速选择图元', type: 'large' }]
});
},
async onDeactivate(context) {
context.removeRibbonGroup('plugins', 'my-quick-select');
context.removeMenuItem('plugins', 'MYQSELECT');
context.unregisterCommand('MYQSELECT');
}
};
6. 命名规范
| 元素 | 规则 | 例 |
|---|---|---|
| 命令名 | 全大写 | DRAWCROSS |
| 图标名 | 与命令同名;注册表统一转小写,大小写不敏感,注册一次菜单与 Ribbon 共用 | DRAWCROSS ≡ drawcross |
| 主菜单 / Ribbon ID | kebab-case | my-plugin |
7. 三种写法对比
| 维度 | PluginContext | MainView 增量 | MainView 全量 |
|---|---|---|---|
| 时机 | 运行时(激活/停用) | 启动一次 | 启动一次 |
| 粒度 | 逐项 | 批量增量 | 整体替换 |
| 自动清理 | 是(onDeactivate) | 否(生命周期跟随 MainView) | 否 |
| 适合 | 第三方插件 | 应用级定制(删/改默认) | 完全自定义 |
红旗
- 默认主菜单 / Ribbon 的 ID 写错(如
tools↔tool)会让命令藏起来;记牢上面默认表 - 增量与全量同时写时,全量赢;调试时不要混用
addMenu/addRibbonTab重名是 no-op,不能用于"重置";要替换请先 remove 再 add- 图标键名注册与查找都转小写:菜单按命令名找图标、Ribbon 按钮按
icon找图标,大小写都无所谓,不必为大小写各注册一份 - 移除按钮时
removeRibbonButton(tab, group, cmd)用的是 cmd 名,不是 button 的 id - 命令名不要与内置命令重名(
QSELECT、FINDREPLACE等已是内置命令):registerCommand是直接 push、查找按首个匹配,重名会被内置命令遮蔽,且unregisterCommand会误删内置项
参考(官方文档 MCP)
- 文档:
vjcad/doc/plugin/plugin-menu-ribbon.md - 示例:
vjcad/example/plugin/04pluginmanager.js、vjcad/example/ui/16menucustom.js