小程序树状图实现

作者: 小疯子 分类: 小程序 发布时间: 2020-03-27 17:02

前言

作为一个非前端开发的,小程序使用的js来实现树状图百度了半天的资料都没有实现的,最后用谷歌了一下十分钟找到了解决方案,因为前端知识掌握不多,所以代码或函数使用的有些乱七八糟的,但是最终功能可以实现了凑合记录看看吧
最终实现的结构差不多是长着这样子,无限极树状图(当然还是限制一下最大到多少级,元素控制一下数量要不然递归起来慢死)

实现步骤

1. 树状图组件(treeMenu)

treeMenu.wxml

<view class="ul re">
  <view class="li li{{item.id}}" wx:if="{{model.length>0}}" wx:for="{{model}}" wx:key="id" wx:for-index="idx" wx:for-item="item">

   <view class='span span{{item.id}} re' data-id="{{item.id}}" bindtap='tapItem' style="background-color:{{item.del_flag==null?'':'#9f9f9f'}}">
      <view class='name'>{{item.name}}</view>
    </view>

    <view class='dai bb'>
      <view class='plus' wx:if="{{item.children!=undefined&&item.children!=null&&item.children.length>0}}" bindtap='toggle' data-id='{{item.id}}' data-expand='{{item.expand}}' >
        <image src="../../images/{{item.expand!=true?'right':'down'}}.png" class="menu-img" ></image>
      </view>
    </view>

    <view class='operatelist' wx:if="{{isedit==true}}">
      <button type="default" class='editc' bindtap="edit" data-id='{{item.id}}'>修改</button>
      <button type="default" class='quickeditc' bindtap="quickedit">快改</button>
      <button type="default" class='delete' wx:if="{{item.children==undefined||item.children==null||item.children.length<=0}}" bindtap="deleteitem" data-id='{{item.id}}'>删除</button>
    </view>

    <view class='ul' data-id='{{item.id}}' data-expand='{{item.expand}}' wx:if="{{item.children!=undefined&&item.children!=null&&item.children.length>0}}" hidden="{{item.expand!=true}}">
      <treeMenu model='{{item.children}}' isedit="{{isedit}}"></treeMenu>
    </view>

  </view>
</view>

treeMenu.js:

// pages/components/mytree/mytree.js
Component({
  properties: {
    model: Object,
    isedit: {
      type: Boolean,
      value: false
    }
  },
  data: {
    open: false,
    isBranch: false,
  },

  methods: {
    toggle: function (e) {
      var id = e.currentTarget.dataset.id;
      var isexpand = e.currentTarget.dataset.expand;
      if (isexpand == null || isexpand == 'undefined' || isexpand == '') {
        isexpand = false;
      }
      this.triggerEvent('toggle', { itemid: id, isexpand: isexpand }, { bubbles: true, composed: true });
    },

    edit: function (e) {
      var id = e.currentTarget.dataset.id;
      this.triggerEvent('edit', { itemid: id }, { bubbles: true, composed: true });
    },

    deleteitem: function (e) {
      var id = e.currentTarget.dataset.id;
      this.triggerEvent('deleteitem', { itemid: id }, { bubbles: true, composed: true });

    },

    tapItem: function (e) {

      console.log('use multiindexof result is:' + JSON.stringify(this.properties.model))
      var itemid = e.currentTarget.dataset.id;
      console.log('组件里点击的id: ' + itemid);

      this.triggerEvent('tapitem', { itemid: itemid }, { bubbles: true, composed: true });
    }
  },

  ready: function (e) {
    this.setData({
      isBranch: Boolean(this.data.model.children && this.data.model.children.length),
    });
    // console.log(this.data);
  },
})

treeMenu.json

{
  "component": true,
  "usingComponents": {
    "treeMenu": "/components/treeMenu/treeMenu"
  }
}

treeMenu.css

.page {
  min-height: 100%;
  background-color: #fff;
  color: #524b4b;
}
.ul {
  padding-left:26rpx;
  position: relative;
}

.li {
  list-style-type: none;
  margin: 0;
  padding: 15rpx 0rpx 0rpx 0;
  position: relative;
}

.li::before, .li::after {
  content: '';
  left: 0rpx;
  position: absolute;
  right: auto;
}

.li::before {
  border-left: 1rpx solid #999;
  bottom: 100rpx;
  height: 100%;
  top: 0;
  width: 2rpx;
}

