原本打算通过递归实现,发现在小程序开发者工具中只能展示一级。最后采用了另外一种思路。把所有数据全部拍平根据是否有children自动缩进形成层级效果。该组件能实现勾选操作展开折叠的基础交互功能:
效果:
<template>
<view class="tree-container">
<view
v-for="(node, index) in flatTreeData"
:key="node.label + index"
:style="{ marginLeft: node.level * 20 + 'px' }"
class="tree-node"
>
<!-- 当前节点 -->
<view class="node-content">
<!-- 展开/收起图标 -->
<image
v-if="node.children && node.children.length > 0"
:src="node.expanded ? expandedIcon : collapsedIcon"
class="expand-icon"
@click.stop="toggleExpand(node)"
/>
<!-- 节点名称 -->
<text class="node-label" @click="toggleExpand(node)">{{ node.label }}</text>
<!-- 自定义选择框 -->
<image :src="node.checked ? checkedIcon : uncheckedIcon" class="checkbox-icon" @click="toggleCheck(node)" />
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
// 定义树节点接口
interface TreeNode {
label: string
checked?: boolean
expanded?: boolean
children?: TreeNode[]
level?: number // 添加层级信息
}
// 定义 props
const props = defineProps<{
treeList: TreeNode[]
}>()
// 图标路径
const checkedIcon = '/static/checkbox_checked.png' // 选中状态图标
const uncheckedIcon = '/static/checkbox_unchecked.png' // 未选中状态图标
const expandedIcon = '/static/arr_up.png' // 展开状态图标
const collapsedIcon = '/static/arr_down.png' // 收起状态图标
// 记录所有勾选的节点
const checkedNodes = ref<TreeNode[]>([])
// 扁平化树形数据
const flatTreeData = computed(() => {
const flatten = (nodes: TreeNode[], level = 0): TreeNode[] => {
let result: TreeNode[] = []
nodes.forEach((node) => {
// 添加层级信息
node.level = level
result.push(node)
// 如果节点展开且有子节点,递归处理子节点
if (node.expanded && node.children) {
result = result.concat(flatten(node.children, level + 1))
}
})
return result
}
let v = flatten(props.treeList)
console.log('v---', v)
return v
})
// 切换节点选中状态
const toggleCheck = (node: TreeNode) => {
// 切换当前节点的选中状态
node.checked = !node.checked
// 如果当前节点是父节点,则勾选所有子节点
if (node.children && node.children.length > 0) {
toggleChildren(node, node.checked)
}
// 更新 checkedNodes
updateCheckedNodes(node)
}
// 递归切换子节点状态
const toggleChildren = (node: TreeNode, checked: boolean) => {
if (node.children) {
node.children.forEach((child) => {
child.checked = checked
toggleChildren(child, checked) // 递归处理子节点
})
}
}
// 更新 checkedNodes
const updateCheckedNodes = (node: TreeNode) => {
if (node.checked) {
checkedNodes.value.push(node)
} else {
checkedNodes.value = checkedNodes.value.filter((n) => n !== node)
}
console.log('当前勾选的节点:', checkedNodes.value)
}
// 切换节点展开状态
const toggleExpand = (node: TreeNode) => {
if (node.children && node.children.length > 0) {
node.expanded = !node.expanded
}
}
// 递归为所有节点添加 checked 和 expanded 属性
const initTreeData = (nodes: TreeNode[]) => {
nodes.forEach((node) => {
node.checked = false // 默认不选中
node.expanded = false // 默认不展开
if (node.children) {
initTreeData(node.children) // 递归处理子节点
}
})
}
// 监听 treeList 的变化,初始化数据
watch(
() => props.treeList,
(newVal) => {
if (newVal && newVal.length > 0) {
initTreeData(newVal) // 初始化 checked 和 expanded 属性
}
},
{ immediate: true }
)
</script>
<style scoped>
.tree-container {
padding: 20px;
}
.tree-node {
margin-bottom: 8px;
}
.node-content {
display: flex;
align-items: center;
}
.checkbox-icon {
width: 20px;
height: 20px;
margin-right: 8px;
}
.node-label {
flex: 1;
}
.expand-icon {
width: 16px;
height: 16px;
margin-left: 8px;
}
</style>
在父组件中使用:
<Tree :treeList="treeList" />
const treeList = ref<TreeNode[]>([
{
label: 'Node 1',
checked: false,
expanded: false,
children: [
{
label: 'Node 1.1',
checked: false,
expanded: false,
children: [
{
label: 'Node 1.1.1',
checked: false,
expanded: false,
children: [
{
label: 'Node 1.1.1.1',
checked: false,
expanded: false,
children: [{ label: 'Node 1.1.1', checked: false }]
}
]
},
{ label: 'Node 1.1.2', checked: false }
]
},
{ label: 'Node 1.2', checked: false }
]
},
{
label: 'Node 2',
checked: false,
expanded: false,
children: [
{ label: 'Node 2.1', checked: false },
{ label: 'Node 2.2', checked: false }
]
}
])
注意