Home

微信小程序自用Tree树形控件

使用有赞的 vant 组件库开发微信小程序过程中,使用到了 tree 树形控件,但 vant 组件库无此组件,所以手撸了一个简陋二级 tree 树形控件

功能包含

全选按钮功能、反选按钮功能 父级列表前的开关 icon 子级列表的选中的禁止或启用 父级列表显示子级列表可选数量 父级下所有可选子级选中则父选中(禁用状态不算) 使用了 van-collapse 组件带有折叠关闭动画效果 …

效果展示

微信小程序自用Tree树形控件

微信小程序自 Tree 树形控件 WXML 代码部分

<van-collapse value="{{ activeNames }}" bind:change="onChange">
	<block wx:for="{{wrongList}}" wx:for-item="itm" wx:key="index">
		<van-collapse-item name="{{itm.code}}" data-hans="itm.code">
			<view slot="title">
				<image src="https://xxxx/{{KG[itm.code]!=true ?'open':'close'}}.png" style="width:20rpx" mode="widthFix" />
				{{itm.name}}
				<view catchtap="catchtap">
					<van-checkbox class="fuCheck" disabled="{{tempCodeArr[itm.code].length==0}}" value="{{ checkedAll[itm.code] }}" data-hans="{{itm.code}}" bind:change="checkcheck" />
					<view class="counts" style="right:106rpx">({{itm.fallibleCount}})</view>
				</view>
			</view>
			<view class="items">
				<van-checkbox-group value="{{ choisObj[itm.code] }}" data-hanscode="{{itm.code}}" bind:change="checkChange">
					<van-cell-group>
						<block wx:if="{{!_itm.hansFu}}" wx:for="{{ itm.children }}" wx:for-index="_index" wx:for-item="_itm" wx:key="code">
							<van-cell title="{{ _itm.name }}" value-class="value-class" clickable>
								<van-checkbox name="{{ _itm.code }}" disabled="{{_itm.fallibleCount=='0'?true:false}}" />
								<view class="counts">({{_itm.fallibleCount}})</view>
							</van-cell>
						</block>
					</van-cell-group>
				</van-checkbox-group>
			</view>
		</van-collapse-item>
	</block>
</van-collapse>

微信小程序自 Tree 树形控件 JS 部分

/*
 * @Author: Han
 * @Date: 2021-01-04 14:18:09
 * @LastEditors: Han
 * @LastEditTime: 2021-01-04 15:07:09
 * @FilePath: \wechat-app\tree.js
 */
