Files
workspace/code/g3soft-libs/docs/content/ui/data/tree.md
T
2026-10-09 17:32:14 +08:00

9.7 KiB
Raw Blame History

title, description
title description
Tree 树形控件 层级数据展示与操作:选中、勾选联动、过滤、拖拽排序、懒加载、右键菜单

Tree 树形控件

Tree 用于展示带层级的数据(组织架构、目录、菜单、权限),并支持在树上直接做操作。

何时使用

  • 数据天然有父子关系(目录、分类、组织)时;
  • 需要「勾选一批节点交给后端」时(权限分配、批量操作);
  • 需要拖拽调整层级或顺序时;
  • 只是展示扁平的一列选项时,用 Select 或 Checkbox 更简单。

基础用法

treeData 是嵌套结构,默认按 { key, title, children } 取值;选中值用 v-model:selected-keys。

勾选与半选

checkable 开启复选框;默认是父子联动(选中父节点自动勾选全部子孙,部分选中时父节点显示半选)。checkStrictly 关闭联动,父子互不影响。

check 事件的载荷是 { checked, halfChecked },checked 已包含联动推导出的子孙节点,可以直接提交给后端。

展开状态

场景 用法
初始全展开 default-expand-all
初始展开指定项(并自动补全祖先) default-expanded-keys + default-expand-parent(默认开)
完全受控 传 expanded-keys + v-model:expanded-keys
选中/勾选后自动展开祖先 auto-expand-parent

过滤

filter 传关键词:命中的节点与它的祖先链会显示,命中文字高亮,命中路径自动展开(这个自动展开不会写进你的 expandedKeys)。

#title 插槽存在时,内置高亮不生效——因为标题完全由你渲染。需要自带高亮时请在自己的插槽里处理。

懒加载

lazy 模式下,展开一个「没有 children 且未加载过」的节点会触发 load;你请求完把 children 写回数据,并把 key 记入 loaded-keys(组件据此停止显示加载图标、并判断是否还有子节点)。

拖拽排序

draggable 开启拖拽。落点按行的上 25% / 中 50% / 下 25% 分成 before / inside / after,你需要在 drop 事件里自己重排数据——组件不会直接修改 tree-data。

externalDragKey 可以把「树外拖入」的来源(如字段库)映射成拖拽键,从而让树内节点成为落点(用于「把字段拖进目录」这类场景)。

右键菜单

contextMenu 传数组或 (node, key) => items:节点上右键收到该节点,空白处右键收到 null。

自定义字段与渲染

fieldNames 映射后端字段;节点自身的 icon / disabled / disableCheckbox / draggable: false / isLeaf 可以逐节点覆盖行为。

键盘操作

树是标准的 role="tree"(节点 role="treeitem"),采用 roving tabindex:

按键 行为
↑ / ↓ 在可见节点间移动(自动跳过被折叠的子树)
→ 展开当前节点;已展开则进入第一个子节点
← 收起当前节点;已收起则跳到父节点
Enter 选中当前节点
Space 勾选(无复选框时等同选中)

API

Props

名称 类型 默认值 说明
treeData TreeNodeData[] [] 树数据(嵌套结构)
fieldNames TreeFieldNames { key: 'key', title: 'title', children: 'children', icon: 'icon' } 字段名映射
checkable boolean false 显示复选框
selectable boolean true 是否允许选中(false 时行点击不选中)
multiple boolean false 多选:Ctrl/Cmd + 点击切换、普通点击单选替换、再点唯一项取消
checkStrictly boolean false 勾选严格模式:父子不联动
disabled boolean false 整树禁用(展开 / 选中 / 勾选 / 拖拽 / 右键全部短路)
lazy boolean false 异步加载模式;首次展开未加载节点时触发 load
draggable boolean false 允许拖拽排序
externalDragKey (dataTransfer, event) => TreeKey | null — 把树外拖入源映射成拖拽键,返回 null 表示不识别
filter string '' 过滤关键词(匹配 title,大小写不敏感)
blockNode boolean false 节点占满整行(便于显示 hover 底色)
defaultExpandAll boolean false 初始展开全部(仅非受控初始化生效)
defaultExpandParent boolean true 初始补全 defaultExpandedKeys 的祖先路径
autoExpandParent boolean false 选中 / 勾选后自动展开其祖先路径
contextMenu TreeContextMenuItem[] | ((node, key) => TreeContextMenuItem[]) — 右键菜单数据源(node 为 null 表示空白处)
expandedKeys TreeKey[] — 受控展开键(传了即受控)
selectedKeys TreeKey[] — 受控选中键
checkedKeys TreeKey[] | { checked: TreeKey[] } — 受控勾选键(两种形态都接受)
loadedKeys TreeKey[] — 受控已加载键(懒加载用)
defaultExpandedKeys TreeKey[] [] 非受控初始展开键
defaultSelectedKeys TreeKey[] [] 非受控初始选中键
defaultCheckedKeys TreeKey[] [] 非受控初始勾选键
defaultLoadedKeys TreeKey[] [] 非受控初始已加载键

