Skip to content

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-keylabel-keychildren-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是否可单选booleanfalse
default-expand-all是否默认展开所有节点booleanfalse
show-action是否显示操作按钮区域booleanfalse
checkable是否开启多选booleanfalse
checked-keys选中的节点(多选)string[][]
check-strictly父子节点是否不联动booleanfalse

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>

基于 MIT 许可发布 · 由 ❤️ 和 ☕ 驱动 · 支持作者