Skip to content

Tree 树形组件

14:29

组件概况

组件概述

Tree 树形组件是一种用于展示层级结构数据的组件,支持节点的展开/折叠、选择、勾选等功能,可用于构建菜单、文件目录、分类列表等场景。

详细功能描述

  • 支持静态数据和动态异步加载数据
  • 支持节点的展开/折叠操作
  • 支持单选和多选(复选框)模式
  • 支持级联选择和非级联选择
  • 支持节点搜索和高亮
  • 支持自定义节点内容和展开/折叠按钮
  • 支持受控和非受控两种模式
  • 提供丰富的事件回调
  • 支持自定义样式和主题

适用业务场景

  • 导航菜单展示
  • 文件目录结构
  • 分类列表展示
  • 组织架构图
  • 权限管理树形结构
  • 任何需要展示层级关系数据的场景

完整API参考

Props

名称类型默认值必填描述
dataarray[]树形结构数据
keyFieldstring'id'节点唯一标识字段名
labelFieldstring'label'节点标签字段名
childrenFieldstring'children'子节点字段名
isLeafFieldstring'isLeaf'是否为叶子节点的字段名
disabledFieldstring'disabled'是否禁用的字段名
defaultCheckedKeysarray[]默认选中节点键值数组
defaultExpandedKeysarray[]默认展开节点键值数组
checkedKeysarrayundefined受控选中节点键值数组
expandedKeysarrayundefined受控展开节点键值数组
checkablebooleanfalse是否显示复选框
selectablebooleanfalse是否可选择
cascadebooleanfalse是否级联选择
expandOnClickbooleanfalse是否点击节点展开/收缩
checkOnClickbooleanfalse是否点击节点勾选/取消勾选
loadNodefunction-异步加载节点数据的函数
allowCheckingNotLoadedbooleanfalse是否允许勾选未加载的节点
patternstring''搜索过滤模式
showIrrelevantNodesbooleantrue是否显示搜索无关的节点
indentWidthnumber24节点缩进宽度
showSwitcherbooleantrue是否显示展开/收缩按钮
expandIconstring'right-box-filled'展开图标
collapseIconstring'down-box-filled'收缩图标
loadingColorstring''加载图标颜色
checkedColorstring''复选框选中颜色
rotatableSwitcherbooleanfalse展开/收缩按钮是否可旋转
highlightBgColorstring'#f9ae3d'搜索高亮背景颜色
selectedBgColorstring'#f3f4f6'选中节点背景颜色
switcherSizenumber14展开/收缩按钮大小
switcherColorstring'#909399'展开/收缩按钮颜色
customStylestring''自定义根节点样式
customClassstring''自定义根节点样式类

Events

事件名触发条件参数说明
click点击节点时
checked选中状态变化时选中节点键值数组
expanded展开状态变化时展开节点键值数组
update:checked-keys选中节点键值变化时(受控模式)选中节点键值数组
update:expanded-keys展开节点键值变化时(受控模式)展开节点键值数组

Methods

该组件没有对外暴露的方法。

Slots

插槽名作用域变量使用说明
switcherhide(是否隐藏)、loading(是否加载中)、expanded(是否展开)自定义展开/收缩按钮
contentnode(当前节点数据)自定义节点内容

多场景使用示例代码

基础用法

vue
<template>
  <view>
    <wd-tree :data="treeData" />
  </view>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const treeData = ref([
  {
    id: '1',
    label: '节点1',
    children: [
      {
        id: '1-1',
        label: '节点1-1'
      },
      {
        id: '1-2',
        label: '节点1-2'
      }
    ]
  },
  {
    id: '2',
    label: '节点2',
    children: [
      {
        id: '2-1',
        label: '节点2-1'
      }
    ]
  }
])
</script>

可勾选模式

vue
<template>
  <view>
    <wd-tree 
      :data="treeData" 
      checkable 
      :cascade="true"
      @checked="onChecked"
    />
    <view class="result">
      选中的节点: {{ checkedKeys.join(',') }}
    </view>
  </view>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const treeData = ref([
  {
    id: '1',
    label: '节点1',
    children: [
      {
        id: '1-1',
        label: '节点1-1'
      },
      {
        id: '1-2',
        label: '节点1-2'
      }
    ]
  },
  {
    id: '2',
    label: '节点2',
    children: [
      {
        id: '2-1',
        label: '节点2-1'
      }
    ]
  }
])

const checkedKeys = ref<string[]>([])

const onChecked = (keys: string[]) => {
  checkedKeys.value = keys
}
</script>

<style scoped>
.result {
  margin-top: 20px;
  padding: 10px;
  background-color: #f5f7fa;
  border-radius: 8px;
}
</style>

异步加载节点

vue
<template>
  <view>
    <wd-tree :data="asyncTreeData" :load-node="loadNode" />
  </view>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const asyncTreeData = ref([
  {
    id: '1',
    label: '异步节点1',
    isLeaf: false
  },
  {
    id: '2',
    label: '异步节点2',
    isLeaf: false
  }
])

const loadNode = (node: any) => {
  return new Promise((resolve) => {
    // 模拟异步加载
    setTimeout(() => {
      resolve([
        {
          id: `${node.id}-1`,
          label: `异步加载子节点 ${node.id}-1`,
          isLeaf: true
        },
        {
          id: `${node.id}-2`,
          label: `异步加载子节点 ${node.id}-2`,
          isLeaf: true
        }
      ])
    }, 1000)
  })
}
</script>

