前言
最近公司的小程序来了一个需求,需要实现一个树形结构的展示。pc端的tree组件可谓百家争鸣,拿来即用的组件很多,奈何在网上检索一番,并未发现小程序端有适合的tree组件,于是便开始了尝试,自己写一个简单的tree组件。(末尾附完整代码)
先看效果
开始
首先项目结构如图
模拟一下树形结构数据
index/index.js
data: {
dataTree: [
{
id: 1,
name: '一级A',
children: [
{
id: 23,
name: '二级A-a',
children: [
{
id: 98,
name: '三级A-a-1'
}
]
},
{
id: 20,
name: '二级A-b',
}
]
},
{
id: 2,
name: '一级B',
children: [
{
id: 21,
name: '二级B-a',
}
]
}
],
由于数据的层级是未知的,所以需要使用递归组件,即tree组件调用自身
tree/index.js
{
"component": true,
"usingComponents": {
"c-tree": "/component/tree/index"
}
}
只有当父节点被展开且存在子节点时才会展示递归组件
wx:if="{{item.children && item.children.length > 0 && item.open }}
tree/index.wxml
<c-tree
wx:if="{{item.children && item.children.length > 0 && item.open }}"
dataTree='{{ item.children }}'
selectKey="{{selectKey}}"
isSelectLastNode="{{isSelectLastNode}}"
isOpenAll="{{isOpenAll}}"
>
</c-tree>
接下来是组件向页面传递一个自定义事件select
item : 被点击的数据
isSelectLastNode: 配置参数,是否必须选择最后一个节点
tree/index.js
select(e) {
const item = e.currentTarget.dataset.item
if(this.properties.isSelectLastNode) {
console.log(item)
if (!item.children || item.children.length == 0) {
this.triggerEvent('select', { item: item }, { bubbles: true, composed: true })
} else {
this.triggerEvent('select', { tips: '必须选择最后一个节点' }, { bubbles: true, composed: true })
}
} else {
this.triggerEvent('select', { item: item }, { bubbles: true, composed: true })
}
}
注意:
为了给选中的节点加上样式,在页面引入tree组件时需要传入一个参数selectKey
,其值为节点数据的id,所以在页面拿到组件回传的数据时都要更新selectKey
完整代码
tree组件
index.js
// pages/common/comTree/index.js
/**
dataTree = [
{
id: 1,
name: '一级名称',
children: []
}
]
*/
Component({
/**
* 组件的属性列表
*/
properties: {
dataTree: {
type: Array,
value: []
},
selectKey: { // 选中的节点id
type: String,
value: ''
},
isSelectLastNode: { //是否必须选中最后一节点
type: Boolean,
value: false
},
isOpenAll: { //是否展开全部节点
type: Boolean,
value: false
}
},
observers: {
'dataTree': function(params) {
params.forEach(v => {
v.open = this.properties.isOpenAll // 是否展开
})
this.setData({
tree: params
})
}
},
/**
* 组件的初始数据
*/
data: {
tree: []
},
/**
* 组件的方法列表
*/
methods: {
isOpen(e) {
const open = 'tree[' + e.currentTarget.dataset.index + '].open'
this.setData({
[open]: !this.data.tree[e.currentTarget.dataset.index].open
})
},
select(e) {
const item = e.currentTarget.dataset.item
if(this.properties.isSelectLastNode) {
console.log(item)
if (!item.children || item.children.length == 0) {
this.triggerEvent('select', { item: item }, { bubbles: true, composed: true })
} else {
this.triggerEvent('select', { tips: '必须选择最后一个节点' }, { bubbles: true, composed: true })
}
} else {
this.triggerEvent('select', { item: item }, { bubbles: true, composed: true })
}
}
}
})
index.wxml
<!--pages/common/comTree/index.wxml-->
<view wx:for="{{tree}}" wx:key="index" class="tree">
<view class="tree-item tree-item-select">
<view class="tree-item-onOff" wx:if="{{item.children && item.children.length > 0}}" bindtap="isOpen" data-index="{{index}}">
<image src="/assets/u1490.svg" class="{{item.open ? 'tree-item-onOff-open' : 'tree-item-onOff-closed'}}"></image>
</view>
<view class="tree-item-onOff" wx:else>
</view>
<view class="tree-item-name {{selectKey == item.id ? 'tree-item-name-select' : '' }}" bindtap="select" data-item="{{item}}" data-index="{{index}}">
<view class="name">{{item.name}}</view>
<view class="img">
<!-- <image wx:if="{{selectKey == item.id }}" src="/assets/icon/u435.svg"></image> -->
</view>
</view>
</view>
<c-tree
wx:if="{{item.children && item.children.length > 0 && item.open }}"
dataTree='{{ item.children }}'
selectKey="{{selectKey}}"
isSelectLastNode="{{isSelectLastNode}}"
isOpenAll="{{isOpenAll}}"
>
</c-tree>
</view>
index.wxss
.tree {
text-align: left;
/* height: 45px;
line-height: 45px; */
padding-left: 15px;
}
.tree-item {
height: 45px;
line-height: 45px;
display: flex;
/* padding-left: 15px; */
}
.tree-item-onOff {
width: 30px;
display: flex;
justify-content: center;
align-items: center;
}
.tree-item-onOff image {
width: 9px;
height: 5px;
display: block;
transition: 0.4s;
}
.tree-item-onOff-closed {
transform: rotate(-90deg);
}
.tree-item-onOff-open {
transform: rotate(0deg);
}
.tree-item-name {
width: calc(100% - 40px);
display: flex;
padding-left: 10px;
}
.name {
width: calc(100% - 50px);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.img {
width: 50px;
display: flex;
justify-content: center;
align-items: center;
}
.img image {
width: 15px;
height: 10px;
display: block;
}
.tree-item-name-select {
background: #ECF7FA;
color: #0079FE;
}
.tree-item-name-select2 {
color: #0079FE;
}
index.json
{
"component": true,
"usingComponents": {
"c-tree": "/component/tree/index"
}
}
index页面
index.json
{
"usingComponents": {
"tree": "/component/tree/index"
}
}
index.wxml
<!--index.wxml-->
<view class="container">
<tree
dataTree="{{dataTree}}"
selectKey="{{selectKey}}"
bind:select="handleSelect"
isSelectLastNode="true"
isOpenAll="true"
></tree>
</view>
index.js
Page({
data: {
dataTree: [
{
id: 1,
name: '一级A',
children: [
{
id: 23,
name: '二级A-a',
children: [
{
id: 98,
name: '三级A-a-1'
}
]
},
{
id: 20,
name: '二级A-b',
}
]
},
{
id: 2,
name: '一级B',
children: [
{
id: 21,
name: '二级B-a',
}
]
}
],
selectKey: '', //选中的节点id
},
handleSelect(e) {
if (e.detail.tips) {
console.log('必须选择到最后一个节点')
} else {
this.setData({
selectKey: e.detail.item.id
})
}
},
onLoad: function () {
}
})