更新记录
1.2.1(2026-11-06) 下载此版本
新增一项视觉优化:
onlyRadioLeaf生效时仍显示单选图标且父节点文字置灰,视觉上与「不可选中」不符。现开启onlyRadioLeaf后默认隐藏单选图标(显式传showRadioIcon可覆盖),父节点文字默认不变灰(新增onlyRadioLeafGray配置开启置灰)
1.2.0(2026-11-06) 下载此版本
新增四项交互与健壮性改进:
交互改进(统一为「选中失败则退化为展开/折叠」)
- 单选模式下开启
onlyRadioLeaf后,点击非叶子节点既不选中也不展开,只能通过点击箭头展开折叠。现改为点击非叶子节点即展开/折叠该节点(等价于点击箭头),末级节点点击仍正常选中 - 禁用节点(
disabled: true)点击后完全无反应,无法展开查看其下级。现禁用节点可点击展开/折叠浏览下级,但不触发change;叶子节点点击仍无反应;开启checkedDisabled后禁用节点仍可选中,此时不展开 - 懒加载(
loadMode)模式下开启expandChecked时,自动展开只修改了展开状态而没有请求数据,出现「箭头朝下但没有子节点」的空展开。现自动展开会同时触发loadApi把子节点取回来,箭头状态与实际数据保持一致
健壮性
- 搜索时的懒加载会逐层请求节点,接口若对任意节点都返回子节点会无限递归直到页面卡死。现新增
searchLoadLimit配置(默认 1000)限制搜索加载总量,超限时给出提示并停止;同时请求改为分批并发(每批 6 个),避免一次性发起过多请求
1.1.9(2026-11-06) 下载此版本
新增两项交互改进,统一为「选中失败则退化为展开/折叠」:
- 单选模式下开启
onlyRadioLeaf后,点击非叶子节点既不选中也不展开,只能通过点击箭头展开折叠。现改为点击非叶子节点即展开/折叠该节点(等价于点击箭头),末级节点点击仍正常选中 - 禁用节点(
disabled: true)点击后完全无反应,无法展开查看其下级。现禁用节点可点击展开/折叠浏览下级,但不触发change;叶子节点点击仍无反应;开启checkedDisabled后禁用节点仍可选中,此时不展开
平台兼容性
uni-app(3.8.10)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | √ | √ | √ | - | √ | - | - |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| √ | - | - | - | - | - | - | - | - | - | - | - |
lu-data-tree
uni-app 树形选择组件,支持单选、多选、父子级联、严格模式、节点禁用、懒加载、关键词筛选、默认展开及自定义节点插槽。
该组件基于 da-tree 树形选择 进行修改
特性
- 支持 Vue 2 和 Vue 3
- 支持单选和多选
- 支持父子节点联动及严格模式
- 支持半选状态
- 支持默认选中和默认展开
- 支持节点禁用
- 支持异步懒加载
- 支持关键词筛选
- 支持自定义节点内容
- 支持实例方法控制选中和展开状态
- 支持大数据量增量渲染
安装
将 lu-data-tree 放入项目的 uni_modules 目录,并确保项目中已安装 uni-icons。
组件支持 easycom,通常无需手动导入:
<lu-data-tree :data="treeData" />
基础用法
<template>
<lu-data-tree
:data="treeData"
labelField="name"
valueField="id"
@change="handleChange"
@expand="handleExpand"
/>
</template>
<script setup>
import { ref } from 'vue';
const treeData = ref([
{
id: '1',
name: '一级节点',
children: [
{ id: '1-1', name: '二级节点' }
]
}
]);
function handleChange(keys, currentNode) {
console.log(keys, currentNode);
}
function handleExpand(expanded, currentNode) {
console.log(expanded, currentNode);
}
</script>
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
data |
Array |
[] |
树形数据 |
themeColor |
String |
#007aff |
选中状态主题色 |
showCheckbox |
Boolean |
false |
是否启用多选;关闭时为单选 |
showCount |
Boolean |
false |
是否显示当前节点的直接子节点数量 |
defaultCheckedKeys |
Array \| String \| Number |
null |
默认选中节点;单选传单个 key,多选传 key 数组(单选模式下若传入数组,只取第一个) |
defaultExpandAll |
Boolean |
false |
是否默认展开全部节点 |
defaultExpandedKeys |
Array |
null |
默认展开的节点 key 数组 |
expandChecked |
Boolean |
false |
选中节点时是否自动展开到该节点,单选与多选均生效 |
checkStrictly |
Boolean |
false |
多选时是否关闭默认父子联动 |
autoCheckAllChildren |
Boolean |
false |
严格模式下,选中节点时是否选中全部子孙节点 |
autoUncheckAllChildren |
Boolean |
false |
严格模式下,取消节点时是否取消全部子孙节点 |
autoCheckAllParent |
Boolean |
false |
严格模式下,选中节点时是否选中全部祖先节点 |
autoUnCheckAllParent |
Boolean |
false |
严格模式下,取消节点时是否取消全部祖先节点 |
showRadioIcon |
Boolean |
- | 单选模式下是否显示单选图标;不传时 onlyRadioLeaf 生效则不显示,否则显示;显式传入 true / false 时以传入值为准 |
onlyRadioLeaf |
Boolean |
false |
单选时是否只允许选中叶子节点;非叶子节点点击时改为展开/折叠 |
onlyRadioLeafGray |
Boolean |
false |
onlyRadioLeaf 生效时,非叶子节点的文本是否置灰,默认不变灰 |
indent |
Number |
40 |
层级缩进距离,单位为 rpx |
checkboxPlacement |
String |
left |
选择图标位置,可选 left 或 right |
loadMode |
Boolean |
false |
是否启用懒加载模式;空子节点也显示展开图标 |
loadApi |
Function |
null |
懒加载函数,接收当前节点并返回子节点数组;无子节点时可返回空数组 |
alwaysFirstLoad |
Boolean |
false |
是否每次展开都重新调用懒加载函数 |
searchLoadLimit |
Number |
1000 |
搜索时懒加载的节点数上限,防止接口对任意节点都返回子节点时无限递归 |
checkedDisabled |
Boolean |
false |
是否允许处理禁用节点的选中状态;开启后禁用节点可直接点击选中,父子联动与父节点状态回算也会一并纳入禁用节点 |
packDisabledkey |
Boolean |
true |
获取选中 key 时是否包含禁用节点 |
isLeafFn |
Function |
null |
自定义叶子节点判断函数,接收原始节点并返回布尔值 |
filterValue |
String |
'' |
筛选关键词,按节点标签进行包含匹配 |
labelField |
String |
label |
节点标签字段名 |
valueField |
String |
value |
节点唯一值字段名,要求全树唯一 |
childrenField |
String |
children |
子节点字段名 |
disabledField |
String |
disabled |
禁用状态字段名;禁用节点不可选中,但可点击展开/折叠 |
appendField |
String |
append |
节点副标签字段名 |
leafField |
String |
leaf |
叶子节点标记字段名 |
sortField |
String |
sort |
同级节点排序字段名;排序按父节点分组进行,排序后每个节点的子树仍保持连续 |
选择模式
单选
默认关闭 showCheckbox,组件处于单选模式:
<lu-data-tree
:data="treeData"
defaultCheckedKeys="1-1"
@change="handleChange"
/>
单选事件中的第一个参数为单个 key;取消选中时为 null。
多选
<lu-data-tree
:data="treeData"
showCheckbox
:defaultCheckedKeys="['1-1']"
@change="handleChange"
/>
默认情况下父子节点联动。选中父节点会联动选中可用子孙节点,子节点部分选中时父节点显示半选状态。
开启 expandChecked 后,无论是点击勾选还是调用 setCheckedKeys,选中节点都会自动展开到该节点(展开其祖先链并显示其子节点)。筛选状态下不触发自动展开,以免破坏筛选结果结构。
懒加载(loadMode)模式下,自动展开会同时触发 loadApi 把子节点数据取回来,箭头状态与实际数据保持一致;不开启 expandChecked 时,点击节点不会展开、也不会发起请求,只能点击箭头展开。
禁用节点
节点数据中设置 disabled: true 后,该节点不能被选中,但仍可点击展开/折叠以浏览其下级:
<lu-data-tree :data="treeData" labelField="name" valueField="id" />
- 点击禁用节点 → 展开/折叠该节点(等价于点击箭头),触发
expand事件,但不触发change - 禁用节点若是叶子节点,点击无任何反应
- 开启
checkedDisabled后,禁用节点可以直接被选中,此时点击不再展开 - 懒加载模式下禁用节点不显示展开箭头,也不会触发
loadApi
仅选中末级节点
单选模式下开启 onlyRadioLeaf 后,只有末级(叶子)节点可以被选中:
<lu-data-tree
:data="treeData"
labelField="name"
valueField="id"
onlyRadioLeaf
@change="handleChange"
/>
- 非叶子节点不可选中,点击时改为展开/折叠该节点(等价于点击箭头)
- 末级节点点击正常选中,触发
change事件 - 该属性仅对单选模式生效,多选模式下不产生任何影响
- 懒加载模式下未加载的节点无法预知是否为末级,默认按「非末级」处理:可以展开加载,但不能选中
配套行为:
- 单选图标:开启
onlyRadioLeaf后默认不再显示单选图标(非叶子节点本就选不了,显示反而误导)。需要保留图标时显式传:showRadioIcon="true"即可 - 文本置灰:非叶子节点的文本默认不变灰,保持与普通节点一致。需要提示「只能选末级」时传
:onlyRadioLeafGray="true"
<!-- 默认:隐藏单选图标,父节点文字正常 -->
<lu-data-tree :data="treeData" onlyRadioLeaf />
<!-- 保留单选图标 + 父节点文字置灰 -->
<lu-data-tree :data="treeData" :showRadioIcon="true" onlyRadioLeaf onlyRadioLeafGray />
严格模式
开启 checkStrictly 后,默认父子联动关闭。此时每个节点只能处于未选中或选中状态,不会产生半选状态。需要联动时,可按需开启以下配置:
<lu-data-tree
:data="treeData"
showCheckbox
checkStrictly
autoCheckAllChildren
autoUncheckAllChildren
autoCheckAllParent
autoUnCheckAllParent
/>
懒加载
<lu-data-tree
:data="treeData"
loadMode
:loadApi="loadChildren"
/>
async function loadChildren(node) {
const children = await fetchChildren(node.key);
return children || [];
}
loadApi 接收处理后的当前节点。返回值应为子节点数组;没有子节点时返回 []。组件会根据 childrenField 合并并插入新节点。
传入的节点是内部节点的副本,字段与内部节点一致。其中 children、originItem、parents 采用惰性深拷贝——只有真正被访问时才会复制一次并缓存,未访问时不会产生复制开销;其余字段为浅拷贝。因此可以安全地读取和修改 originItem,不会影响组件内部状态。
筛选
<template>
<input v-model="keyword" placeholder="请输入关键词" />
<lu-data-tree :data="treeData" :filterValue="keyword" />
</template>
<script setup>
import { ref } from 'vue';
const keyword = ref('');
</script>
匹配节点及其祖先节点会显示。清空关键词后,组件会恢复筛选前的展开状态。
懒加载模式下,筛选范围仅包含已经加载到组件中的节点;为保证匹配完整,组件会在筛选时自动逐层加载尚未加载的节点,加载总量受 searchLoadLimit 限制(默认 1000),超限时停止加载并在控制台提示,此时可能存在未加载的匹配项。
事件
| 事件 | 回调参数 | 说明 |
|---|---|---|
change |
(checkedKeys, currentNode) |
选中状态变化时触发;单选取消时 checkedKeys 为 null;调用 setCheckedKeys 时同样触发 |
expand |
(expanded, currentNode) |
节点展开或收起时触发;expanded 为布尔值;调用 setExpandedKeys 时同样触发 |
currentNode 为组件处理后的内部节点,其中 originItem 保存原始数据节点。
内部节点中:
disabled:节点被禁用,无法选中(但可点击展开/折叠)radioDisabled:单选模式且开启onlyRadioLeaf时,非叶子节点不可选中(仍可展开)
数据刷新与选中态
当 data 属性发生变化时,组件会重新构建内部数据,并按以下优先级决定选中态:
defaultCheckedKeys发生变化 → 以defaultCheckedKeys为准- 用户手动操作过选中状态(点击或调用
setCheckedKeys)→ 保留操作后的运行时状态 - 以上都不满足 → 使用
defaultCheckedKeys
因此异步加载数据、或列表数据被整体替换时,用户的选中结果不会被重置;而主动取消选中后,数据刷新也不会让默认选中「复活」。如需强制恢复到默认选中,重新赋值 defaultCheckedKeys 即可。
实例方法
通过 ref 获取组件实例:
<lu-data-tree ref="treeRef" :data="treeData" showCheckbox />
const treeRef = ref(null);
treeRef.value?.setCheckedKeys(['1-1'], true);
treeRef.value?.setExpandedKeys(['1'], true);
const checkedKeys = treeRef.value?.getCheckedKeys();
const expandedKeys = treeRef.value?.getExpandedKeys();
| 方法 | 参数 | 说明 |
|---|---|---|
setCheckedKeys |
(keys, checked) |
设置节点选中状态;单选 keys 为 key,多选为 key 数组;设置后触发 change 事件,事件参数为按实际选中状态校正后的结果 |
setExpandedKeys |
(keys, expand) |
设置节点展开状态;keys 为 key 数组或 'all';设置后触发 expand 事件 |
getCheckedKeys |
- | 获取已选中的 key,无选中时返回 [] |
getHalfCheckedKeys |
- | 获取半选中的 key,无半选时返回 [];单选模式下表示选中节点的祖先链 |
getUncheckedKeys |
- | 获取未选中的 key,无未选中时返回 [] |
getCheckedNodes |
- | 获取已选中的内部节点,无选中时返回 [] |
getHalfCheckedNodes |
- | 获取半选中的内部节点,无半选时返回 [] |
getUncheckedNodes |
- | 获取未选中的内部节点,无未选中时返回 [] |
getExpandedKeys |
- | 获取已展开的 key,无展开时返回 [];叶子节点不计入展开状态 |
getUnexpandedKeys |
- | 获取未展开的 key,无未展开时返回 [] |
getExpandedNodes |
- | 获取已展开的内部节点 |
getUnexpandedNodes |
- | 获取未展开的内部节点 |
getNodeByKey |
(key) |
根据 key 获取内部节点;找不到时返回 undefined |
自定义节点内容
组件提供默认插槽,插槽参数如下:
node:组件处理后的内部节点data:原始数据节点
<lu-data-tree :data="treeData">
<template #default="{ node, data }">
<view class="custom-node">
<text>{{ node.label }}</text>
<text v-if="data.description">{{ data.description }}</text>
</view>
</template>
</lu-data-tree>
## 空状态插槽
组件提供两个空状态插槽,插槽内容均完全由调用方控制,不会自动附加组件空状态样式:
- `empty`:传入的树形数据为空时使用
- `search-empty`:搜索完成且没有匹配节点时使用
未提供插槽时,分别显示“暂无数据”和“未匹配到数据”。
```vue
<lu-data-tree :data="treeData" :filterValue="filterValue">
<template #empty>
<view class="custom-empty">暂无组织数据</view>
</template>
<template #search-empty>
<view class="custom-search-empty">没有找到相关节点</view>
</template>
</lu-data-tree>
节点数据结构
默认字段结构如下:
[
{
value: '1',
label: '一级节点',
disabled: false,
leaf: false,
children: [
{
value: '1-1',
label: '二级节点'
}
]
}
]
使用自定义字段时:
<lu-data-tree
:data="treeData"
labelField="name"
valueField="id"
childrenField="nodes"
/>
valueField 对应的值必须在整棵树中唯一,否则节点选中、展开和实例查询可能产生冲突。
注意事项
- 组件依赖
uni-icons。 loadMode必须同时传入有效的loadApi函数。loadApi应返回数组;无子节点时返回[]。onlyRadioLeaf与懒加载组合时,未加载的节点无法确定是否为叶子,默认按「非叶子」处理:可以正常展开并加载子节点,但不能被选中。loadApi需在数据初始化前传入。若在data就绪之后才赋值,节点会被当作叶子节点处理(不显示展开图标)。- 父容器应设置明确高度,否则
scroll-view的百分比高度可能无法正常显示。 - 当前组件适用于普通 uni-app Vue2/Vue3 项目,不适用于 uni-app x。
- 组件声明支持的平台以
package.json为准。

收藏人数:
https://gitee.com/lu-my/lu-data-tree.git
下载插件并导入HBuilderX
下载示例项目ZIP
赞赏(0)
下载 381
赞赏 1
下载 12765834
赞赏 1959
赞赏
京公网安备:11010802035340号