Yakit YakitMenu 导航菜单组件解析:API 数据结构、右键上下文菜单定位与主题样式实现
【免费下载链接】yakitCyber Security ALL-IN-ONE Platform项目地址: https://gitcode.com/GitHub_Trending/ya/yakit
Yakit 的YakitMenu是贯穿整个客户端的导航菜单组件,既用于页面功能入口的普通菜单,也是 HTTP 数据包表格右键菜单、编辑器菜单等上下文菜单的统一实现。本文基于组件官方 API 文档,结合YakitMenu.tsx、showByRightContext.tsx及样式源码,完整讲清菜单数据结构(YakitMenuItemType)、关键属性默认值、antd Menu 的转换逻辑,以及右键菜单在视口中的自动定位算法,帮助你在 Yakit 前端中正确构建两级菜单与自定义右键菜单。
组件定位与目录结构
YakitMenu位于渲染进程 UI 组件库中,为页面和功能提供导航的菜单列表。组件目录 YakitMenu 包含四个文件:
- YakitMenu.tsx:核心组件实现,负责把声明式的菜单数据转换为 antd
Menu的items结构; - showByRightContext.tsx:右键上下文菜单的挂载与定位逻辑;
- yakitMenu.module.scss:菜单类型与尺寸的 CSS 变量主题;
- showByRightContext.module.scss:右键容器外框样式;
- index.md:本组件的 API 文档(即本文骨架来源)。
从源码结构看,Yakit 全应用共有多处直接依赖该组件,例如 HelpDoc.tsx(帮助菜单)与 useHTTPFlowTableContextMenu.tsx(HTTP 历史表格右键菜单),是典型的基础 UI 层组件。
API 总览:Menu 属性
以下是组件文档中Menu的完整属性表(见 index.md):
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| data | 菜单内容 | YakitMenuItemType[] | - |
| width | 菜单项最小宽度 | number | 128 |
| type | 菜单类型 | "primary"|"grey" | "primary" |
| isHint | 是否鼠标悬浮展示文字内容弹窗 | boolean | false |
| popupClassName | 外层装饰类 | string | - |
| size | 菜单尺寸 | "default"|"rightMenu" | - |
| menuprop | ant-menu 其余 api 属性(参考 ant 官网) | - | - |
对照 YakitMenu.tsx 的实现,默认值在组件内解构时给出:data = []、width = 128、type = 'primary'、isHint = false、size = 'default',与文档一致。此外源码中还存在文档未列出的两个扩展属性,实际开发中可以注意:
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| parentTitleClick | 点击一级菜单是否触发 onClick | boolean | false |
| menuWrapperClassName | 外层装饰 className | string | - |
YakitMenuProp接口继承自 antd 的MenuProps(见 YakitMenu.tsx#L36-L49),因此 antd Menu 的其余 API(如onClick、style等)均可透传,对应文档中 "menuprop" 一项。
width 与 size 的落地效果
width并非最终宽度,而是菜单项的最小宽度:在 generateMenuInfo 中,每个菜单项的 label 都被包裹进style={{ minWidth: width }}的容器,菜单整体因此获得不小于该值的宽度。
size则通过 CSS 变量切换整套尺寸(见 yakitMenu.module.scss#L130-L140):
default:行高 28px、内容高 16px、padding6px 8px;rightMenu(右键高度紧凑型):行高 24px、内容高 16px、padding4px 8px。
右键场景常传size="rightMenu"使菜单更紧凑。
type 主题与 isHint 悬浮提示
type在组件内映射为yakit-menu-primary/yakit-menu-grey两个样式类(YakitMenu.tsx#L66-L73)。这两类通过 SCSS 中的:has()选择器驱动整套 CSS 变量:
primary:悬浮/选中态使用主题色(fetch-color('primary', ...)),单项type="danger"/"success"时整项切换为对应色彩体系(见 yakitMenu.module.scss#L68-L129);grey:悬浮背景为中性灰(--Colors-Use-Neutral-Bg-Hover),更适合工具型菜单。
isHint为true时,每项 label 会包一层 antdTooltip(zIndex={9999}、placement="leftBottom")。悬浮提示的文案取title,未填时回退到字符串类型的label(YakitMenu.tsx#L94):
const hintTitle = info.title ? info.title : typeof info.label === 'string' ? info.label : ''菜单数据结构:YakitMenuItemType
文档定义了菜单数据为可递归的联合类型:
type YakitMenuItemType = YakitMenuItemProps | YakitMenuItemDividerProps;
YakitMenuItemProps
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| label | 菜单展示内容 | string | ReactNode | - |
| key | 菜单选项值 | string | - |
| disabled | 是否禁用 | boolean | false |
| children | 子菜单数据 | YakitMenuItemType[] | - |
| itemIcon | 菜单 icon | ReactNode | - |
| title | tooltip 提示,不填默认用 label | string | - |
| type | 单项菜单类型(只在叶子节点时有效) | "success" | "danger" | - |
源码中的 YakitMenuItemProps 接口 比文档多出两个字段,值得补充说明:
type实际还接受'info' | 'text'两种取值:info对应置灰可点击样式(generateItemInfomixin),text对应纯文本强调样式(generateItemTextmixin),映射逻辑见 YakitMenu.tsx#L75-L81;noStyle:取消统一的固定高度、固定 padding 及悬浮/选中态颜色,仅保留初始背景、icon 与文字颜色,适合在自定义容器中嵌入菜单单项。
另外注意:children的类型标注为YakitMenuItemProps[],而联合类型YakitMenuItemType才是文档对外承诺的数据形状——即子菜单中同样允许插入分隔线。
YakitMenuItemDividerProps
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| type | 分割线类型 | "divider" | "divider" |
在generateMenuInfo中,组件通过判断type === 'divider'识别分隔线,并转换为 antd 的MenuDividerType(YakitMenu.tsx#L84-L92),最终渲染为 antd 的ant-menu-item-divider,颜色由--Colors-Use-Neutral-Border控制。
两级菜单的转换逻辑
YakitMenu的核心是递归函数generateMenuInfo,它把 Yakit 声明式数据翻译成 antdMenu的ItemType树,规则如下(YakitMenu.tsx#L83-L166):
- 分隔线项:直接输出
{ type: 'divider' }; - 带 children 的项(父菜单):label 渲染为「icon + 单行省略标题 +
ChevronRightOutlined箭头」的容器,同时给该项设置popupClassName(合并yakit-menu-submenu、类型类、尺寸类与外部传入的popupClassName),使二级弹层菜单继承同样的主题; - 叶子项:label 渲染为「icon + 标题」容器,按
type/noStyle追加yakit-menu-item-*类名; - parentTitleClick:仅当
parentTitleClick与onClick同时存在时,父菜单项才挂载onTitleClick,点击一级标题即可触发onClick({ key, keyPath: [key], domEvent })——这正是 HTTP 表格右键菜单中"点击一级菜单直接执行"行为(如"插件扩展"入口)的实现来源。
最终组件渲染为外层装饰div.yakit-menu-div-wrapper包裹的 antd<Menu>(YakitMenu.tsx#L168-L180)。从源码结构看,SCSS 注释明确指出样式"限制菜单层级为两层及以内"(yakitMenu.module.scss#L364、#L372),因此官方数据结构虽支持递归 children,但受控样式仅保证两级菜单的视觉效果,多层级场景应谨慎。
右键上下文菜单:showByRightContext
showByRightContext是一个命令式 API:传入YakitMenuProp(或任意 ReactNode)、可选的 x/y 坐标,即在鼠标位置弹出一个独立的上下文菜单容器,返回{ destroy }用于手动关闭(showByRightContext.tsx#L39-L125)。
视口防溢出定位算法
菜单默认以coordinate.clientX/clientY(全局鼠标坐标,来自 globalVariable)为原点。当容器已有测量尺寸时,用genX/genY修正坐标,避免菜单超出视口:
genX:若target + coordinate > client(右侧放不下),则left = coordinate - target - 6,否则left = coordinate + 6;genY:按底部余量分三段处理——余量 ≥ 10px 时向下偏移 6px;余量不足且菜单底部越过鼠标时向上翻折到鼠标上方;其余情况按溢出量回退 6px(showByRightContext.tsx#L13-L31)。
容器固定position: absolute; z-index: 9999,id 为yakit-right-context。若同 id 容器已存在则复用,isForce为 true 时先移除旧容器再新建,避免同一位置重复右键叠加多个菜单。
生命周期细节
几个值得关注的实现决策:
- 挂载时通过
emiter.emit('setYakitHeaderDraggable', false)关闭标题栏拖拽,防止 Electron 窗口拖拽区遮挡菜单点击,销毁时再恢复(showByRightContext.tsx#L78-L90); - 渲染采用
ReactDOM.render而非 React 18 的createRoot,源码注释说明原因在于 antd Menu 下createRoot会出现"二级菜单多个同时打开"的问题(#L117-L119); - 首次渲染后监听一次
document捕获阶段的click,点击菜单外部即延迟一帧destory()并卸载 DOM(#L101-L121); - 内容组件
RightContext会在拿到实测宽高后回调offsetPosition,用真实宽高二次校正坐标,解决"先按 0 尺寸定位、内容渲染后越界"的时序问题; - 外框样式带
max-height: calc(100vh - 10px); overflow: hidden auto(showByRightContext.module.scss),超长菜单会纵向滚动而不是无限增长。
实战:HTTP 历史表格右键菜单
useHTTPFlowTableContextMenu.tsx 是该 API 最复杂的调用方,其调用形态可作为右键菜单的参考模板:
showByRightContext( { width: 180, parentTitleClick: true, data: rowContextmenu, // YakitMenuItemType[] 结构 onClick: async ({ key, keyPath }) => { /* 按 key/keyPath 分发单条与批量操作 */ }, }, event.clientX, event.clientY, )该实现中可以看到 YakitMenu 数据结构的完整发挥:每项children承载二级动作(如"复制到 Web Fuzzer → 发送并跳转/仅发送"),叶子项通过keybindings渲染快捷键文案(keys-style类名在 yakitMenu.module.scss#L187-L189 中定义了着色),onClick里依据keyPath区分"单行菜单""批量菜单""插件扩展子树"三类分支。此外 YakitDiffEditor.tsx、YakitEditor.tsx、EditTable.tsx 也均以相同方式挂载右键菜单。
实战:普通 Popover 菜单
非右键场景则直接把<YakitMenu>作为 Popover 的内容。例如 HelpDoc.tsx#L25-L48 构建了一个带二级菜单的帮助面板:
const menu = ( <YakitMenu data={[ { key: 'official_website', label: t('HelpDoc.officialWebsite') }, { key: 'Github', label: 'Github', children: [ { label: t('HelpDoc.featureRequest'), key: 'feature_request' }, { label: 'BUG', key: 'report_bug' }, ], }, { key: 'aboutUs', label: t('HelpDoc.aboutUs') }, ]} popupClassName={styles['ui-op-submenu']} onClick={({ key }) => menuSelect(key)} ></YakitMenu> )这里popupClassName用于给二级弹层追加自定义样式,onClick仅接收被点击叶子项的key做跳转分发——展示了 data/onClick/popupClassName 三个核心属性的最小可用组合。
小结
YakitMenu以声明式data: YakitMenuItemType[]描述菜单树,内部通过generateMenuInfo递归转换为 antd Menu 的items,自动处理分隔线、两级子菜单、主题类与尺寸类的注入;width(默认 128)是菜单项最小宽度,type(primary/grey)与size(default/rightMenu)通过 CSS 变量体系切换整套视觉;isHint开启后以 Tooltip 提供悬浮提示,文案优先取title;- 右键场景使用命令式 API
showByRightContext(props, x?, y?, isForce?),自带防溢出坐标修正、外部点击销毁、标题栏拖拽联动与真实尺寸二次校正,是 Yakit 中 HTTP 表格、编辑器、Diff 视图等右键菜单的统一入口; - 样式受控层级为两级菜单,更深层级虽有类型支持但无对应视觉保障,设计菜单结构时应以"父项 + 动作子项"的两级形态为主。
【免费下载链接】yakitCyber Security ALL-IN-ONE Platform项目地址: https://gitcode.com/GitHub_Trending/ya/yakit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考