Files
workspace/code/fms/.codebuddy/plans/fms-tree-component_16ac753e(未完成).md
T
2026-08-10 22:34:33 +08:00

10 KiB
Raw Blame History

name, overview, design, todos
name overview design todos
fms-tree-component 基于 shadcn-vue 模式(reka-ui + Tailwind v4 + Vue 3 Composition API)从零封装 FmsTree 组件,参考旧系统 FmsTree 的核心能力(虚拟根节点、右键菜单、i18n、展开/选择),并确保与模块管理页面等场景兼容。
architecture styleKeywords fontSystem colorSystem
framework component
vue shadcn
shadcn-vue
new-york
Minimalism
Clean
Semantic Design
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
add-t-function 在 stores/app.ts 中添加 t() 翻译函数,支持从 DB 的 b_i18n 表获取翻译文本 pending
id content status dependencies
create-use-tree-i18n 创建 composables/useTreeI18n.ts,提供递归树节点翻译能力 pending
add-t-function
id content status
create-fms-tree-node 创建 components/FmsTreeNode.vue 递归节点组件,基于 Collapsible + ContextMenu 实现展开折叠、选中高亮、右键菜单、键盘导航 pending
id content status dependencies
create-fms-tree 创建 components/FmsTree.vue 根组件,实现虚拟根节点包裹、props 定义、v-model 双向绑定、事件转发和 slot 透传 pending
create-fms-tree-node
create-use-tree-i18n
id content status dependencies
verify-and-format 验证组件在模块管理页面中可正常渲染,运行 pnpm fmt:check 检查格式 pending
create-fms-tree

用户需求

在新系统 fms-vue 中封装一个可复用的 FmsTree 树组件,替代旧系统 fms-vue2 中的 FmsTree.vue。新项目使用 shadcn-vue (reka-ui + Tailwind v4) 技术栈,不能使用 antdv-next。

产品概述

FmsTree 是一个通用的树形展示组件,为模块管理、数据分类、文件目录等场景提供统一的树交互体验。

核心功能

  • 树形结构展示:递归渲染树节点,支持无限层级嵌套
  • 展开/折叠:点击箭头图标展开或折叠子节点,支持 v-model:expandedKeys 受控模式
  • 节点选中:点击节点高亮选中,支持 v-model:selectedKeys 受控模式,支持单选/多选
  • 虚拟根节点:自动在 treeData 外层包裹虚拟根节点(如 全部分类),确保首次加载时树处于展开可交互状态
  • 右键菜单:通过 contextMenu prop 传入菜单生成函数,右键节点弹出自定义菜单项
  • 标题插槽:支持 #title slot 自定义节点标题渲染
  • i18n 翻译(本次不做,后续按需添加)
  • 键盘导航:支持上下箭头切换节点、左右箭头展开/折叠、Enter 选中
  • 样式定制:遵循 shadcn-vue 语义颜色体系,支持 Tailwind class 透传

技术栈

  • 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 等)
  • @lucide/vue:ChevronRight 图标(展开箭头)
  • clsx + tailwind-merge:cn() 工具函数
  • @vueuse/core:useDebounceFn(可选,用于搜索防抖场景)

实现方案

核心策略

由于 reka-ui 没有 Tree/TreeView 原语,采用 Collapsible 递归嵌套 的方式自建树结构。每个树节点如果包含 children,则用它自己的 CollapsibleRoot 包裹;叶子节点则为普通 div。这样天然获得展开/折叠动画和 ARIA 属性。

右键菜单通过 reka-ui 的 ContextMenuRoot + ContextMenuTrigger 包裹每个节点行实现,无需全局 Menu 管理器。

虚拟根节点在组件内部用 computed 生成,确保外部调用者只需传入原始 treeData。

架构设计

