Files
workspace/code/fms/.codebuddy/plans/dropdown-enhance_866d28ec.md
T
2026-08-17 17:29:54 +08:00

146 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: dropdown-enhance
overview: "增强组件库 Dropdown:支持 item 自带 onClick、新增 #header 富头部插槽、新增 DropdownItem 子组件(类 el-dropdown-item 写法),并把 AppTopbar 用户下拉迁移到新能力(头像+名字+组织头部)。同时保留现有 items 数组 + 全局 @click 与 #menu 逃生舱。"
todos:
- id: enhance-menu-onclick
content: 修改 menu.vue 普通项点击先执行 item.onClick 再 selectItem
status: completed
- id: add-dropdown-item
content: 新建 DropdownItem.vue 声明式子组件并定义 props
status: completed
dependencies:
- enhance-menu-onclick
- id: enhance-dropdown-header
content: "dropdown.vue 增加 #header 插槽与 DropdownItem VNode 收集转 items"
status: completed
dependencies:
- add-dropdown-item
- id: add-header-style
content: index.scss 新增 .dropdown-header 样式
status: completed
dependencies:
- enhance-dropdown-header
- id: refactor-topbar
content: "AppTopbar 用户下拉改用 #header 富头部并精简 items"
status: completed
dependencies:
- enhance-dropdown-header
- add-header-style
- id: verify-uses
content: 用 [subagent:code-explorer] 核查所有 Dropdown 用法无回归
status: completed
dependencies:
- refactor-topbar
---
## 用户需求
参考 Element Plus 的 `<el-dropdown-item>` 写法,增强项目组件库 `@/components/ui/dropdown` 的下拉菜单能力,使其更灵活、可自定义。
## 产品概述
为 Dropdown 组件库新增三种能力并保持向后兼容:
1. items 数组项支持自带 `onClick(item)` 回调,点击某项时优先执行该项自己的回调,同时保留全局 `@click` 分发。
2. 支持 `<DropdownItem>` 子组件写法(具名/默认插槽声明每个菜单项),类似 el-dropdown-item 的声明式用法。
3. 新增 `#header` 插槽,在菜单项列表上方渲染「头像 + 名字 + 组织」等富头部内容。
现有能力(items + 全局 @click、#menu 逃生舱、键盘导航、子菜单 hover)必须全部保留不破坏。
## 核心特性
- item 级别 `onClick`:在 items 配置里直接写点击处理函数,命中该项时直接触发,无需统一在 @click 里 switch。
- `<DropdownItem>` 子组件:支持 `key`/`icon`/`danger`/`disabled`/`divider` 等属性与默认插槽内容,可替代纯数组声明。
- `#header` 富头部插槽:Dropdown 浮层顶部可插入任意自定义富内容(顶部 header 区,与菜单项列表之间有分隔)。
- 向后兼容:未配置 onClick 的项仍走全局 @click;未使用 DropdownItem 时仍可用 items 数组;未使用 #header 时浮层不变。
- AppTopbar 用户下拉改造:将「头像 + 名字 + 组织」从 trigger 移到菜单 #header 插槽,trigger 仅保留头像或精简触发按钮。
## 技术栈
- 前端框架:Vue 3(`<script setup>` SFC + scoped 样式)
- 组件库模式:`@/components/ui/` 下 SFC + `index.scss`(`@use` 引入),复用现有 Dropdown / DropdownMenu / Popover
- 样式 Token:`--fms-card` / `--fms-border` / `--fms-primary` / `--fms-secondary-hover` / `--fms-text` / `--fms-text-secondary` / `--fms-control-radius` / `--fms-danger` / `--fms-dropdown-*`
- 图标:Lucide(`@lucide/vue`)
## 实现方案
### 总体策略
在现有 Dropdown 组件上做增量增强,不改动 Popover 与定位逻辑;新增 `DropdownItem.vue` 子组件承载声明式写法;在 `DropdownMenu.vue` 与 `dropdown.vue` 中分别支持 `item.onClick` 与 `#header` 插槽。所有增强均为「可选能力」,未使用时不改变既有渲染与行为。
### 关键技术决策
1. **item.onClick 优先级**:在 `menu.vue` 普通项点击处改为「先执行 `item.onClick?.(item)`,再 `selectItem(item)`(继续 emit 全局 click)」。这样既支持项自带回调,又保留全局 @click 分发,互不冲突;项回调里可执行 `close()` 关闭(selectItem 内部已关闭)。
2. **DropdownItem 子组件**:新建 `DropdownItem.vue`,仅用于「声明语义」——它不作为独立渲染节点,而是被 Dropdown 在 `#default` 默认插槽中收集其 props/插槽内容并转为 items;或直接由 DropdownMenu 消费。为最小化复杂度并避免与现有 items 递归体系冲突,采用「DropdownItem 仅暴露 props + 提供 default 插槽内容,Dropdown 在运行时通过 `useSlots().default` 读取子节点(VNode)还原成 items」的方案,复用 `DropdownMenu` 渲染。这样既获得 el-dropdown-item 的写法,又不重复一套渲染逻辑。
- 权衡:直接把 DropdownItem 写成独立可点击 DOM 会与现有键盘导航/`activeIndex` provide 体系割裂,需大量重构。采用「VNode 收集转 items」更符合现有架构、改动最小、风险最低。
- 取舍:DropdownItem 用法与 items 用法二选一(同一次 Dropdown 内不要混用,混用时以 items 优先或合并,计划以 items 优先、默认插槽 fallback)。
3. **#header 插槽**:在 `dropdown.vue` 的 Popover `#content` 内、`<DropdownMenu>` 上方插入 `<slot name="header" />`(仅当 items != null 时显示,#menu 逃生舱模式仍由用户自行决定 header 是否出现)。头部区加 `.dropdown-header` 样式(padding、与下方列表之间 1px 分隔线、不可点击、user-select:none)。
### 性能与可靠性
- 渲染开销:DropdownItem 的 VNode 收集仅在挂载时执行一次(非热路径),无性能问题;items 递归渲染与现有一致。
- 键盘导航:`enabledIndices` 已跳过 divider/label/disabled,新增的 header 不参与索引,无需改动;DropdownItem 转换出的项若带 disabled 自动纳入跳过逻辑。
- 向后兼容:所有新 props/插槽均默认空/可选;现有 AppTopbar 改法只在「使用 #header」时调整结构,未用到的 Dropdown 实例零影响。
## 实现注意事项
- 复用现有 CSS 变量与 `.dropdown-*` 样式体系,不引入新 token;header 分隔线复用 `.dropdown-divider` 视觉或新建 `.dropdown-header` 独立样式(建议独立,避免 margin 冲突)。
- `DropdownItem` 的 `divider` 属性转换为 `{ divider: true }`;`disabled`/`danger`/`icon`/`key` 直接映射;默认插槽内容映射为 `label`(支持富内容时允许 `label` 为 VNode/函数?本期仅支持文本 label,富内容走 #header 或 #menu 逃生舱,保持 YAGNI)。
- AppTopbar 改造:删除 `userMenuItems` 里 `{ type:'label', label: org }`,改为在 `#header` 插槽内渲染「头像 + 名字 + 组织」;trigger 保留头像 + 名字按钮(或精简)。注意 scoped 样式 `.fms-avatar`/`.fms-user-name` 需在 AppTopbar 内保留(头部在浮层内,仍属该组件 scoped 作用域通过 slot 传递)。
- 保持 0 lint:新增 SFC 遵循现有 ESLint 规则(defineProps 默认值、无未使用变量)。
## 架构设计
```mermaid
flowchart TD
A[Dropdown.vue 主组件] -->|items 数组| B[DropdownMenu.vue 递归渲染]
A -->|default 插槽含 DropdownItem| C[收集 VNode 转 items]
C --> B
A -->|#header 插槽| D[富头部区 dropdown-header]
A -->|#menu 逃生舱| E[完全自定义内容]
B -->|普通项点击| F[执行 item.onClick 再 selectItem emit click]
A --> G[Popover 浮层/定位/关闭]
```
组件关系不变,仅在 Dropdown 入口增加「VNode→items」「header 插槽」两路输入,DropdownMenu 增加「onClick 优先」分支。
## 目录结构
```
fms-vue/src/components/ui/dropdown/
├── dropdown.vue # [MODIFY] 新增 #header 插槽(Popover #content 内,DropdownMenu 上方);支持从 default 插槽收集 DropdownItem VNode 转换为 items(fallback 于未传 items 时);onClick 透传由 menu.vue 处理。
├── menu.vue # [MODIFY] 普通项点击逻辑改为:先 item.onClick?.(item),再 selectItem(item);其余 divider/label/children 渲染不变。
├── DropdownItem.vue # [NEW] 声明式菜单项子组件。仅承载 props(key/icon/danger/disabled/divider/label) 与默认插槽,不独立渲染交互,由 Dropdown 收集为 items 项。
└── index.scss # [MODIFY] 新增 .dropdown-header 样式(padding、底部 1px 分隔、user-select:none、非交互)。
fms-vue/src/layouts/components/
└── AppTopbar.vue # [MODIFY] 用户下拉:移除 userMenuItems 中的 type:'label' 组织行;新增 #header 插槽渲染「头像+名字+组织」富头部(复用现有 .fms-avatar/.fms-user-name 样式,新增组织文本样式);trigger 保持头像+名字按钮。
```
## 关键代码结构(接口级)
```ts
// DropdownItem.vue props(声明式项,仅用于被 Dropdown 收集)
defineProps<{
key?: string | number
icon?: Component
label?: string
danger?: boolean
disabled?: boolean
divider?: boolean
}>()
// menu.vue 普通项点击(增强后)
@click="!item.disabled && onItemClick(item)"
// onItemClick: item.onClick?.(item); selectItem(item)
```
## Agent Extensions
### SubAgent
- **code-explorer**
- 用途:在实施前深入核对 Dropdown/Popover/AppTopbar 的所有引用点与 slot/props 用法,确认改动无遗漏调用方。
- 预期结果:产出受影响文件清单与调用关系,确保增强不破坏现有使用 Dropdown 的其他页面(如主题色板 Dropdown 用 #menu 逃生舱)。