--- title: Dropdown 下拉菜单 description: 点击 / 悬停 / 右键触发的操作列表,支持子菜单与键盘导航 --- # Dropdown 下拉菜单 `Dropdown` 把一组操作收进一个菜单,用于**行内操作**(编辑/复制/删除)、**账号菜单**与**右键菜单**。 ## 何时使用 - 操作多于 2 个、或含危险操作需要分组时,收进菜单比并排按钮更清晰; - 表格行、树节点、卡片上承载右侧的「更多操作」; - 右键上下文菜单(`trigger="contextmenu"`); - 只有一个操作时不要用菜单,直接放按钮。 ## 数据源写法 `items` 是最常用的写法,点击后通过 `click` 事件回传 `{ key, item }`。 ## 声明式写法 也可以把 `G3DropdownItem` 写在插槽里:它不渲染任何 DOM,只作为数据载体被 `Dropdown` 收集,因此键盘导航、高亮、子菜单这些能力与 `items` 写法完全一致。使用声明式写法时**建议用 `#trigger` 显式指定触发器**,避免触发器跟菜单项混在默认插槽里。 ## 触发方式 ## 菜单项类型 | 项配置 | 说明 | | --- | --- | | `{ key, label }` | 普通项,点击回传 `key` | | `{ icon }` | 前置图标(组件或 VNode) | | `{ shortcut }` | 右侧快捷键提示文字(仅展示,不绑定按键) | | `{ disabled }` | 禁用项:不可点、键盘导航跳过 | | `{ danger }` | 危险项:红色文字 | | `{ divider: true }` | 分隔线 | | `{ type: 'label', label }` | 分组标签 | | `{ children: [...] }` | 子菜单,hover 展开(`right-start` 弹出) | | `{ onClick: (item) => void }` | 项自带回调,先于全局 `click` 事件执行 | ## 富头部与自定义菜单 `#header` 用于账号卡片之类的头部(不参与键盘导航);`#menu` 是完全自定义菜单结构的逃生舱。 ## 键盘操作 菜单打开后焦点会进入菜单容器,因此可以直接用键盘: | 按键 | 行为 | | --- | --- | | `↑` / `↓` | 移动高亮(跳过分隔线、分组标签与禁用项,循环) | | `Home` / `End` | 跳到首 / 末项 | | `Enter` | 选中高亮项并关闭菜单(有子菜单的父项只高亮,不选中) | | `Esc` | 关闭菜单(由 Popover 处理) | ## API ### Props | 名称 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `items` | `DropdownItemData[]` | — | 菜单项数组;不传时从默认插槽收集 `G3DropdownItem`,否则回退到 `#menu` | | `open` | `boolean` | — | 受控打开状态;不传即非受控 | | `defaultOpen` | `boolean` | `false` | 非受控时的初始状态 | | `trigger` | `'click' \| 'hover' \| 'contextmenu'` | `'click'` | 触发方式 | | `placement` | `Placement` | `'bottom-start'` | 菜单方位 | | `disabled` | `boolean` | `false` | 禁用 | | `mouseEnterDelay` | `number` | `100` | hover 打开延迟(ms) | | `mouseLeaveDelay` | `number` | `200` | hover 关闭延迟(ms)。有子菜单时适当调大 | | `offset` | `number` | `4` | 与触发器的间距(px) | | `unwrapped` | `boolean` | `true` | 零包裹模式,触发器不额外包 `` | ### Events | 名称 | 参数 | 说明 | | --- | --- | --- | | `click` | `({ key, item })` | 选中某个菜单项(不含分隔线 / 分组标签) | | `update:open` | `(open: boolean)` | 打开状态变化 | | `openChange` | `(open: boolean)` | 同上,语义别名 | ### Slots | 名称 | 参数 | 说明 | | --- | --- | --- | | `default` | — | 触发器(未传 `#trigger` 时);或一组 `G3DropdownItem` | | `trigger` | — | 显式指定触发器(推荐) | | `header` | — | 菜单顶部富内容(不参与键盘导航) | | `menu` | `{ close: () => void }` | 完全自定义菜单结构(无 `items` 与 `G3DropdownItem` 时生效) | ### Props(G3DropdownItem) | 名称 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `itemKey` | `string \| number` | — | 标识,点击时回传 | | `label` | `string` | `''` | 菜单文本 | | `icon` | `Component` | — | 前置图标 | | `danger` | `boolean` | `false` | 危险项 | | `disabled` | `boolean` | `false` | 禁用项 | | `divider` | `boolean` | `false` | 渲染为分隔线(其余属性忽略) | | `onClick` | `(item) => void` | — | 项自带回调 | ### 类型定义 ```ts export interface DropdownItemData { key?: string | number label?: string icon?: Component | VNode danger?: boolean disabled?: boolean shortcut?: string divider?: boolean type?: 'label' children?: DropdownItemData[] onClick?: (item: DropdownItemData) => void } export interface DropdownClickPayload { key?: string | number item: DropdownItemData } ``` ### 样式变量 | 变量 | 默认值 | 说明 | | --- | --- | --- | | `--g3-dropdown-min-width` | `180px` | 菜单最小宽度 | | `--g3-dropdown-max-height` | `256px` | 菜单最大高度(超出滚动) | | `--g3-dropdown-item-padding-y` / `-x` | `5px` / `8px` | 菜单项内边距 | | `--g3-dropdown-item-radius` | `4px` | 菜单项圆角 | | `--g3-dropdown-divider-margin` | `4px 0` | 分隔线外边距 | ## 实现说明 - **三种数据源统一成一份 `items` 数组**:声明式的 `G3DropdownItem` 由 Dropdown 在渲染时收集(读取 vnode.props),随后与 `items` 走完全相同的渲染与键盘逻辑,因此不会出现「声明式写法少了某个能力」的问题; - 菜单容器是可聚焦的(`tabindex="-1"`),打开后主动 `focus()`,键盘事件因此能被菜单自身接收,不需要全局监听; - 子菜单是**嵌套的 Popover**(`trigger="hover"`、`placement="right-start"`),递归渲染 `items`;子菜单内部 `interactive=false`,不参与主菜单的高亮联动; - 打开时把 z-index 打到 `popup` 档,关闭回收;右键触发时浮层对齐鼠标位置; - 菜单项高亮用 `activeIndex` 单值驱动,鼠标移入与键盘移动共用它,因此「键盘移动后鼠标移入」不会出现双高亮。