在 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 菜单栏和右键菜单中看到它。