.li::after {
  border-top: 1rpx solid #999;
  height: 60rpx;
  top: 50rpx;
  width: 55rpx;
}

.li .span {
  width: 95%;
  height: 66rpx;
  text-align: left;
  line-height: 66rpx;
  border-bottom: 1rpx solid #e5e5e5;
  /* border-radius: 35rpx; */
  display: inline-block;
  padding: 4rpx 6rpx 0 6rpx;
  text-decoration: none;
  font-size: 32rpx;
  position: relative;
  left: 70rpx;
}

.ul>.li1::before, .ul>.li1::after {
  border-top: 1rpx solid #999;
  height: 40rpx;
  top: 50rpx;
  width: 140rpx;
  border-left: 0;
}

.li:last-child::before {
  height: 52rpx;
}

.texts {
  width: 45rpx;
  height: 45rpx;
  line-height: 45rpx;
  position: absolute;
  right: -8%;
  top: -28%;
  border-radius: 50%;
  background-color: #afafaf;
  font-size: 27rpx;
  color: #fff;
}

.dai {
  position: absolute;
  font-size: 27rpx;
  left: 20rpx;
  top: 34rpx;
  background-color: #f9f9f9;
  z-index: 999;
  padding: 0 3rpx;
  display: flex;
  align-items: center;
  border: 1rpx solid #e5e5e5;
}

.dai image {
  width: 44rpx;
  height: 44rpx;
  display: inline-block;
}

.plus {
  width: 40rpx;
  height: 40rpx;
  line-height: 40rpx;
  text-align: center;
  border-radius: 50%;
  /* background-color: #5c5c5c; */
}
.operatelist {
  flex-direction: row;
  display: flex;
  position: absolute;
  right: 10rpx;
  top: 37rpx;
}

.operatelist button {
  line-height: 35rpx;
  size: default;
  min-height: 25rpx;
  width: 76rpx;
  padding: 5rpx 5rpx 5rpx 5rpx;
  font-size: 32rpx;
  color: #999;
  font-weight: normal;
  border: 0.5rpx solid #999
}
.operatelist .quickeditc {
  margin-left: 15rpx;
  color: #06ae56;
  border-color: #06ae56
}
.operatelist .delete {
  margin-left: 15rpx;
  color: #f40;
  border-color: #f40;
}

2. 页面引用组件

categoryManage.wxml

<treeMenu model='{{categories}}' isedit='true' bind:tapitem='tapItem' bind:toggle="toggle"
    bind:edit='edit' bind:deleteitem='deleteitem'></treeMenu>

categoryManage.js:
这个比较留意一下,其中包含了数据的格式,以及针对点击实现的expand展开和关闭

