9.2 KiB
9.2 KiB
name, overview, todos
| name | overview | todos | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| dropdown-enhance | 增强组件库 Dropdown:支持 item 自带 onClick、新增 #header 富头部插槽、新增 DropdownItem 子组件(类 el-dropdown-item 写法),并把 AppTopbar 用户下拉迁移到新能力(头像+名字+组织头部)。同时保留现有 items 数组 + 全局 @click 与 #menu 逃生舱。 |
|
用户需求
参考 Element Plus 的 <el-dropdown-item> 写法,增强项目组件库 @/components/ui/dropdown 的下拉菜单能力,使其更灵活、可自定义。
产品概述
为 Dropdown 组件库新增三种能力并保持向后兼容:
- items 数组项支持自带
onClick(item)回调,点击某项时优先执行该项自己的回调,同时保留全局@click分发。 - 支持
<DropdownItem>子组件写法(具名/默认插槽声明每个菜单项),类似 el-dropdown-item 的声明式用法。 - 新增
#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 插槽。所有增强均为「可选能力」,未使用时不改变既有渲染与行为。
关键技术决策
- item.onClick 优先级:在
menu.vue普通项点击处改为「先执行item.onClick?.(item),再selectItem(item)(继续 emit 全局 click)」。这样既支持项自带回调,又保留全局 @click 分发,互不冲突;项回调里可执行close()关闭(selectItem 内部已关闭)。 - DropdownItem 子组件:新建
DropdownItem.vue,仅用于「声明语义」——它不作为独立渲染节点,而是被 Dropdown 在#default默认插槽中收集其 props/插槽内容并转为 items;或直接由 DropdownMenu 消费。为最小化复杂度并避免与现有 items 递归体系冲突,采用「DropdownItem 仅暴露 props + 提供 default 插槽内容,Dropdown 在运行时通过useSlots().default读取子节点(VNode)还原成 items」的方案,复用DropdownMenu渲染。这样既获得 el-dropdown-item 的写法,又不重复一套渲染逻辑。
- 权衡:直接把 DropdownItem 写成独立可点击 DOM 会与现有键盘导航/
activeIndexprovide 体系割裂,需大量重构。采用「VNode 收集转 items」更符合现有架构、改动最小、风险最低。 - 取舍:DropdownItem 用法与 items 用法二选一(同一次 Dropdown 内不要混用,混用时以 items 优先或合并,计划以 items 优先、默认插槽 fallback)。
- #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 默认值、无未使用变量)。
架构设计
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 保持头像+名字按钮。
关键代码结构(接口级)
// 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 逃生舱)。