Events

名称 参数 说明
select (keys, { node, selectedKeys, nativeEvent }) 选中项变化
check ({ checked, halfChecked }, { node, checked }) 勾选变化。第二个参数的 checked 表示本次是「勾上」还是「取消」
expand (expandedKeys, { node, expanded }) 展开 / 收起
load (node) 懒加载:首次展开未加载节点
drop ({ dragKey, dropKey, dropPosition, node, event }) 拖拽落下。dropPosition 为 'before' | 'inside' | 'after'
dragstart / dragend ({ node, event }) 拖拽开始 / 结束
contextmenu ({ node, key, event }) 右键(空白处 node 为 null)
contextMenuClick ({ item, node, key, event }) 点击右键菜单项
update:expandedKeys (keys) 展开键变化(受控 / 非受控都会发出)
update:selectedKeys (keys) 选中键变化
update:checkedKeys (keys) 勾选键变化
update:loadedKeys (keys) 已加载键变化

Slots

名称 参数 说明
title { node, expanded, selected } 自定义节点标题(提供后内置高亮失效)
empty — 无数据时的内容,默认取语言包的「暂无数据」

类型定义

export type TreeKey = string | number
export type DropPosition = 'before' | 'inside' | 'after'

export interface TreeFieldNames {
  key?: string
  title?: string
  children?: string
  icon?: string
}

export interface TreeNodeData {
  key?: TreeKey
  title?: string
  children?: TreeNodeData[]
  icon?: Component | VNode
  disabled?: boolean
  disableCheckbox?: boolean
  draggable?: boolean
  isLeaf?: boolean
}

export interface TreeContextMenuItem {
  key: string | number
  label?: string
  icon?: Component | VNode
  disabled?: boolean
  danger?: boolean
  divider?: boolean
}

样式变量

变量 默认值 说明
--g3-tree-node-height 28px 节点行高
--g3-tree-node-gap 6px 相邻节点行之间的竖向间距
--g3-tree-indent 18px 每层缩进量
--g3-tree-icon-size 16px 图标尺寸
--g3-tree-switcher-size 22px 展开箭头点击区
--g3-tree-gutter-x 4px 节点行左右留白
--g3-tree-title-padding-x 6px 标题内边距
--g3-tree-drop-line-size 2px 拖拽落点指示线粗细

实现说明

  • 勾选只存显式键:非严格模式下 checkedKeys 保存的是「用户直接点过的节点」,实际的勾选与半选状态由 conductCheck() 从数据推导(父选中 ⇒ 子孙选中;子全选中 ⇒ 父选中;部分选中 ⇒ 父半选)。点击时还会把该节点的祖先从显式集合里删掉,否则「取消一个子节点」会被父节点残留的显式条目瞬间拉回勾选;
  • 性能:buildFlat() 一次 DFS 建好父键 / 层级 / 子键 / 根键四张索引表(computed 缓存,treeData 变才重算),勾选联动、过滤、拖拽判断都基于索引表,不重复递归;
  • 过滤与展开分离:过滤命中的自动展开只作用于「有效展开集合」,不写回用户的 expandedKeys——关闭过滤后树的展开状态回到用户原样;
  • 键盘可见顺序:visibleKeys 是按当前展开状态 DFS 出来的顺序(遇到折叠就截断),与过滤结果取交集,因此 ↑ ↓ 只在「用户真正看得到的节点」间移动;
  • 拖拽:drop 之前先快照落点位置再清理视觉状态(否则读到的永远是 null);内部拖拽会校验 isDescendant,不允许把节点拖进自己的子树;
  • 层级缩进:不是嵌套 padding,而是节点行内联 --g3-tree-level + CSS calc(gutter + indent * level),拖拽指示线复用同一个计算,因此深层节点的落点线也能精确对齐;
  • 右键菜单:Teleport 到配置容器、按鼠标坐标定位并用 computePosition 做视口翻转,关闭时机是「文档 mousedown(捕获)+ Esc + 滚动 + resize」,组件卸载时统一清理。