Files
bim_engine/docs-old/components/菜单组件-Menu.md

9.2 KiB
Raw Blame History

Menu 组件详细文档

本文档详细描述 Menu 组件的实现细节,包括 API、UI 结构、逻辑流程等,供 AI 根据文档重现组件。


1. 组件概述

1.1 基本信息

  • 组件名称: BimMenu
  • 文件路径: src/components/menu/index.ts
  • 类型定义: src/components/menu/item.ts (配置), src/components/menu/types.ts (选项)
  • 样式文件: src/components/menu/index.css
  • 实现接口: IBimComponent, IRightKeyContent
  • 用途: 渲染一组菜单项,支持分组、排序、图标、快捷键提示和无限级递归子菜单。
  • 特点: 纯数据驱动(基于 MenuItemConfig),内置国际化支持。

1.2 在 SDK 中的位置

  • Menu 组件主要由 RightKeyManager 使用,作为右键菜单的内容载体。
  • 也可以单独实例化并挂载到任何容器中。
  • RightKeyManager 位于 src/managers/right-key-manager.ts

2. 组件类 API 文档

2.1 构造函数

constructor(options: MenuOptions)

参数:

  • options: MenuOptions - 菜单配置选项

MenuOptions 定义:

interface MenuOptions {
    items: MenuItemConfig[]; // 菜单项配置列表
    groupOrder?: string[];   // 分组显示顺序
}

行为:

  • 创建根元素 <ul>
  • 保存配置选项

2.2 公共方法

init(): void

初始化组件(实现 IBimComponent 接口)

功能:

  • 调用 render() 渲染 DOM 结构
  • 订阅语言变更:localeManager.subscribe()
  • 订阅主题变更:themeManager.subscribe()(虽然目前主要通过 CSS 变量,但保留订阅以备扩展)

setTheme(theme: ThemeConfig): void

设置主题(实现 IBimComponent 接口)

参数:

  • theme: ThemeConfig

功能:

  • 将主题颜色映射到 CSS 变量
  • --bim-ui_bg_colortheme.panelBackground
  • --bim-ui_text_primarytheme.textPrimary
  • --bim-ui_border_colortheme.border
  • --bim-ui_bg_hovertheme.componentHover

setLocales(): void

设置语言(实现 IBimComponent 接口)

功能:

  • 清空当前 DOM
  • 重新调用 render(),使 t() 函数获取最新的翻译文本

destroy(): void

销毁组件(实现 IBimComponent, IRightKeyContent 接口)

功能:

  • 取消语言和主题订阅
  • 关闭并销毁所有打开的子菜<EFBFBD><EFBFBD>
  • 从 DOM 中移除自身元素

getElement(): HTMLElement

获取组件根元素(实现 IRightKeyContent 接口)

返回: HTMLElement (根 <ul> 元素)

功能:

  • 允许父容器(如 BimRightKey)获取并挂载此菜单

3. 分化组件说明

BimMenu 没有子类或分化组件。它通过递归自身来实现多级菜单。


4. Manager API 文档

参见 RightKey 组件文档 中的 RightKeyManagerBimMenu 本身是一个纯 UI 组件,通常不直接通过 Manager 管理,而是被 RightKeyManager 动态创建和销毁。


5. UI 详细描述

5.1 DOM 结构

<ul class="bim-menu">
    <!-- 分组 1 -->
    <li class="bim-menu-item [disabled]">
        <div class="bim-menu-item-icon">[SVG图标]</div>
        <div class="bim-menu-item-label">菜单文本</div>
        <!-- 如果有子菜单 -->
        <div class="bim-menu-item-arrow">
            <svg>...</svg> <!-- 右箭头 -->
        </div>
    </li>
    
    <!-- 分组分割线 -->
    <li class="bim-menu-divider"></li>
    
    <!-- 分组 2 -->
    <li class="bim-menu-item">...</li>
</ul>

5.2 CSS 类名和样式

.bim-menu (根容器)

  • display: flex, flex-direction: column
  • background: var(--bim-ui_bg_color)
  • border-radius: 4px
  • padding: 4px 0, margin: 0, list-style: none (关键:移除列表默认样式)
  • min-width: 160px
  • box-shadow: 0 4px 12px rgba(0,0,0,0.2)
  • user-select: none

.bim-menu-item (菜单项)

  • display: flex, align-items: center
  • padding: 6px 12px
  • cursor: pointer
  • position: relative
  • color: var(--bim-ui_text_primary)

.bim-menu-item:hover

  • background-color: var(--bim-ui_bg_hover)

.bim-menu-item.disabled

  • opacity: 0.5
  • cursor: not-allowed
  • pointer-events: none

.bim-menu-divider (分割线)

  • height: 1px
  • background-color: var(--bim-ui_border_color)
  • margin: 4px 0

6. 逻辑流程详细描述

