CustomTree 树形组件
用于展示层级结构数据,支持展开收起、单选、多选等功能。
基础用法
通过 data 属性传入树形数据,组件会自动渲染层级结构。
vue
<script setup>
import CustomTree from "@/components/custom-tree/index.vue"
// 树形数据结构:value(节点值) + label(显示文本) + children(子节点)
const treeData = [
{ value: "1", label: "系统管理", children: [
{ value: "1-1", label: "用户管理" },
{ value: "1-2", label: "角色管理" },
]},
{ value: "2", label: "系统监控", children: [
{ value: "2-1", label: "系统日志" },
]},
]
</script>
<template>
<custom-tree :data="treeData" />
</template>默认展开
设置 default-expand-all 属性,默认展开所有节点。
vue
<custom-tree :data="treeData" default-expand-all />或通过 default-expanded-keys 指定默认展开的节点。
vue
<custom-tree
:data="treeData"
:default-expanded-keys="['1', '1-1']"
/>多选功能
设置 checkable 属性开启多选功能,通过 checked-keys 绑定选中的节点。
vue
<script setup>
import { ref } from "vue"
import CustomTree from "@/components/custom-tree/index.vue"
const treeData = [
{ id: "1", name: "系统管理", children: [
{ id: "1-1", name: "用户管理" },
{ id: "1-2", name: "角色管理" }
]},
{ id: "2", name: "系统监控" }
]
const checkedKeys = ref(["1-1"])
function handleCheck(keys, info) {
console.log("选中:", keys)
console.log("操作:", info.checked ? "选中" : "取消选中", info.node)
}
</script>
<template>
<custom-tree
:data="treeData"
:checkable="true"
:checked-keys="checkedKeys"
value-key="id"
label-key="name"
@check="handleCheck"
/>
</template>父子联动
默认情况下,选中父节点会自动选中所有子节点,取消同理。设置 check-strictly 属性可取消父子联动。
vue
<!-- 父子不联动 -->
<custom-tree
:data="treeData"
:checkable="true"
:check-strictly="true"
/>半选状态
当部分子节点选中时,父节点会显示半选状态(indeterminate)。
自定义节点内容
通过 content 插槽自定义节点显示内容。
vue
<custom-tree :data="treeData" value-key="id" label-key="name">
<template #content="{ node, level, checked, indeterminate }">
<view class="custom-node">
<wd-icon :name="node.icon" size="18" />
<text :class="{ 'is-checked': checked }">{{ node.name }}</text>
<wd-tag v-if="node.type" size="small">{{ node.type }}</wd-tag>
</view>
</template>
</custom-tree>插槽参数:
| 参数 | 说明 | 类型 |
|---|---|---|
| node | 当前节点数据 | TreeOption |
| level | 节点层级(从 0 开始) | number |
| expanded | 是否展开 | boolean |
| selected | 是否选中(单选模式) | boolean |
| checked | 是否选中(多选模式) | boolean |
| indeterminate | 是否半选状态 | boolean |
自定义操作按钮
通过 action 插槽添加操作按钮,设置 show-action 显示默认操作按钮。
vue
<custom-tree :data="treeData" show-action>
<template #action="{ node, level }">
<wd-button size="small" @click.stop="handleEdit(node)">编辑</wd-button>
</template>
</custom-tree>自定义键名
通过 value-key、label-key、children-key 自定义数据字段映射。
vue
<custom-tree
:data="treeData"
value-key="id"
label-key="name"
children-key="children"
/>API
Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| data | 树形数据 | TreeOption[] | [] |
| value-key | 节点值的字段名 | string | 'value' |
| label-key | 节点标签的字段名 | string | 'label' |
| children-key | 子节点的字段名 | string | 'children' |
| default-expanded-keys | 默认展开的节点 | string[] | [] |
| default-selected-keys | 默认选中的节点(单选) | string[] | [] |
| selectable | 是否可单选 | boolean | false |
| default-expand-all | 是否默认展开所有节点 | boolean | false |
| show-action | 是否显示操作按钮区域 | boolean | false |
| checkable | 是否开启多选 | boolean | false |
| checked-keys | 选中的节点(多选) | string[] | [] |
| check-strictly | 父子节点是否不联动 | boolean | false |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| click | 点击节点时触发 | (node: TreeOption) |
| select | 单选选中时触发 | (node: TreeOption, selected: boolean) |
| expand | 展开/收起节点时触发 | (node: TreeOption, expanded: boolean) |
| action | 点击操作按钮时触发 | (node: TreeOption) |
| check | 多选选中变化时触发 | (checkedKeys: string[], info: { checked: boolean, node: TreeOption }) |
Slots
| 插槽名 | 说明 | 参数 |
|---|---|---|
| content | 自定义节点内容 | { node, level, expanded, selected, checked, indeterminate } |
| action | 自定义操作按钮 | { node, level } |
TreeOption 类型
typescript
interface TreeOption {
[key: string]: any // 其他自定义属性
value: string // 节点值(可通过 value-key 映射)
label: string // 节点标签(可通过 label-key 映射)
children?: TreeOption[] // 子节点(可通过 children-key 映射)
disabled?: boolean // 是否禁用
}完整示例
角色权限分配页面示例:
vue
<script setup>
import { ref, computed } from "vue"
import { onLoad } from "@dcloudio/uni-app"
import CustomTree from "@/components/custom-tree/index.vue"
import MenuAPI from "@/api/menu"
import RoleAPI from "@/api/role"
const roleId = ref(0)
const menuList = ref([])
const checkedKeys = ref([])
// 转换菜单为树结构
const menuTree = computed(() => {
return transformMenuToTree(menuList.value)
})
function transformMenuToTree(menus) {
return menus.map(menu => ({
id: String(menu.id),
name: menu.name,
type: menu.type,
children: menu.children ? transformMenuToTree(menu.children) : undefined
}))
}
function handleCheck(keys) {
checkedKeys.value = keys
}
async function handleSubmit() {
await RoleAPI.updateRoleMenus(roleId.value, checkedKeys.value.map(Number))
uni.navigateBack()
}
onLoad(async (query) => {
if (query?.id) {
roleId.value = Number(query.id)
const [menus, roleMenuIds] = await Promise.all([
MenuAPI.getList(),
RoleAPI.getRoleMenuIds(roleId.value)
])
menuList.value = menus
checkedKeys.value = roleMenuIds.map(String)
}
})
</script>
<template>
<view class="page">
<!-- 操作栏 -->
<view class="action-bar">
<text>已选: {{ checkedKeys.length }} 项</text>
</view>
<!-- 权限树 -->
<custom-tree
:data="menuTree"
:checkable="true"
:checked-keys="checkedKeys"
:default-expand-all="true"
value-key="id"
label-key="name"
@check="handleCheck"
>
<template #content="{ node, checked }">
<view class="node-content">
<text :class="{ 'is-checked': checked }">{{ node.name }}</text>
<wd-tag v-if="node.type" size="small">
{{ node.type === 'C' ? '目录' : node.type === 'M' ? '菜单' : '按钮' }}
</wd-tag>
</view>
</template>
</custom-tree>
<!-- 底部操作 -->
<view class="bottom-bar">
<wd-button @click="uni.navigateBack()">取消</wd-button>
<wd-button type="primary" @click="handleSubmit">保存</wd-button>
</view>
</view>
</template>