uniapp中实现一个tree组件

打印 上一主题 下一主题

主题 846|帖子 846|积分 2538

原本计划通过递归实现,发现在小程序开发者工具中只能展示一级。末了接纳了另外一种思绪。把所有数据全部拍平根据是否有children自动缩进形成层级结果。该组件能实现勾选操作展开折叠的底子交互功能:
结果:

  1. <template>
  2.   <view class="tree-container">
  3.     <view
  4.       v-for="(node, index) in flatTreeData"
  5.       :key="node.label + index"
  6.       :style="{ marginLeft: node.level * 20 + 'px' }"
  7.       class="tree-node"
  8.     >
  9.       <!-- 当前节点 -->
  10.       <view class="node-content">
  11.         <!-- 展开/收起图标 -->
  12.         <image
  13.           v-if="node.children && node.children.length > 0"
  14.           :src="node.expanded ? expandedIcon : collapsedIcon"
  15.           class="expand-icon"
  16.           @click.stop="toggleExpand(node)"
  17.         />
  18.         <!-- 节点名称 -->
  19.         <text class="node-label" @click="toggleExpand(node)">{{ node.label }}</text>
  20.         <!-- 自定义选择框 -->
  21.         <image :src="node.checked ? checkedIcon : uncheckedIcon" class="checkbox-icon" @click="toggleCheck(node)" />
  22.       </view>
  23.     </view>
  24.   </view>
  25. </template>
  26. <script setup lang="ts">
  27. import { ref, computed, watch } from 'vue'
  28. // 定义树节点接口
  29. interface TreeNode {
  30.   label: string
  31.   checked?: boolean
  32.   expanded?: boolean
  33.   children?: TreeNode[]
  34.   level?: number // 添加层级信息
  35. }
  36. // 定义 props
  37. const props = defineProps<{
  38.   treeList: TreeNode[]
  39. }>()
  40. // 图标路径
  41. const checkedIcon = '/static/checkbox_checked.png' // 选中状态图标
  42. const uncheckedIcon = '/static/checkbox_unchecked.png' // 未选中状态图标
  43. const expandedIcon = '/static/arr_up.png' // 展开状态图标
  44. const collapsedIcon = '/static/arr_down.png' // 收起状态图标
  45. // 记录所有勾选的节点
  46. const checkedNodes = ref<TreeNode[]>([])
  47. // 扁平化树形数据
  48. const flatTreeData = computed(() => {
  49.   const flatten = (nodes: TreeNode[], level = 0): TreeNode[] => {
  50.     let result: TreeNode[] = []
  51.     nodes.forEach((node) => {
  52.       // 添加层级信息
  53.       node.level = level
  54.       result.push(node)
  55.       // 如果节点展开且有子节点,递归处理子节点
  56.       if (node.expanded && node.children) {
  57.         result = result.concat(flatten(node.children, level + 1))
  58.       }
  59.     })
  60.     return result
  61.   }
  62.   let v = flatten(props.treeList)
  63.   console.log('v---', v)
  64.   return v
  65. })
  66. // 切换节点选中状态
  67. const toggleCheck = (node: TreeNode) => {
  68.   // 切换当前节点的选中状态
  69.   node.checked = !node.checked
  70.   // 如果当前节点是父节点,则勾选所有子节点
  71.   if (node.children && node.children.length > 0) {
  72.     toggleChildren(node, node.checked)
  73.   }
  74.   // 更新 checkedNodes
  75.   updateCheckedNodes(node)
  76. }
  77. // 递归切换子节点状态
  78. const toggleChildren = (node: TreeNode, checked: boolean) => {
  79.   if (node.children) {
  80.     node.children.forEach((child) => {
  81.       child.checked = checked
  82.       toggleChildren(child, checked) // 递归处理子节点
  83.     })
  84.   }
  85. }
  86. // 更新 checkedNodes
  87. const updateCheckedNodes = (node: TreeNode) => {
  88.   if (node.checked) {
  89.     checkedNodes.value.push(node)
  90.   } else {
  91.     checkedNodes.value = checkedNodes.value.filter((n) => n !== node)
  92.   }
  93.   console.log('当前勾选的节点:', checkedNodes.value)
  94. }
  95. // 切换节点展开状态
  96. const toggleExpand = (node: TreeNode) => {
  97.   if (node.children && node.children.length > 0) {
  98.     node.expanded = !node.expanded
  99.   }
  100. }
  101. // 递归为所有节点添加 checked 和 expanded 属性
  102. const initTreeData = (nodes: TreeNode[]) => {
  103.   nodes.forEach((node) => {
  104.     node.checked = false // 默认不选中
  105.     node.expanded = false // 默认不展开
  106.     if (node.children) {
  107.       initTreeData(node.children) // 递归处理子节点
  108.     }
  109.   })
  110. }
  111. // 监听 treeList 的变化,初始化数据
  112. watch(
  113.   () => props.treeList,
  114.   (newVal) => {
  115.     if (newVal && newVal.length > 0) {
  116.       initTreeData(newVal) // 初始化 checked 和 expanded 属性
  117.     }
  118.   },
  119.   { immediate: true }
  120. )
  121. </script>
  122. <style scoped>
  123. .tree-container {
  124.   padding: 20px;
  125. }
  126. .tree-node {
  127.   margin-bottom: 8px;
  128. }
  129. .node-content {
  130.   display: flex;
  131.   align-items: center;
  132. }
  133. .checkbox-icon {
  134.   width: 20px;
  135.   height: 20px;
  136.   margin-right: 8px;
  137. }
  138. .node-label {
  139.   flex: 1;
  140. }
  141. .expand-icon {
  142.   width: 16px;
  143.   height: 16px;
  144.   margin-left: 8px;
  145. }
  146. </style>
复制代码
在父组件中使用:
  1.   <Tree :treeList="treeList" />
  2. const treeList = ref<TreeNode[]>([
  3.   {
  4.     label: 'Node 1',
  5.     checked: false,
  6.     expanded: false,
  7.     children: [
  8.       {
  9.         label: 'Node 1.1',
  10.         checked: false,
  11.         expanded: false,
  12.         children: [
  13.           {
  14.             label: 'Node 1.1.1',
  15.             checked: false,
  16.             expanded: false,
  17.             children: [
  18.               {
  19.                 label: 'Node 1.1.1.1',
  20.                 checked: false,
  21.                 expanded: false,
  22.                 children: [{ label: 'Node 1.1.1', checked: false }]
  23.               }
  24.             ]
  25.           },
  26.           { label: 'Node 1.1.2', checked: false }
  27.         ]
  28.       },
  29.       { label: 'Node 1.2', checked: false }
  30.     ]
  31.   },
  32.   {
  33.     label: 'Node 2',
  34.     checked: false,
  35.     expanded: false,
  36.     children: [
  37.       { label: 'Node 2.1', checked: false },
  38.       { label: 'Node 2.2', checked: false }
  39.     ]
  40.   }
  41. ])
复制代码
注意

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

傲渊山岳

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表