当前位置: 首页 > news >正文

微信小程序:实现树形结构组件

实现可折叠展开的树形菜单 + 复选框联动功能

在开发企业级管理系统或权限管理模块时,常常需要展示具有层级关系的数据,如部门-员工结构、权限组等。这时候我们通常会使用“树形结构”来展示数据。本文将手把手教你如何在微信小程序中实现一个支持展开/收起 多级复选框联动 的树形结构组件。


一、最终效果预览

我们将实现如下功能:

支持多级节点
点击箭头展开/收起子节点
点击复选框选择节点
一级节点选中时自动选中所有子节点
获取所有选中的叶子节点信息


二、项目结构说明

本项目包含两个主要部分:

  1. 主页面 Page
  2. 树形组件 TreeNode

组件化设计使得树形结构更易维护、复用。


三、主页面实现

1. WXML 结构

<view class="tree-container"><block wx:for="{{treeData}}" wx:key="id"><tree-node data="{{item}}" index="{{index}}" indent="0"expandedNodes="{{expandedNodes}}"selectedNodes="{{selectedNodes}}"bindtoggle="toggleNode" bindselect="selectNode"isTopLevel="{{true}}"></tree-node></block>
</view>
<!-- <button bind:tap="getSelData"></button> -->

2. JS 数据与方法

通过children标记为子项,如果有数据标识为有子信息就会再次执行树形组件将子项进行展示

getSelData为视图层中提交按钮的方法,可以直接获取叶子节点的信息

