Files
workspace/code/fms/.codebuddy/plans/fms-tree-component_8d7b2eee.md
T
2026-08-10 22:34:33 +08:00

8.1 KiB
Raw Blame History

name, overview, design, todos
name overview design todos
fms-tree-component 基于 shadcn-vue 模式(reka-ui Collapsible + ContextMenu + Tailwind v4 + Vue 3 Composition API)从零封装 FmsTree/FmsTreeNode 组件,含虚拟根节点、右键菜单、展开折叠、节点选中、键盘导航。本次不做多语言。
architecture styleKeywords fontSystem colorSystem
framework component
vue shadcn
shadcn-vue
new-york
Minimalism
Clean
fontFamily heading subheading body
Inter
size weight
14px 500
size weight
12px 400
size weight
13px 400
primary background text functional
var(--primary)
var(--accent)
var(--background)
var(--accent)
transparent
var(--foreground)
var(--muted-foreground)
var(--accent-foreground)
var(--border)
var(--ring)
id content status
create-fms-tree-node 创建 components/FmsTreeNode.vue 递归节点组件,基于 Collapsible + ContextMenu 实现展开折叠、选中高亮、右键菜单、键盘导航 completed
id content status dependencies
create-fms-tree 创建 components/FmsTree.vue 根组件,实现虚拟根节点包裹、props 定义、v-model 双向绑定、事件转发和 slot 透传 completed
create-fms-tree-node
id content status dependencies
verify-and-format 验证组件可正常渲染,运行 pnpm fmt:check 检查格式 completed
create-fms-tree

产品概述

在新系统 fms-vue 中封装一个可复用的 FmsTree 树组件,基于 shadcn-vue(reka-ui + Tailwind v4)技术栈,为模块管理等场景提供统一的树交互体验。本次不做多语言、拖拽、虚拟滚动。

核心功能

  • 树形结构展示:递归渲染节点,支持无限层级嵌套
  • 展开/折叠:点击箭头图标展开或折叠子节点,支持 v-model:expandedKeys 受控模式
  • 节点选中:点击节点高亮选中,支持 v-model:selectedKeys,单选模式
  • 虚拟根节点:treeData 外层自动包裹虚拟根节点(如 全部分类),确保首次加载时树处于展开可交互状态
  • 右键菜单:通过 contextMenu prop 传入菜单项生成函数,右键节点弹出 ContextMenu
  • 标题插槽:支持 #title slot 自定义节点标题渲染
  • 键盘导航:ArrowUp/Down 切换焦点,ArrowRight/Left 展开折叠,Enter 选中

技术栈

  • Vue 3 Composition API + TypeScript(<script setup lang="ts">)
  • reka-ui:CollapsibleRoot / CollapsibleTrigger / CollapsibleContent(展开/折叠)
  • reka-ui:ContextMenuRoot / ContextMenuTrigger / ContextMenuContent / ContextMenuItem(右键菜单)
  • Tailwind v4:语义颜色(bg-accent、text-muted-foreground、bg-popover 等)
  • @lucide/vue:ChevronRight 图标(展开箭头)
  • clsx + tailwind-merge:cn() 工具函数

实现方案

核心策略

由于 reka-ui 没有 Tree 原语,采用 Collapsible 递归嵌套 自建树结构。每个有 children 的节点用 Collapsible 包裹,叶子节点为普通 div。右键菜单通过 ContextMenu 包裹每个节点行实现。虚拟根节点在 FmsTree.vue 中用 computed 生成。

架构设计

FmsTree.vue(根容器)
├── computed:effectiveTreeData(包裹虚拟根节点)
│   └── FmsTreeNode.vue(递归节点,v-for children)
│       ├── Collapsible(当 node.children 存在)
│       │   ├── ContextMenu(包裹节点行)
│       │   │   ├── ContextMenuTrigger
│       │   │   │   └── 节点行(缩进 + ChevronRight箭头 + title slot)
│       │   │   └── ContextMenuContent → ContextMenuItem
│       │   └── CollapsibleContent
│       │       └── FmsTreeNode(递归子节点)
│       └── 无 children 时:纯节点行(ContextMenu 包裹)

组件文件拆分

src/
└── components/
    ├── FmsTree.vue            # [NEW] 根组件:props/v-model定义、虚拟根节点、事件转发
    └── FmsTreeNode.vue        # [NEW] 递归节点:Collapsible逻辑、缩进、选中高亮、键盘导航

数据流