import { get } from "api";
Page({
	data: {
		// 父级按钮合集
		checkedAll: [],
		// 面板状态合集
		activeNames: [],
		// 章节List
		wrongList: [],
		// 选中的子节点合集
		choisObj: {},
		// 临时 父 子 数组
		tempCodeArr: {},
		// 全选按钮状态
		selectAllStatus: true
	},
	// 全选事件
	selectAll() {
		const _this = this;
		const status = this.data.selectAllStatus;
		const okTempCodeArr = JSON.parse(JSON.stringify(_this.data.tempCodeArr));
		Object.keys(okTempCodeArr).forEach(itm => {
			okTempCodeArr[itm].length == 0 && delete okTempCodeArr[itm];
		});
		// 模拟点击
		Object.keys(okTempCodeArr).forEach(itm => {
			_this.checkChange({
				currentTarget: {
					dataset: {
						hanscode: itm
					}
				},
				detail: status ? okTempCodeArr[itm] : []
			});
		});
		this.setData({
			selectAllStatus: !status
		});
	},
	// 父级按钮
	checkcheck(e) {
		const codes = e.currentTarget.dataset.hans;
		// 判断父级按钮状态
		this.setData({
			checkedAll: {
				...this.data.checkedAll,
				[codes]: e.detail
			},
			choisObj: {
				...this.data.choisObj,
				[codes]: e.detail ? this.data.tempCodeArr[codes] : []
			}
		});
		// 去除空对象,并设置按钮状态
		const tempObj = this.data.choisObj;
		Object.keys(tempObj).forEach(itm => {
			tempObj[itm].length == 0 && delete tempObj[itm];
		});
		this.setData({
			choisObj: tempObj,
			btnStatus: Object.keys(tempObj).length
		});
	},
	// 子级按钮点击选中或非事件
	checkChange(e) {
		const codes = e.currentTarget.dataset.hanscode;
		// 可选中的是否全选
		const status = e.detail.length == this.data.tempCodeArr[codes].length;
		this.setData({
			choisObj: {
				...this.data.choisObj,
				[codes]: e.detail
			},
			checkedAll: {
				...this.data.checkedAll,
				[codes]: status
			}
		});
		const tempObj = this.data.choisObj;
		Object.keys(tempObj).forEach(itm => {
			tempObj[itm].length == 0 && delete tempObj[itm];
		});
		this.setData({
			choisObj: tempObj,
			btnStatus: Object.keys(tempObj).length
		});
	},
	// 折叠面板切换事件
	onChange(event) {
		let tempArr = [];
		// 当前面板折叠状态 临时变量
		let key = false;

		// 由于面板可以多个同时展开,所以 ?
		// 控制面板标题前 图片的 + 或 -
		if (this.data.activeNames.length > event.detail.length) {
			// 深拷贝
			tempArr = JSON.parse(JSON.stringify(this.data.activeNames));
			event.detail.forEach(itm => {
				const n = tempArr.indexOf(itm);
				n != -1 && tempArr.splice(n, 1);
			});
			key = false;
		} else {
			tempArr = JSON.parse(JSON.stringify(event.detail));
			this.data.activeNames.forEach(itm => {
				const n = tempArr.indexOf(itm);
				n != -1 && tempArr.splice(n, 1);
			});
			key = true;
		}
		this.setData({
			activeNames: event.detail,
			KG: {
				...this.data.KG,
				[tempArr[0]]: key
			}
		});
	},
	// 切换事件
	onSwitchChange(e) {
		// 切换时,清空除额外所有数据
		this.setData({
			xx: [],
			xx: {},
			...xx
		});
		// 之后重新获取列表
		this.XXX();
	},

	// 获取书本信息
	async getBookArr() {
		wx.showLoading({
			title: "获取数据中"
		});
		let tempArr = [];
		const _this = this;
		const {
			ret: { bookList: _res }
		} = await get("api");
		wx.hideLoading();
		// 书本信息赋给下拉框
		Array.isArray(_res) &&
			_res.forEach(itm => {
				tempArr.push({
					text: itm.name,
					value: itm.id
				});
			});
		tempArr.length &&
			_this.setData({
				bookDataArr: tempArr
			});
	},
	// 获取章节列表
	async getWrongList() {
		const _this = this;
		const _res = await get(`api`);
		let _tempArr = _res.ret.bookCatalogs;
		// 章节数据处理
		Array.isArray(_tempArr) &&
			_tempArr.length &&
			_tempArr.forEach((itm, idx) => {
				let tempArrs = [];
				itm.fallibleCount = Number(itm.fallibleCount);
				itm.children.length
					? itm.children.forEach(_itm => {
							itm.fallibleCount += Number(_itm.fallibleCount);
							_itm.fallibleCount != "0" && tempArrs.push(_itm.code);
					  })
					: _tempArr[idx].children.push({
							name: itm.name,
							code: itm.code,
							fallibleCount: itm.fallibleCount,
							hansFu: true
					  });
				_this.setData({
					tempCodeArr: {
						..._this.data.tempCodeArr,
						[itm.code]: tempArrs
					}
				});
			});
		_res.ret &&
			this.setData({
				wrongList: _tempArr
			});
	},
	// 防止父级点击事件冒泡空事件
	catchtap() {}
});