// miniprogram/pages/categoryManage/categoryManage.js
Page({

  /**
   * Page initial data
   */
  data: {
    categories: [],
  },

  /**
   * Lifecycle function--Called when page load
   */
  onLoad: function (options) {
    const categories_data = [
      {
        name: '全部xxxxxxxx',
        id: '000',
        children: [
          {
            name: '全部',
            id: '0001',
            children: [
              {
                name: '全部',
                id: '0002'
              }
            ]
          }
        ]
      },
      {
        name: '衣服',
        id: '111',
        children: [{
          name: '冬装',
          id: '1112',
          children: [
            {
              name: '毛衣',
              id: '1113',
            },
            {
              name: '羽绒服',
              id: '1114',
            },
            {
              name: '秋裤',
              id: '1115',
            }
          ]
        },
        {
          name: '夏装',
          id: '1116',
          children: [
            {
              name: '连衣裙',
              id: '1117',
            },
            {
              name: '短裤',
              id: '1181',
            },
            {
              name: '打底内衣',
              id: '1191',
            }
          ]
        }
        ]
      },
      {
        name: '家电',
        id: '1121',
        children: [{
          name: '厨房用品',
          id: '1211',
          children: [
            {
              name: '微波炉',
              id: '1311',
            },
            {
              name: '面包机',
              id: '1411',
            },
            {
              name: '打蛋器',
              id: '1511',
            }
          ]
        },
        {
          name: '电子娱乐',
          id: '1611',
          children: [
            {
              name: 'switch',
              id: '1711',
            },
            {
              name: 'xbox',
              id: '1811',
            }
          ]
        }
        ]
      },
      {
        name: '箱包',
        id: '1911',
        children: [{
          name: '行李箱',
          id: '211',
          children: [
            {
              name: '大行李箱',
              id: '311',
            }
          ]
        },
        {
          name: '双肩包',
          id: '411',
          children: [
            {
              name: '包包1号',
              id: '511',
            },
            {
              name: '包包2号',
              id: '611',
            }
          ]
        },
        {
          name: '单肩包',
          id: '171',
          children: [
            {
              name: '粉红小包',
              id: '181',
            },
            {
              name: '大紫色单肩',
              id: '191',
            }
          ]
        },
        {
          name: '钱包',
          id: '11',
          children: [
            {
              name: '老公黑钱包',
              id: '12',
            },
            {
              name: '我的红钱包',
              id: '13',
            }
          ]
        }
        ]
      }
    ];
    this.setData({ categories: categories_data });
  },
   //事件处理函数
  tapItem: function (e) {
    console.log('index接收到的itemid: ' + e.detail.itemid);
  },
  edit: function (e) {
    console.log('edit接受的数据: itemid:' + e.detail.itemid)
    wx.navigateTo({
      url: 'categoryEdit/categoryEdit?itemid='+e.detail.itemid,
    })
  },
  deleteitem: function (e) {
    wx.showModal({
      title: '你确定吗?',
      content: '确定删除此分类吗?',
      success(res) {
        if (res.confirm) {
          console.log('用户点击确定id:' + e.detail.itemid)
          // todo 进行删除请求操作
          // 删除过程loading完成后刷新页面数据,错误也给出错误数据但是不用刷新categories了
        } else if (res.cancel) {
          console.log('用户点击取消')
        }
      }
    })
  },
  toggle: function (e) {
    console.log('点击父组件接受的数据: itemid:' + e.detail.itemid+',expand:'+e.detail.isexpand);
    var itemid = e.detail.itemid;
    var isexpand = e.detail.isexpand;
    console.log(this.data.categories)

    var hha = []; // 用于当收起一个节点的时候下面的子节点也跟着收起来,因为变量拼接实在是没法用,只能重新更改数据再通过setdata赋值过去啦 Array.prototype.multiIndexOf = function (value) { var result; this.some(function iter(path) { //path指的就是当前元素?好像不是的说 return function (a, i) { // a是输入值,i是索引 if (a.id === value) { result = path.concat(i); hha = a; return true; }; return Array.isArray(a.children) && a.children.some(iter(path.concat(i))); } }([])); return result; } var indexx = this.data.categories.multiIndexOf(itemid) var indexresult = ""; let dd = JSON.stringify(indexx) dd = dd.substring(1, dd.length - 1); var indexarr = dd.split(","); indexarr.forEach(function (element, index, arr1) { if(index == 0) { indexresult += [${element}] } else { indexresult += .children[${element}] } }) /** start: 查找和传入元素id一致的节点 */ // 更新对应节点的expand this.setData({ [categories${indexresult}.expand]: !isexpand }) if(isexpand == true) {// 也就是目前执行是缩起操作,则其children中的所有expand也都要为false进行缩起,不能父级缩起了再点展开结果下级已经展开了这个逻辑不对的 const recursion = (obj) => { const o = obj; o.expand = false; if (o.children) { // check if has children o.children.forEach(v => { // if has children do the same recursion for every children recursion(v); }); } return o; // return final new object } recursion(hha) this.setData({ [categories${indexresult}]: hha }) } }, })
 
附上一个费劲的递归,根据itemid找到树状结构的位置信息
function getCategoryInfoByItemId(categories, itemid) {
  Array.prototype.multiIndexOf = function (value) {
    var result;
    this.some(function iter(path) { //path指的就是当前元素?好像不是的说
      console.log('path------:' + JSON.stringify(path))
      return function (a, i) { // a是输入值,i是索引
        if (a.id === value) {
          console.log('bb path' + JSON.stringify(path))
          result = result = path.concat({ 'id': a.id, 'name': a.name, 'index': i });;
          return true;
        };
        return Array.isArray(a.children) && a.children.some(iter(path.concat({ 'id': a.id, 'name': a.name, 'index': i })));
      }
    }([]));
    return result;
  }

  var resultdetailinfo = categories.multiIndexOf(itemid)
  console.log('result data is:' + JSON.stringify(resultdetailinfo))
  return resultdetailinfo;
}

返回值差不多上这样:
result data is:[{"id":"111","name":"衣服","index":1},{"id":"1112","name":"冬装","index":0},{"id":"1114","name":"羽绒服","index":1}]

0