外部调用者传入 treeData + expandedKeys + selectedKeys → FmsTree 包裹虚拟根节点 → FmsTreeNode 递归渲染,CollapsibleRoot.open 受 expandedKeys 控制,节点行 class 受 selectedKeys 控制,事件向上冒泡(select / menuClick / update:expandedKeys / update:selectedKeys)。

实现细节

Props 设计(FmsTree.vue)

interface TreeNode {
  key: string
  title: string
  children?: TreeNode[]
  [key: string]: unknown
}

// Props
treeData: TreeNode[]
expandedKeys?: string[]
selectedKeys?: string[]
contextMenu?: (node: TreeNode) => { key: string; label: string }[]
rootKey?: string        // 默认 '__fms_root__'
rootTitle?: string      // 默认 '全部分类'
showRoot?: boolean      // 默认 true
indent?: number         // 默认 24

Events

emit('update:expandedKeys', keys: string[])
emit('update:selectedKeys', keys: string[])
emit('select', keys: string[], info: { node: TreeNode })
emit('menuClick', payload: { key: string; node: TreeNode })

组件关键实现要点

FmsTreeNode.vue(递归节点):

  • 接收 props:node、expandedKeys、selectedKeys、indent(从 depth 计算)、contextMenu
  • CollapsibleRoot 的 v-model:open 根据 expandedKeys.includes(node.key) 受控
  • 节点行的 padding-left 由 depth * indent 计算
  • ChevronRight 图标通过 CSS rotate-90 transition 表示展开状态
  • 选中态 class:selectedKeys.includes(node.key) ? 'bg-accent text-accent-foreground' : ''
  • 悬停态 class:hover:bg-accent/50(非选中时)
  • 键盘导航:外层容器 @keydown 处理 ArrowUp/Down(focus 相邻节点)、ArrowRight(展开)/ArrowLeft(折叠)、Enter(选中)
  • 递归渲染:CollapsibleContent 内 <FmsTreeNode> 自引用 v-for children

FmsTree.vue(根组件):

  • effectiveTreeData computed:包裹虚拟根节点 [{ key: rootKey, title: rootTitle, children: treeData }]
  • expandedKeys 首次为空时自动添加 rootKey
  • 传递给 FmsTreeNode 时 depth 从 0 开始(虚拟根 level 由 showRoot 控制是否显示)
  • 透传 #title slot 到 FmsTreeNode

与旧系统差异

旧系统 FmsTree 新系统 FmsTree
基于 antdv <a-tree> 基于 reka-ui Collapsible + ContextMenu
透传 a-tree attrs 仅暴露明确设计的 props
select 行为修正用 selectSuppress 标志 CollapsibleRoot v-model:open 受控,无副作用
虚拟滚动 + 拖拽互斥 暂不实现
localizeNodes 递归翻译 本次不做

性能考虑

  • Vue 3 v-for diff 已做优化,递归组件用显式 key 确保 DOM 复用
  • expandedKeys / selectedKeys 通过 props 受控,避免内外状态不同步
  • 不实现虚拟滚动(当前无海量数据场景)

日志与错误处理

  • TreeNode.key 缺失时 console.warn 提示
  • contextMenu prop 未传入时节点不响应右键(不报错)

设计风格

遵循 shadcn-vue new-york 风格:简洁、中性、语义化。树组件作为列表型控件,采用极简设计。

节点行样式

  • 高度 32px,flex items-center,rounded-sm
  • 默认:transparent 背景,text-foreground
  • 悬停:bg-accent/50 背景
  • 选中:bg-accent text-accent-foreground
  • 缩进:每层级 padding-left: 24px
  • 展开箭头:ChevronRight 图标,展开时 rotate-90,transition-transform duration-200
  • 节点间无 gap,紧密排列

右键菜单

复用项目已有 ContextMenu 组件样式:bg-popover、border、shadow-md、rounded-md,菜单项 h-8 px-2 text-sm

交互

  • 点击箭头:仅展开/折叠,不选中
  • 点击行:选中节点
  • 右键行:弹出 ContextMenu
  • 键盘:ArrowUp/Down 切换焦点,ArrowRight/Left 展开/折叠,Enter 选中

Agent Extensions

Skill

  • shadcn-vue
  • 目的:验证 Collapsible 和 ContextMenu 组件的 reka-ui API(v-model:open、Trigger/Content/Root 用法),确保封装正确
  • 预期结果:确认 CollapsibleRoot 的 open/defaultOpen props、ContextMenuRoot 的 modal 等关键 API 用法