9.7 KiB
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+ CSScalc(gutter + indent * level),拖拽指示线复用同一个计算,因此深层节点的落点线也能精确对齐; - 右键菜单:Teleport 到配置容器、按鼠标坐标定位并用
computePosition做视口翻转,关闭时机是「文档 mousedown(捕获)+ Esc + 滚动 + resize」,组件卸载时统一清理。