FmsTree.vue               (根容器)
├── 虚拟根节点 computed     (包裹 treeData,defaultOpen)
│   └── FmsTreeNode.vue    (递归节点,v-for children)
│       ├── CollapsibleRoot (当 node.children 存在时)
│       │   ├── ContextMenuRoot (当 contextMenu prop 存在时)
│       │   │   ├── ContextMenuTrigger
│       │   │   │   └── 节点行 (缩进 + 箭头 + title slot)
│       │   │   └── ContextMenuContent → ContextMenuItem v-for
│       │   └── CollapsibleContent
│       │       └── FmsTreeNode (递归)
│       └── 无 children 时:纯节点行 (带 ContextMenu)
└── 透传 slots (#title, #icon 等)

组件文件拆分

src/
├── components/
│   ├── FmsTree.vue            # [NEW] 根组件:props 定义、虚拟根节点、右键菜单 orchestration、事件转发
│   └── FmsTreeNode.vue        # [NEW] 递归节点:Collapsible 逻辑、缩进计算、选中高亮、键盘导航
├── composables/
│   └── useTreeI18n.ts         # [NEW] 树节点 i18n 翻译 composable
└── stores/
    └── app.ts                 # [MODIFY] 添加 t() 翻译函数

数据流

外部调用者
  │ treeData (TreeNode[]) + expandedKeys + selectedKeys
  ▼
FmsTree.vue
  │ computed: effectiveTreeData (包裹虚拟根节点)
  │ computed: localizedData (i18n 翻译)
  ▼
FmsTreeNode.vue (递归)
  │ CollapsibleRoot.open = expandedKeys.includes(node.key)
  │ 节点行 class = selectedKeys.includes(node.key) ? 'bg-accent' : ''
  │ @click → emit('select', keys, node)
  │ @rightclick → ContextMenu 弹出
  ▼
事件向上冒泡 → FmsTree emit select / menuClick / update:expandedKeys / update:selectedKeys

实现细节

Props 设计

// FmsTree.vue props
interface FmsTreeProps {
  treeData: TreeNode[]                 // 原始树数据
  expandedKeys?: string[]              // 受控展开 keys
  selectedKeys?: string[]             // 受控选中 keys
  multiple?: boolean                   // 是否多选,默认 false
  contextMenu?: (node: TreeNode) => { items: ContextMenuItem[], onClick?: (payload: { key: string, node: TreeNode }) => void }
  rootKey?: string                     // 虚拟根节点 key,默认 '__fms_root__'
  rootTitle?: string                   // 虚拟根节点标题,默认 '全部分类'
  showRoot?: boolean                   // 是否显示虚拟根节点,默认 true
  indent?: number                      // 每层缩进像素,默认 24
}

事件设计

// FmsTree.vue emits
emit('update:expandedKeys', keys: string[])
emit('update:selectedKeys', keys: string[])
emit('select', keys: string[], info: { node: TreeNode, selected: boolean })
emit('menuClick', payload: { key: string, node: TreeNode })

TreeNode 数据类型

interface TreeNode {
  key: string                          // 唯一标识(bigint id 序列化为 string)
  title: string                        // 显示标题
  children?: TreeNode[]                // 子节点
  b_id?: string                        // 业务 ID
  b_name?: string                      // 业务名称
  b_code?: string                      // 业务编码
  b_i18n?: string                      // 国际化 key
  b_module_type?: string               // 模块类型: directory | page | data
  [key: string]: unknown               // 其他业务字段透传
}

性能考虑

  • 递归节点组件使用 v-for 渲染子节点,Vue 3 的 diff 算法已做优化
  • expandedKeys 和 selectedKeys 通过 props 受控,避免组件内部状态与外部不同步
  • i18n 翻译仅在节点 title 通过 b_i18n key 查找时触发,不影响渲染性能
  • 不实现虚拟滚动(项目当前无海量树数据场景,如有需要后续可基于 ScrollArea 和 @tanstack/virtual 添加)
  • 避免不必要的 re-render:FmsTreeNode 使用 props.key 作为显式 key,确保 Vue 复用节点 DOM

与旧系统的差异

旧系统 FmsTree 新系统 FmsTree
基于 antdv <a-tree> 基于 reka-ui Collapsible + ContextMenu
透传 a-tree attrs 仅暴露明确设计的 props(不盲目透传)
select 行为修正用 selectSuppress 标志 用 CollapsibleRoot 的 v-model:open 受控,消除副作用
虚拟滚动 + 拖拽互斥逻辑 暂不实现,按需后续添加
localizeNodes 递归翻译 在 composable useTreeI18n 中处理

日志和错误处理

  • 组件为纯 UI 组件,不产生日志
  • TreeNode.key 缺失时给出 console.warn 提示
  • 右键菜单 contextMenu prop 未传入时,节点不响应右键(不报错)

向后兼容

  • FmsTree.vue 是新文件,不影响现有代码
  • app.ts 的 t() 函数为增量添加,现有功能不变

设计风格

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

页面布局(树组件内)

每个树节点行从左侧到右侧依次为:展开箭头(16px lucide 图标)→ 缩进空白(每层 24px)→ 标题内容。悬停时显示浅背景色,选中时显示 accent 背景色。

节点行样式

  • 高度 32px,flex items-center,rounded-sm
  • 默认:transparent 背景,text-foreground
  • 悬停:bg-accent/50 背景(非选中态时)
  • 选中:bg-accent text-accent-foreground 背景 + 文字色
  • 缩进:每层级 padding-left: 24px(通过 style binding 计算)
  • 展开箭头:ChevronRight 图标,展开时 rotate-90 transition-transform
  • 间距:节点行之间无 gap,紧密排列

右键菜单样式

  • 复用项目已有的 ContextMenu 组件样式(bg-popover、border、shadow-md、rounded-md)
  • 菜单项:h-8、px-2、text-sm、gap-2,hover 时 bg-accent

交互

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

Agent Extensions

Skill

  • shadcn-vue
  • 目的:验证 ContextMenu 和 Collapsible 组件的 API,确保封装正确使用 reka-ui 原语
  • 预期结果:确认 ContextMenu 的 Root/Trigger/Content/Item 用法、Collapsible 的 Root/Trigger/Content 用法