Yakit YakitMenu 导航菜单组件解析:API 数据结构、右键上下文菜单定位与主题样式实现
2026/9/16 18:57:06 网站建设 项目流程

Yakit YakitMenu 导航菜单组件解析:API 数据结构、右键上下文菜单定位与主题样式实现

【免费下载链接】yakitCyber Security ALL-IN-ONE Platform项目地址: https://gitcode.com/GitHub_Trending/ya/yakit

Yakit 的YakitMenu是贯穿整个客户端的导航菜单组件,既用于页面功能入口的普通菜单,也是 HTTP 数据包表格右键菜单、编辑器菜单等上下文菜单的统一实现。本文基于组件官方 API 文档,结合YakitMenu.tsxshowByRightContext.tsx及样式源码,完整讲清菜单数据结构(YakitMenuItemType)、关键属性默认值、antd Menu 的转换逻辑,以及右键菜单在视口中的自动定位算法,帮助你在 Yakit 前端中正确构建两级菜单与自定义右键菜单。

组件定位与目录结构

YakitMenu位于渲染进程 UI 组件库中,为页面和功能提供导航的菜单列表。组件目录 YakitMenu 包含四个文件:

  • YakitMenu.tsx:核心组件实现,负责把声明式的菜单数据转换为 antdMenuitems结构;
  • 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菜单项最小宽度number128
type菜单类型"primary"|"grey""primary"
isHint是否鼠标悬浮展示文字内容弹窗booleanfalse
popupClassName外层装饰类string-
size菜单尺寸"default"|"rightMenu"-
menupropant-menu 其余 api 属性(参考 ant 官网)--

对照 YakitMenu.tsx 的实现,默认值在组件内解构时给出:data = []width = 128type = 'primary'isHint = falsesize = 'default',与文档一致。此外源码中还存在文档未列出的两个扩展属性,实际开发中可以注意:

参数说明类型默认值
parentTitleClick点击一级菜单是否触发 onClickbooleanfalse
menuWrapperClassName外层装饰 classNamestring-

YakitMenuProp接口继承自 antd 的MenuProps(见 YakitMenu.tsx#L36-L49),因此 antd Menu 的其余 API(如onClickstyle等)均可透传,对应文档中 "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),更适合工具型菜单。

isHinttrue时,每项 label 会包一层 antdTooltipzIndex={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是否禁用booleanfalse
children子菜单数据YakitMenuItemType[]-
itemIcon菜单 iconReactNode-
titletooltip 提示,不填默认用 labelstring-
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 声明式数据翻译成 antdMenuItemType树,规则如下(YakitMenu.tsx#L83-L166):

  1. 分隔线项:直接输出{ type: 'divider' }
  2. 带 children 的项(父菜单):label 渲染为「icon + 单行省略标题 +ChevronRightOutlined箭头」的容器,同时给该项设置popupClassName(合并yakit-menu-submenu、类型类、尺寸类与外部传入的popupClassName),使二级弹层菜单继承同样的主题;
  3. 叶子项:label 渲染为「icon + 标题」容器,按type/noStyle追加yakit-menu-item-*类名;
  4. parentTitleClick:仅当parentTitleClickonClick同时存在时,父菜单项才挂载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)是菜单项最小宽度,typeprimary/grey)与sizedefault/rightMenu)通过 CSS 变量体系切换整套视觉;isHint开启后以 Tooltip 提供悬浮提示,文案优先取title
  • 右键场景使用命令式 APIshowByRightContext(props, x?, y?, isForce?),自带防溢出坐标修正、外部点击销毁、标题栏拖拽联动与真实尺寸二次校正,是 Yakit 中 HTTP 表格、编辑器、Diff 视图等右键菜单的统一入口;
  • 样式受控层级为两级菜单,更深层级虽有类型支持但无对应视觉保障,设计菜单结构时应以"父项 + 动作子项"的两级形态为主。

【免费下载链接】yakitCyber Security ALL-IN-ONE Platform项目地址: https://gitcode.com/GitHub_Trending/ya/yakit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询