Page({data: {treeData: [{id: 1,name: "员工组1",children: []},{id: 2,name: "员工组2",children: []},{id: 3,name: "员工组3",children: [{id: 31,name: "员工组3-1",// children: [//   {//     id: 311,//     name: "员工组3-1-1",//     children: []//   },//   {//     id: 312,//     name: "员工组3-1-2",//     children: []//   }// ]},{id: 32,name: "员工组3-2",children: []}]},{id: 4,name: "员工组4(空组)",children: []}],expandedNodes: [], //默认展开项selectedNodes: []},toggleNode(e) {const {id} = e.detail;const {expandedNodes} = this.data;const newExpandedNodes = new Set(expandedNodes);if (newExpandedNodes.has(id)) {newExpandedNodes.delete(id);} else {newExpandedNodes.add(id);}this.setData({expandedNodes: [...newExpandedNodes] // 转为数组});},selectNode(e) {const {id} = e.detail;const {treeData,selectedNodes} = this.data;// 查找被点击的节点const findNode = (nodes, targetId) => {for (const node of nodes) {if (node.id === targetId) return node;if (node.children) {const found = findNode(node.children, targetId);if (found) return found;}}};const targetNode = findNode(treeData, id);// 判断是否是一级节点(根据您的数据结构)const isTopLevel = treeData.some(item => item.id === id);let newSelectedNodes = [...selectedNodes];if (isTopLevel) {// 一级节点:选中/取消所有子节点const childIds = this.getAllChildIds(targetNode);if (newSelectedNodes.includes(id)) {// 取消选中(移除当前节点和所有子节点)newSelectedNodes = newSelectedNodes.filter(nodeId => nodeId !== id && !childIds.includes(nodeId));} else {// 选中(添加当前节点和所有子节点)newSelectedNodes = [...newSelectedNodes, id, ...childIds];}} else {// 非一级节点保持原逻辑if (newSelectedNodes.includes(id)) {newSelectedNodes = newSelectedNodes.filter(nodeId => nodeId !== id);} else {newSelectedNodes = [...newSelectedNodes, id];}}this.setData({selectedNodes: newSelectedNodes});},// 获取所有子节点IDgetAllChildIds(node) {if (!node.children || node.children.length === 0) return [];return node.children.reduce((acc, child) => {return [...acc, child.id, this.getAllChildIds(child)];}, []);},//查询选中的数据getSelectedNodeDetails() {//获取全部数据// const { treeData, selectedNodes } = this.data;// const result = [];// // 递归查找匹配的节点// const findNodeById = (nodes) => {//   for (const node of nodes) {//     if (selectedNodes.includes(node.id)) {//       result.push({ id: node.id, name: node.name });//     }//     if (node.children && node.children.length > 0) {//       findNodeById(node.children);//     }//   }// };// findNodeById(treeData);// return result;//获取叶子节点const {treeData,selectedNodes} = this.data;const result = [];const isTopLevel = (id) => {return treeData.some(node => node.id === id);};const findLeafNodes = (nodes) => {for (const node of nodes) {if (selectedNodes.includes(node.id)) {// 排除一级节点 && 必须是叶子节点if (!isTopLevel(node.id) && (!node.children || node.children.length === 0)) {result.push({id: node.id,name: node.name});}}if (node.children && node.children.length > 0) {findLeafNodes(node.children);}}};findLeafNodes(treeData);return result;},//获取全部数据getSelData() {console.log(this.getSelectedNodeDetails())}
});

3. JSON 配置引入组件

{"usingComponents": {"tree-node":"/components/TreeNode/index"},"navigationBarTitleText": "测试","navigationBarBackgroundColor": "#f5f5f5","navigationBarTextStyle":"black"
}

四、树形组件实现(TreeNode)

1. WXML 结构

<view class="tree-node" style="margin-left: {{indent}}px;"><view class="tree-line"><!-- 箭头图标 --><view class="toggle-icon {{hasChildren ? '' : 'hidden'}}" bindtap="toggle">{{isExpanded ? '▼' : '▶'}}</view><!-- 复选框 --><view class="checkbox" bindtap="select" style="background: {{isSelected ? '#07c160' : '#fff'}}"><text class="checkbox-icon">{{isSelected ? '✓' : ''}}</text></view><!-- 节点名称 --><text class="node-name">{{data.name}}</text></view><!-- 子节点 --><view class="children-container" wx:if="{{isExpanded && hasChildren}}"><block wx:for="{{data.children}}" wx:key="id"><!-- 修改这里为绝对路径 --><tree-node data="{{item}}" indent="{{indent + 15}}" expandedNodes="{{expandedNodes}}" selectedNodes="{{selectedNodes}}" bindtoggle="onChildToggle" bindselect="onChildSelect"></tree-node></block></view>
</view>

2. Component JS 逻辑

Component({properties: {data: Object,indent: {type: Number,value: 0},isTopLevel: {  // 新增属性type: Boolean,value: false},expandedNodes: Array,selectedNodes: Array},data: {isExpanded: false,isSelected: false,hasChildren: false},attached() {// console.log('Node data:', this.data.data);// console.log('Has children:', this.data.hasChildren);// console.log('Is expanded:', this.data.isExpanded);},observers: {'data.children': function(children) {this.setData({hasChildren: !!(children && children.length) // 确保布尔值});},'expandedNodes': function(expandedNodes) {this.setData({isExpanded: expandedNodes.includes(this.properties.data.id)});},'selectedNodes': function(selectedNodes) {this.setData({isSelected: selectedNodes.includes(this.properties.data.id)});}},methods: {toggle() {if (!this.data.hasChildren) return;this.triggerEvent('toggle', { id: this.properties.data.id });},select() {this.triggerEvent('select', { id: this.properties.data.id });},onChildToggle(e) {this.triggerEvent('toggle', e.detail);},onChildSelect(e) {this.triggerEvent('select', e.detail);},}
});

3. CSS 样式

.tree-node {display: flex;font-size: 16px;flex-direction: column;font-size: 90%;justify-content: flex-start; /* 垂直对齐方式 */
}.toggle-icon {width: 15px;height: 15px;margin:0 1px;display: flex;align-items: center;justify-content: center;font-size: 12px;color: rgb(68, 68, 68);
}.toggle-icon.hidden {visibility: hidden;
}.checkbox {width: 13px;height: 13px;margin-right: 4px;border-radius: 4px;display: flex;align-items: center;justify-content: center;
}.checkbox-icon {font-size: 10px;color: #fff;
}.node-name {flex: 1;
}.children-container {border-left: 1px dashed #eee;
}.tree-line{display: flex;justify-content: center;align-items: center;padding:5px 0;
}/* 禁用状态样式 */
.checkbox.disabled {background-color: #f5f5f5;border-color: #ddd !important;
}.checkbox.disabled .icon {color: rgb(133, 133, 133);
}/* 保持图片中的箭头样式 */
.toggle-icon {opacity: 0.5; /* 箭头也变灰 */
}

4. JSON 声明组件

需要再次引用改组件,因为这里使用的是递归的方式,将二级、三级标题进行循环展示

{"component": true,"usingComponents": {"tree-node":"/components/TreeNode/index"}
}

http://www.lryc.cn/news/576174.html

相关文章:

  • 【MySQL进阶】服务器配置与管理——系统变量,选项,状态变量
  • 将ONNX模型转换为(OPENMV可用的格式)TensorFlow Lite格式
  • Flotherm许可状态检查
  • Godot4.3类星露谷游戏开发之【简易库存】(UI部分)
  • HTTPS hostname wrong: should be <xxx>错误解决
  • 【大模型水印论文阅读2】前缀文本编码、均匀性约束
  • Stable Diffusion 3终极提示词库:2000个工业设计场景生成公式(2025企业级实战指南)
  • 强化学习理论基础:从Q-learning到PPO的算法演进(2)
  • openGL学习(基本窗口)
  • [ linux-系统 ] 磁盘与文件系统
  • 【论文阅读 | CVPR 2025 |MambaVision:一种混合 Mamba-Transformer 视觉骨干网络】
  • 2025.6.27总结
  • 机器人 URDF学习笔记
  • Windows 10 ARM64平台CAN程序开发
  • 飞凌A40i使用笔记
  • React中的ErrorBoundary
  • 【Yonghong 企业日常问题08 】永洪BI的Apache Tomcat版本升级指南
  • 【CV数据集介绍-40】Cityscapes 数据集:助力自动驾驶的语义分割神器
  • 攻防世界-MISC-Cephalopod
  • gemini-cli 踩坑实录
  • ARM64 linux系统的一般执行过程
  • C++ 函数特性详解:默认参数、重载、引用与指针区别
  • Flutter 网络请求指南, 从 iOS 到 Flutter 的 Dio + Retrofit 组合
  • 《聊一聊ZXDoc》之汽车服务导向SOME/IP
  • 【k近邻】 K-Nearest Neighbors算法原理及流程
  • 在shell中直接调用使用R
  • 远眺科技工业园区数字孪生方案,如何实现智能管理升级?
  • 告别堡垒机和VPN!Teleport:下一代基础设施统一访问入口
  • CTP IC失效现象和失效原理分析
  • 利用python实现NBA数据可视化