新增自定义菜单项

在 Univer 中,无论是顶部的工具栏菜单(Ribbon)还是右键菜单(Context Menu),都可以通过编写插件来实现扩展。下面将介绍如何使用依赖注入系统中的 IMenuManagerService 来注册菜单项。

预览

1. 插件环境

确保你对插件机制有所了解。

首先构造一个 controller 类,用于注册菜单项命令、菜单项图标、菜单项配置。

TypeScript
import { Disposable, ICommandService, Inject, Injector } from '@univerjs/core'import { ComponentManager, IMenuManagerService } from '@univerjs/ui'export class CustomMenuController extends Disposable {  constructor(    @Inject(Injector) private readonly _injector: Injector,    @ICommandService private readonly _commandService: ICommandService,    @IMenuManagerService private readonly _menuManagerService: IMenuManagerService,    @Inject(ComponentManager) private readonly _componentManager: ComponentManager,  ) {    super()    this._initCommands()    this._registerComponents()    this._initMenus()  }  /**   * register commands   */  private _initCommands(): void {}  /**   * register icon components   */  private _registerComponents(): void {}  /**   * register menu items   */  private _initMenus(): void {}}

将这个 controller 注册到插件中

TypeScript
import type { Dependency } from '@univerjs/core'import { Inject, Injector, Plugin, touchDependencies, UniverInstanceType } from '@univerjs/core'import { CustomMenuController } from './controllers/custom-menu.controller'const SHEET_CUSTOM_MENU_PLUGIN = 'SHEET_CUSTOM_MENU_PLUGIN'export class UniverSheetsCustomMenuPlugin extends Plugin {  static override type = UniverInstanceType.UNIVER_SHEET  static override pluginName = SHEET_CUSTOM_MENU_PLUGIN  constructor(@Inject(Injector) protected readonly _injector: Injector) {    super()  }  override onStarting(): void {    ;([[CustomMenuController]] as Dependency[]).forEach((d) => this._injector.add(d))  }  override onRendered(): void {    touchDependencies(this._injector, [[CustomMenuController]])  }}

2. 菜单项命令

注册菜单之前,需要构造一个 Command,这个 Command 会在菜单被点击时执行。

TypeScript
import type { IAccessor, ICommand } from '@univerjs/core'import { CommandType } from '@univerjs/core'export const SingleButtonOperation: ICommand = {  id: 'custom-menu.operation.single-button',  type: CommandType.OPERATION,  handler: async (accessor: IAccessor) => {    console.log('Single button operation')    return true  },}

将这个 Command 注册到 ICommandService

TypeScript
import { SingleButtonOperation } from '../commands/operations/single-button.operation'export class CustomMenuController extends Plugin {  private _initCommands(): void {    ;[SingleButtonOperation].forEach((c) => {      this.disposeWithMe(this._commandService.registerCommand(c))    })  }}

3. 菜单项图标

如果你的菜单项需要图标,也需要提前注册图标。

先构造一个图标 tsx 组件

TSX
export function ButtonIcon() {  return (    <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">      <path        fill="currentColor"        d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2m.16 14a6.981 6.981 0 0 0-5.147 2.256A7.966 7.966 0 0 0 12 20a7.97 7.97 0 0 0 5.167-1.892A6.979 6.979 0 0 0 12.16 16M12 4a8 8 0 0 0-6.384 12.821A8.975 8.975 0 0 1 12.16 14a8.972 8.972 0 0 1 6.362 2.634A8 8 0 0 0 12 4m0 1a4 4 0 1 1 0 8a4 4 0 0 1 0-8m0 2a2 2 0 1 0 0 4a2 2 0 0 0 0-4"      />    </svg>  )}

将这个图标注册到 ComponentManager

TypeScript
import { ButtonIcon } from '../components/button-icon/ButtonIcon'export class CustomMenuController extends Plugin {  private _registerComponents(): void {    this.disposeWithMe(this._componentManager.register('ButtonIcon', ButtonIcon))  }}

4. 菜单项国际化

如果你的菜单项需要国际化,需要提前添加好国际化资源。

TypeScript
export default {  customMenu: {    button: '按钮',    singleButton: '单个按钮',  },}
TypeScript
export default {  customMenu: {    button: 'Button',    singleButton: 'Single button',  },}

将这个国际化资源注册到 ILocaleService

TypeScript
import { LocaleService } from '@univerjs/core'import enUS from './locale/en-US'import zhCN from './locale/zh-CN'export class UniverSheetsCustomMenuPlugin extends Plugin {  static override type = UniverInstanceType.UNIVER_SHEET  static override pluginName = SHEET_CUSTOM_MENU_PLUGIN  constructor(    @Inject(Injector) protected readonly _injector: Injector,    @Inject(LocaleService) private readonly _localeService: LocaleService,  ) {    super()    this._localeService.load({      enUS,      zhCN,    })  }}

5. 菜单项配置

定义一个菜单项配置工厂函数,返回一个菜单项配置对象。