6.1 渲染流程 (render)

  1. 数据分桶: 遍历 items,根据 item.group (默认为 'default') 将菜单项放入不同的数组中。
  2. 确定顺序:
    • 如果提供了 groupOrder,优先按其顺序排列组。
    • 未指定的组按遍历顺序追加。
  3. 渲染分组:
    • 遍历排序后的组键。
    • 如果不是第一组,先插入一个 .bim-menu-divider
    • 获取组内项,根据 item.order 升序排序。
    • 遍历组内项,如果 item.visible !== false,调用 createItemElement 创建 DOM。

6.2 交互流程

点击事件

  • 绑定: 在 .bim-menu-item 上绑定 click
  • 逻辑:
    • e.stopPropagation(): 阻止冒泡。
    • 检查 !hasChildren: 只有叶子节点才触发点击。
    • 调用 item.onClick?.()
    • 注意: 点击后,通常期望菜单关闭。这依赖于 RightKeyManager 或外部逻辑(通过 onClick 内部调用关闭,或者点击触发全局关闭)。

子菜单展开 (mouseenter)

  • 触发: 鼠标移入带有子项的菜单项。
  • 逻辑:
    • 调用 closeSubMenu() 关闭当前可能已打开的其他子菜单。
    • 计算位置:通常位于父项右侧 (rect.right),顶部对齐 (rect.top)。
    • 创建子菜单容器 div (fixed 定位, z-index 10001)。
    • 关键修复: 在容器上绑定 mousedownstopPropagation,防止触发 BimRightKey 的全局关闭逻辑。
    • 实例化新的 BimMenu (递归) 并 init()
    • 将新菜单挂载到容器,容器挂载到 document.body
    • 保存引用到 this.activeSubMenu
    • 边界检测: 如果右侧超出屏幕,改为向左展开。

子菜单关闭

  • 触发: 鼠标移入不带子项的菜单项。
  • 逻辑: 调用 closeSubMenu(),销毁实例并移除 DOM。

7. 国际化支持

7.1 实现方式

  • 使用 src/services/locale.ts 中的 t() 函数。
  • createItemElement 中,直接使用 t(item.label) 设置文本。
  • 组件初始化时订阅语言变更,变更发生时清空并重绘 (setLocales -> render)。

8. 主题支持

8.1 变量映射

  • setTheme 中设置内联样式变量:
    • --bim-ui_bg_color: 面板背景
    • --bim-ui_text_primary: 主要文字
    • --bim-ui_border_color: 边框/分割线
    • --bim-ui_bg_hover: 悬停背景

9. 使用示例

9.1 数据结构示例

const menuItems: MenuItemConfig[] = [
    {
        id: 'view',
        label: 'menu.view', // 翻译键
        group: 'view',
        children: [
            {
                id: 'home',
                label: 'menu.home',
                onClick: () => console.log('Go Home')
            }
        ]
    },
    {
        id: 'delete',
        label: 'menu.delete',
        group: 'edit',
        icon: '<svg>...</svg>',
        onClick: () => console.log('Delete')
    }
];

9.2 手动创建(通常不直接使用,而是通过 RightKeyManager

const menu = new BimMenu({ items: menuItems });
menu.init();
document.body.appendChild(menu.getElement());

10. 实现细节(供 AI 重现)

10.1 递归设计

组件自身不包含递归渲染 DOM 的逻辑,而是通过“交互触发”来递归实例化。即:子菜单不是一开始就渲染在 DOM 树中的,而是当用户鼠标悬停时,动态创建一个新的 BimMenu 实例并挂载到 body 上。这种设计避免了深层嵌套 DOM 带来的样式问题(如 overflow: hidden 截断子菜单)。

10.2 事件冲突处理

由于子菜单挂载在 body 上,点击子菜单在 DOM 树看来是“点击了主菜单外部”。这会触发 BimRightKey 的“点击外部关闭”逻辑。 解决方案: 子菜单容器监听 mousedownstopPropagation(),欺骗 BimRightKey 认为点击没有发生(或者发生在内部)。


11. 类型定义

11.1 MenuItemConfig

interface MenuItemConfig {
    id: string;
    label: string;
    onClick?: () => void;
    icon?: string;       // SVG 字符串
    group?: string;      // 分组标识
    order?: number;      // 排序权重
    children?: MenuItemConfig[];
    disabled?: boolean;
    visible?: boolean;
}

11.2 MenuOptions

interface MenuOptions {
    items: MenuItemConfig[];
    groupOrder?: string[];
}

12. 文件清单

  • src/components/menu/index.ts: 组件核心逻辑
  • src/components/menu/index.css: 组件样式
  • src/components/menu/item.ts: 仅包含类型定义 (MenuItemConfig)
  • src/components/menu/types.ts: 组件选项接口

13. 更新记录

日期 修改内容 修改人
2024-XX-XX 重构为纯配置对象驱动,移除 BimMenuItem 类 AI Assistant