自定义节点内容

vue
<template>
  <view>
    <wd-tree :data="customTreeData">
      <template #content="{ node }">
        <view class="custom-content">
          <wd-icon name="folder" v-if="node.children && node.children.length > 0" color="#e6a23c" />
          <wd-icon name="file" v-else color="#909399" />
          <text class="node-label">{{ node.label }}</text>
          <wd-tag v-if="node.tag" :type="node.tag.type" size="small">{{ node.tag.text }}</wd-tag>
        </view>
      </template>
    </wd-tree>
  </view>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const customTreeData = ref([
  {
    id: '1',
    label: '文件夹1',
    tag: { type: 'primary', text: 'new' },
    children: [
      {
        id: '1-1',
        label: '文件1-1',
        tag: { type: 'success', text: 'done' }
      },
      {
        id: '1-2',
        label: '文件1-2',
        tag: { type: 'warning', text: 'warning' }
      }
    ]
  },
  {
    id: '2',
    label: '文件夹2',
    children: [
      {
        id: '2-1',
        label: '文件2-1'
      }
    ]
  }
])
</script>

<style scoped>
.custom-content {
  display: flex;
  align-items: center;
  gap: 8px;
}

.node-label {
  flex: 1;
}
</style>

搜索过滤

vue
<template>
  <view>
    <wd-input 
      v-model="searchText" 
      placeholder="搜索节点" 
      clearable 
      style="margin-bottom: 20px;"
    />
    <wd-tree 
      :data="searchTreeData" 
      :pattern="searchText"
      highlight-bg-color="#fff3cd"
    />
  </view>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const searchText = ref('')

const searchTreeData = ref([
  {
    id: '1',
    label: '北京',
    children: [
      {
        id: '1-1',
        label: '朝阳区'
      },
      {
        id: '1-2',
        label: '海淀区'
      },
      {
        id: '1-3',
        label: '西城区'
      }
    ]
  },
  {
    id: '2',
    label: '上海',
    children: [
      {
        id: '2-1',
        label: '浦东新区'
      },
      {
        id: '2-2',
        label: '黄浦区'
      }
    ]
  },
  {
    id: '3',
    label: '广州',
    children: [
      {
        id: '3-1',
        label: '天河区'
      },
      {
        id: '3-2',
        label: '越秀区'
      }
    ]
  }
])
</script>

样式定制指南

自定义展开/收缩按钮

vue
<template>
  <view>
    <wd-tree :data="customSwitcherTreeData">
      <template #switcher="{ hide, loading, expanded }">
        <view class="custom-switcher" v-if="!hide">
          <wd-loading v-if="loading" mode="semicircle" size="12" color="#409eff" />
          <text v-else class="switcher-icon">{{ expanded ? '▼' : '▶' }}</text>
        </view>
      </template>
    </wd-tree>
  </view>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const customSwitcherTreeData = ref([
  {
    id: '1',
    label: '节点1',
    children: [
      {
        id: '1-1',
        label: '节点1-1'
      }
    ]
  },
  {
    id: '2',
    label: '节点2'
  }
])
</script>

<style scoped>
.custom-switcher {
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.switcher-icon {
  font-size: 12px;
  color: #909399;
}
</style>

自定义节点样式

vue
<template>
  <view>
    <wd-tree 
      :data="customStyleTreeData" 
      selectable 
      selected-bg-color="#ecf5ff"
    />
  </view>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const customStyleTreeData = ref([
  {
    id: '1',
    label: '节点1',
    children: [
      {
        id: '1-1',
        label: '节点1-1'
      }
    ]
  },
  {
    id: '2',
    label: '节点2'
  }
])
</script>

<style scoped>
/* 自定义树形组件样式 */
.wd-tree {
  /* 自定义节点间距 */
  .wd-tree__content {
    padding: 8px 0;
    border-radius: 4px;
    margin: 2px 0;
  }
  
  /* 自定义标签样式 */
  .wd-tree__label {
    font-size: 14px;
    color: #606266;
  }
  
  /* 自定义选中状态样式 */
  .wd-tree__content {
    &:active {
      background-color: #f5f7fa;
    }
  }
}
</style>

注意事项

  1. 数据格式:确保传入的数据格式正确,每个节点必须包含唯一的keyField字段。
  2. 异步加载:使用loadNode异步加载数据时,需要在节点数据中设置isLeafField为false,否则不会触发加载。
  3. 级联选择:开启cascade后,父节点的选择状态会影响子节点,子节点的选择状态也会影响父节点。
  4. 搜索过滤:pattern属性用于搜索过滤,会自动展开匹配节点的所有祖先节点。
  5. 性能优化:对于大量节点的数据,建议使用虚拟滚动或分页加载,避免一次性渲染过多节点影响性能。
  6. 受控模式:使用checkedKeys和expandedKeys时,需要配合update:checked-keys和update:expanded-keys事件使用,实现受控模式。
  7. 自定义样式:可以通过customClass、customStyle以及插槽来自定义组件样式。
  8. 事件处理:组件提供了丰富的事件回调,可以根据业务需求进行处理。

📖 Released under the MIT License