最近在做小程序项目,用的微信原生写法,业务用到了树型选择器,在网上找了一些weui,vantui都没有类似的组件,就自己手写了一个。
- 首先想到的肯定递归组件,我们现先创建一个组件.设置好图标,随便找一些图片都可以,这里我用了vantui中的图标.组件建议写在专门的组件文件夹下。
<!-- 树形组件 tree.wxml -->
<view>
<view style="padding-left:25rpx;">
<view wx:for="{{ treeList }}" wx:for-item="item" wx:key="id">
<view class="tree-item">
<view bind:tap="showChildren" data-id="{{ item.id }}" data-parentid="{{ item.parentId }}" class="tree-label">
<van-icon custom-style="{{ item.collapse?'transform:rotate(90deg)':'transform:rotate(0deg)' }}" name="play" wx:if="{{ item.children }}" />
<view class="{{ item.selected? 'current':'general' }}">{{item.name}}</view>
</view>
<van-icon
bind:tap="handleClick"
data-currentitem="{{ item }}"
wx:if="{{ !item.children && item.selected }}"
style="border-color: #1989fa;"
size="20px"
color="#1989fa"
name="checked"
/>
<van-icon
bind:tap="handleClick"
data-currentitem="{{ item }}"
wx:if="{{ !item.children && !item.selected }}"
size="20px"
name="circle"
color="#c8c9cc"
/>
</view>
<!-- 递归组件 -->
<block wx:if="{{ item.children && item.collapse }}">
<tree treeList="{{ item.children }}" treeListTemp="{{ treeListTemp }}" currentItem="{{ currentItem }}"></tree>
</block>
</view>
</view>
</view>
在组件的json文件中声明组件,“component”: true,调用自己。
{
"component": true,
"usingComponents": {
"van-icon": "@vant/weapp/icon/index",
"tree": "../tree/tree"
}
}
这是组件里的样式,主要区分选中状态的样式,这里大家可以再优化下。
/*tree.wxss*/
view{
font-size: 16px;
font-family: PingFangSC-Regular;
}
.tree-item{
display: flex;
justify-content: space-between;
align-items: center;
}
.tree-label{
padding-right: 15rpx;
flex: 1;
display: flex;
align-items: center;
position: relative;
}
.current{
color: #1989fa;
}
.general{
color: #1c2438;
}
然后是数据处理,设置选中状态及折叠状态,这里可以将选中的值冒泡给调用处去处理。下面的组件中的方法,我对每个方法都作了详细的说明。
// tree.ts
//树形单选组件
Component({
properties: {
treeList: Array, // 树形数组
treeListTemp: Array,
currentItem: Object
},
data: {},
methods: {
/**
* 点击标签展示子节点
* @param e
*/
showChildren: function (e: any) {
//点击项的id
let id = e.currentTarget.dataset.id;
// 父级id
let parentid = e.currentTarget.dataset.parentid;
this.findCurrentItem(id, this.data.treeList);
if (parentid == null) {
this.setData({
treeListTemp: this.data.treeList
})
} else {
this.hanleStatusChange(this.data.treeListTemp, parentid);
this.setData({
treeListTemp: this.data.treeListTemp
})
}
this.triggerEvent("resetTree", { changeList: this.data.treeListTemp }, { bubbles: true, composed: true });
},
/**
* 设置折叠状态
* @param list 处理集合
* @param parentid 父级id
*/
hanleStatusChange: function (list: AnyArray, parentid: Number) {
for (let i = 0; i < list.length; i++) {
if (list[i].id == parentid) {
list[i].children = this.data.treeList;
break;
} else {
if (list[i].children) {
this.hanleStatusChange(list[i].children, parentid);
}
}
}
},
/**
* 功能类型 collapse:点击标签展开收起
* */
findCurrentItem: function (id: Number, list: AnyArray) {
list.forEach(it => {
if (it.id == id) {
it.collapse = !it.collapse;
} else {
if (it.children) {
this.findCurrentItem(id, it.children);
}
}
})
},
/**
* 点击选择框
* @param e 节点
*/
handleClick: function (e: any) {
let checkedItem = e.currentTarget.dataset.currentitem;
console.log('选中项', e.currentTarget.dataset.currentitem);
this.setChooseStatus(checkedItem.id, this.data.treeListTemp);
checkedItem.selected = !checkedItem.selected;
this.setData({
treeListTemp: this.data.treeListTemp
})
this.triggerEvent("resetTree", { changeList: this.data.treeListTemp, checkedItem: checkedItem }, { bubbles: true, composed: true });
},
/**
* 设置选中状态
* @param id 选中项的id
*/
setChooseStatus(id: Number, list: AnyArray) {
list.forEach(el => {
if (el.id == id) {
el.selected = !el.selected;
} else {
el.selected = false;
if (el.children) {
this.setChooseStatus(id, el.children);
}
}
});
},
},
})
- 下面开始使用组件,在你页面引入这个树型组件,你可以使用一个容器控制显示与隐藏,这里我用的vantui中的popup弹出层组件。将你的树型数组传入组件,写一个监听事件,监听自组件的选中事件。
<!--index.wxml-->
<view class="container">
<view class="content">{{context}}</view>
<view class="btn">
<van-button bind:click="openPicker" type="primary" block>选择地区</van-button>
</view>
</view>
<van-popup show="{{ picker }}" close-on-click-overlay position="bottom" lock-scroll="{{ true }}" bind:click-overlay="closePicker">
<van-cell class="toolbar">
<view class="title-group">
<view bindtap="closePicker" class="cancel">取消</view>
<view class="picker-title">选择地区</view>
<view bindtap="confirm" class="confirm">确认</view>
</view>
</van-cell>
<view class="tree-style">
<custom-tree treeList="{{ list }}" treeListTemp="{{ list }}" bind:resetTree="resetTree" />
</view>
</van-popup>
页面的js文件
// index.ts
//@ts-nocheck
Page({
data: {
picker: false,
context: '',
list: [
{
id: '9',
parentId: null,
name: '上海市',
collapse: false,
selected: false,
children: [
{
id: '91',
parentId: '9',
name: '静安区',
collapse: false,
selected: false,
},
{
id: '92',
parentId: '9',
name: '静安区',
collapse: false,
selected: false,
}
]
},
{
id: '1',
parentId: null,
name: '江苏省',
collapse: false,
selected: false,
children: [
{
id: '11',
parentId: '1',
name: '南京市',
collapse: false,
selected: false,
children: [
{
id: '111',
parentId: '11',
name: '玄武区',
collapse: false,
selected: false,
}
]
}
]
},
{
id: '2',
parentId: null,
name: '浙江省',
collapse: false,
selected: false,
children: [
{
id: '21',
parentId: '2',
name: '金华市',
collapse: false,
selected: false,
children: [
{
id: '211',
parentId: '21',
name: '婺城区',
collapse: false,
selected: false,
}
]
}
]
}
],
currentCheck: {}, //当前选中项
},
/**
* 打开选择窗
*/
openPicker() {
this.setData({
picker: true
})
},
closePicker() {
this.setData({
picker: false
})
},
confirm() {
let { name } = this.data.currentCheck;
this.setData({
context: name,
picker: false
})
},
/**
* 监听事件,自组件选中时会触发,重置整棵树
*/
resetTree: function (e: any) {
this.setData({
currentCheck: e.detail.checkedItem,
list: e.detail.changeList
})
},
onLoad() { },
})
至此就完成了,如果有什么问题,欢迎在评论区留言或者私信我,完整代码已经传到了gitee https://gitee.com/jadecn/custom-tree上,也欢迎大家提Issue。