TypeScript
import type { IMenuButtonItem } from '@univerjs/ui'import { MenuItemType } from '@univerjs/ui'import { SingleButtonOperation } from '../../commands/operations/single-button.operation'export function CustomMenuItemSingleButtonFactory(): IMenuButtonItem<string> {  return {    // 绑定 Command id,单击该按钮将触发该命令    id: SingleButtonOperation.id,    // 菜单项的类型,在本例中,它是一个按钮    type: MenuItemType.BUTTON,    // 按钮的图标,需要在 ComponentManager 中注册    icon: 'ButtonIcon',    // 按钮的提示,优先匹配国际化,如果没有匹配到,将显示原始字符串    tooltip: 'customMenu.singleButton',    // 按钮的标题,优先匹配国际化,如果没有匹配到,将显示原始字符串    title: 'customMenu.button',  }}

将这些菜单项构造成成 Schema 并通过 IMenuManagerService 合并到菜单中。

TypeScript
import { ContextMenuGroup, ContextMenuPosition, RibbonStartGroup } from '@univerjs/ui'import { SingleButtonOperation } from '../commands/operations/single-button.operation'import { CustomMenuItemSingleButtonFactory } from './menu/single-button.menu'export class CustomMenuController extends Disposable {  private _initMenus(): void {    this._menuManagerService.mergeMenu({      [RibbonStartGroup.OTHERS]: {        [SingleButtonOperation.id]: {          order: 10,          menuItemFactory: CustomMenuItemSingleButtonFactory,        },      },      [ContextMenuPosition.MAIN_AREA]: {        [ContextMenuGroup.OTHERS]: {          [SingleButtonOperation.id]: {            order: 12,            menuItemFactory: CustomMenuItemSingleButtonFactory,          },        },      },    })  }}

6. 下拉列表

除了添加单个按钮的菜单项,还可以添加一个下拉菜单项,具体实现方式类似,只在构造菜单 Schema 时有所区别:

  • 将菜单项配置返回类型 IMenuButtonItem<string> 替换为 IMenuSelectorItem<string>
  • 将菜单项类型 MenuItemType.BUTTON 替换为 MenuItemType.SUBITEMS
  • 下拉列表主按钮需要自定义一个 id,作为下拉列表的唯一标识,用于关联下拉列表的子菜单项
TypeScript
import type { IMenuButtonItem, IMenuSelectorItem } from '@univerjs/ui'import { MenuItemType } from '@univerjs/ui'import {  DropdownListFirstItemOperation,  DropdownListSecondItemOperation,} from '../../commands/operations/dropdown-list.operation'export const CUSTOM_MENU_DROPDOWN_LIST_OPERATION_ID = 'custom-menu.operation.dropdown-list'export function CustomMenuItemDropdownListMainButtonFactory(): IMenuSelectorItem<string> {  return {    // 当 type 为 MenuItemType.SUBITEMS 时,工厂函数作为下拉列表的容器,可以设置任意唯一的id    id: CUSTOM_MENU_DROPDOWN_LIST_OPERATION_ID,    // 菜单项的类型    type: MenuItemType.SUBITEMS,    icon: 'MainButtonIcon',    tooltip: 'customMenu.dropdownList',    title: 'customMenu.dropdown',  }}export function CustomMenuItemDropdownListFirstItemFactory(): IMenuButtonItem<string> {  return {    id: DropdownListFirstItemOperation.id,    type: MenuItemType.BUTTON,    title: 'customMenu.itemOne',    icon: 'ItemIcon',  }}export function CustomMenuItemDropdownListSecondItemFactory(): IMenuButtonItem<string> {  return {    id: DropdownListSecondItemOperation.id,    type: MenuItemType.BUTTON,    title: 'customMenu.itemTwo',    icon: 'ItemIcon',  }}

构造菜单 Schema:

TypeScript
import {  DropdownListFirstItemOperation,  DropdownListSecondItemOperation,} from '../commands/operations/dropdown-list.operation'import {  CUSTOM_MENU_DROPDOWN_LIST_OPERATION_ID,  CustomMenuItemDropdownListFirstItemFactory,  CustomMenuItemDropdownListMainButtonFactory,  CustomMenuItemDropdownListSecondItemFactory,} from './menu/dropdown-list.menu'export class CustomMenuController extends Disposable {  private _initMenus(): void {    this._menuManagerService.mergeMenu({      [RibbonStartGroup.OTHERS]: {        [CUSTOM_MENU_DROPDOWN_LIST_OPERATION_ID]: {          order: 11,          menuItemFactory: CustomMenuItemDropdownListMainButtonFactory,          [DropdownListFirstItemOperation.id]: {            order: 0,            menuItemFactory: CustomMenuItemDropdownListFirstItemFactory,          },          [DropdownListSecondItemOperation.id]: {            order: 1,            menuItemFactory: CustomMenuItemDropdownListSecondItemFactory,          },        },      },      [ContextMenuPosition.MAIN_AREA]: {        [ContextMenuGroup.OTHERS]: {          [CUSTOM_MENU_DROPDOWN_LIST_OPERATION_ID]: {            order: 9,            menuItemFactory: CustomMenuItemDropdownListMainButtonFactory,            [DropdownListFirstItemOperation.id]: {              order: 0,              menuItemFactory: CustomMenuItemDropdownListFirstItemFactory,            },            [DropdownListSecondItemOperation.id]: {              order: 1,              menuItemFactory: CustomMenuItemDropdownListSecondItemFactory,            },          },        },      },    })  }}

导出这个插件,并在 Univer 实例中注册。

TypeScript
export { UniverSheetsCustomMenuPlugin } from './plugin'
TypeScript
import { UniverSheetsCustomMenuPlugin } from './plugin'univer.registerPlugin(UniverSheetsCustomMenuPlugin)

现在你已经成功添加了一个新的菜单项,可以在 Univer 菜单栏和右键菜单中看到它。

© 2026 DreamNum Co., Ltd.