uniapp中实现一个tree组件

原本打算通过递归实现,发现在小程序开发者工具中只能展示一级。最后采用了另外一种思路。把所有数据全部拍平根据是否有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 }
    ]
  }
])

注意

相关推荐
郑州光合科技余经理8 小时前
本地生活平台搭建:跨业态用户标识怎么贯通
java·开发语言·前端·后端·uni-app·php·ai编程
2501_915909061 天前
iOS应用从开发到上架App Store的完整发布流程与步骤指南
android·ios·小程序·https·uni-app·webview
不爱说话郭德纲1 天前
从“点点点”到一键出包:我把 uni-app x Android 离线打包做成了脚本
android·前端·uni-app
snotJam1 天前
关于Uniapp的Android自定义基座使用
android·uni-app
今年下半年2 天前
【微信小程序】uni-app + Vue3 + Vite 的小程序项目实现「进入指定范围才能打卡」的考勤功能
微信小程序·uni-app·定位·考勤
郑州光合科技余经理6 天前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
遗憾随她而去.7 天前
uniApp跨端开发: 解决不同平台显示差异问题
uni-app
程序鉴定师10 天前
2026年深圳小程序/App开发公司技术选型指南:从架构设计到交付标准全维度评估
java·小程序·uni-app·php·objective-c
Q264336502310 天前
【有源码】基于uni-app的旅游行程规划小程序-基于微信小程序的智慧文旅综合服务平台
java·微信小程序·小程序·uni-app·毕业设计·springboot·源代码
qq_3168377511 天前
阿里云百炼 fun-asr-realtime uniapp vue3 调用
javascript·vue.js·uni-app