likes
comments
collection
share

微信小程序树形选择器的使用

作者站长头像
站长
· 阅读数 3

找了一圈,有一些能用的但是不是很完善,所以打算自己改完后记录一下。如果有需要的可以直接复制使用。

支持多选的树形选择

微信小程序树形选择器的使用

微信小程序树形选择器的使用

props属性

名称说明类型默认值
dataTree传入的树型列表Array[]
checkrule初始选中的列表Array[]
isOpenAll是否展开全部节点Booleanfalse
checkStrictly在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 falseBooleanfalse
props属性配置项Object{label: 'name',children: 'children',value: 'id'}

checkrule传入的key,即props中的value的字段的值,如['1','2']

props配置显示名称,默认label为name,子项为children,绑定的值为id,根据自己需要传入修改

事件

名称说明
select选项改变后触发,返回两个对象,一个为item,当前点击项,一个为idList,所有选中的数组列表数据
clickItem点击子项触发,返回当前的item

ref

通过获取组件实例可以使用setSelectKey方法,用于动态设置选中的值,举例,下面的操作会将id为1和2的子项设置为选中状态

const component = this.selectComponent(`#tree-id`)
component.setSelectKey(['1','2'])

使用举例

<tree-select-mut
  isOpenAll
  dataTree="{{dataTree}}"
></tree-select-mut>

代码

tree.wxml

<view wx:for="{{tree}}" wx:key="index" style="margin-left: {{treeListIndex*10+20}}rpx">
  <!-- 一级菜单 -->
  <view class="tree-item">
    <view wx:if="{{item[props.children] && item[props.children].length > 0}}" bindtap="isOpen" data-index="{{index}}">
      <image src="{{arrowUrl}}" class="tree-arrow {{item.open ? 'tree-expand' : ''}}" />
    </view>
    <view class="tree-item-no-children" wx:else> </view>
    <view class="tree-item-name-warp" bindtap="select" data-item="{{item}}" data-index="{{index}}">
      <image wx:if="{{item.checked === 1 }}" src="{{choiceUrl}}" class="tree-check-box"></image>
      <image wx:if="{{item.checked === 0 }}" src="{{unchoiceUrl}}" class="tree-check-box"></image>
      <image wx:if="{{item.checked === -1 }}" src="{{unfullChoiceUrl}}" class="tree-check-box"></image>
      <view class="tree-item-name {{item.checked === 1 ? 'tree-item-name-select' : '' }}">{{item[props.label]}}</view>
    </view>
  </view>
  <!-- 二级菜单 -->
  <tree
    wx:if="{{item[props.children]&& item[props.children].length > 0 && item.open }}"
    data-parent="{{item}}"
    dataTree="{{ item[props.children] }}"
    props="{{props}}"
    isOpenAll="{{isOpenAll}}"
    checkStrictly="{{checkStrictly}}"
    treeListIndex="{{treeListIndex+1}}"
    catch:select="handleSelect"
  />
</view>

tree.js

Component({
  properties: {
    dataTree: {
      type: Array,
      value: []
    },
    checkrule: {
      type: Array,
      value: []
    },
    props: {
      type: Object,
      value: {
        label: 'name',
        children: 'children',
        value: 'id'
      }
    },
    treeListIndex: {
      // 当期树形列表的索引
      type: Number,
      value: 1
    },
    isOpenAll: {
      // 是否展开全部节点
      type: Boolean,
      value: false
    },
    checkStrictly: {
      // 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 false
      type: Boolean,
      value: false
    }
  },
  observers: {
    dataTree: function (params) {
      var arr = []
      if (this.properties.checkrule.length > 0) {
        this.setData({
          allChoiceIdList: this.properties.checkrule
        })
        arr = this.showcheck(params)
      } else {
        arr = params
      }
      this.setData({
        tree: this._initSourceData(arr)
      })
    }
  },
  data: {
    choiceUrl:
      'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAA0CAMAAADypuvZAAAAUVBMVEUAAAAAe/8AfP8Ae/8Aev8Aev8Ae/8AfP8Aev8Aev8AfP8Ae/8AfP8AfP8Ae/8Ae/8Ae/8AfP8Aev8Aev8Ae/8AfP8Aev8Ae/8Akv8Aev8Aev+kEjuvAAAAGnRSTlMA82X4/Mi9Pe92cUxEN4R9XzIZqWhakGwDkkXSKaQAAADMSURBVEjH7dbLDoIwEEbhmQoq4hXEy7z/g1oMCZIjhXZn5Oy/ZFaTX3wuV5uZ5k7aqrVFta488iZSiTiLzkkej3LReKRiCS1oQVlTR6OslEcdiValyKeSmcZ3Bpo2TfYdZZOG6LnR8dvehugmQrXa03jUG99BQ4ZoJ52icWZAQ7UNGCAqpSGyYqB0I21XMyKqy5aGiIqGqO/Uqc7cLYCgYICgunYWQFAwQOgIAzSmCgsjKhoiquKnnuUfII03mjRzkgZVynRLG4lJc/QFPGf4PGUpSLcAAAAASUVORK5CYII=',
    unchoiceUrl:
      'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAA0BAMAAAA3VgbYAAAAG1BMVEUAAACampqRkZGRkZGQkJCQkJCSkpKSkpKQkJDSjQFsAAAACHRSTlMAF/PjyL1lWZ2u+0EAAABaSURBVDjLY2Bgc+rAAlQSGBgYIzqwglYBBtbmQgYsQNwigCHDkAErEG5j8CjALsXewqAhgF2KsYmhgwEH6BiVGpUalRqVwiqFp0jBUxDhKb7wFHp4iko8BSwAp9JkJXcYpZEAAAAASUVORK5CYII=',
    unfullChoiceUrl:
      'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAA0BAMAAAA3VgbYAAAAFVBMVEUAAAAAe/8Aev8Ae/8AfP8Aev8Aev8LwEpAAAAABnRSTlMA88i9ZRljrLGUAAAAVElEQVQ4y2NgYFFMwwKEHBgYWM3SsILkAAbmNBzAgMENl1QKgxouqSQGMVxSiQxpOMGo1HCTYkABVJcajF4elaKNFJ4iBU9BhKf4wlPo4Skq8RSwALWN/JpuKoEOAAAAAElFTkSuQmCC',
    arrowUrl:
      'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSIjYzhjOGM4IiBkPSJNOCA2YTEgMSAwIDAgMSAxLjYtLjhsOCA2YTEgMSAwIDAgMSAwIDEuNmwtOCA2QTEgMSAwIDAgMSA4IDE4VjZaIi8+PC9zdmc+',
    tree: [],
    allChoiceIdList: [] // 所有选中的id数组
  },
  methods: {
    setSelectKey(checkrule) {
      let arr = []
      this.setData({
        allChoiceIdList: checkrule
      })
      arr = this.showcheck(this.properties.dataTree)

      this.setData({
        tree: this._initSourceData(arr)
      })
    },
    isOpen(e) {
      const open = 'tree[' + e.currentTarget.dataset.index + '].open'
      this.setData({
        [open]: !this.data.tree[e.currentTarget.dataset.index].open
      })
    },
    _initSourceData(nodes) {
      const { children } = this.properties.props
      nodes.forEach((element) => {
        if (element.checked === undefined) element.checked = 0
        element.open = this.properties.isOpenAll // 是否展开
        if (element[children] && element[children].length > 0)
          element[children] = this._initSourceData(element[children])
      })
      return nodes
    },
    // 选择
    select(e) {
      let item = e.currentTarget.dataset.item
      item = this._handleClickItem(item)
      this.data.tree = this._updateTree(this.data.tree, item)
      this.setData({
        tree: this.data.tree
      })
      this.data.allChoiceIdList = this.getAllChoiceId(this.data.tree)
      this.triggerEvent('select', { item: item, idList: this.data.allChoiceIdList }, { bubbles: true, composed: true })
      this.triggerEvent('clickItem', { item: item }, { bubbles: true, composed: true })
    },
    // 选择冒泡事件
    handleSelect(e) {
      let parent = e.currentTarget.dataset.parent
      let currentTap = e.detail.item
      // 修正它的父节点
      parent.children = this._updateTree(parent.children, currentTap)
      if (!this.properties.checkStrictly) {
        const { half, all, none } = this.getChildState(parent.children)
        if (half) parent.checked = -1
        if (all) parent.checked = 1
        if (none) parent.checked = 0
      }

      // 修正整个tree
      this.data.tree = this._updateTree(this.data.tree, parent)
      this.setData({
        tree: this.data.tree
      })
      this.data.allChoiceIdList = this.getAllChoiceId(this.data.tree)
      this.triggerEvent(
        'select',
        { item: parent, idList: this.data.allChoiceIdList },
        { bubbles: true, composed: true }
      )
    },
    /**
     * @method 处理点击选择
     * @param {Object} node 节点对象
     * @returns {Object} node 处理完毕的节点
     * @description 有子节点则全选中或全取消,当前为最底层单节点则选中或单取消
     */
    _handleClickItem(node) {
      const { children } = this.properties.props
      switch (node.checked) {
        case 0:
          node.checked = 1
          if (!this.properties.checkStrictly) {
            if (node[children] && node[children].length > 0) node[children] = this._allChoice(node[children])
          }
          break
        case 1:
          node.checked = 0
          if (!this.properties.checkStrictly) {
            if (node[children] && node[children].length > 0) node[children] = this._allCancel(node[children])
          }
          break
        default:
          node.checked = 1
          if (!this.properties.checkStrictly) {
            if (node[children] && node[children].length > 0) node[children] = this._allChoice(node[children])
          }
          break
      }
      return node
    },
    /**
     * @method 全选
     * @param {Array} nodes 节点数组
     * @returns {Array} nodes 处理完毕的节点数组
     */
    _allChoice(nodes) {
      const { children } = this.properties.props
      if (nodes.length <= 0) return
      for (let i = 0; i < nodes.length; i++) {
        nodes[i].checked = 1
        if (nodes[i][children] && nodes[i][children].length > 0)
          nodes[i][children] = this._allChoice(nodes[i][children])
      }
      return nodes
    },
    /**
     * @method 全取消
     * @param {Array} nodes 节点数组
     * @returns {Array} nodes 处理完毕的节点数组
     */
    _allCancel(nodes) {
      const { children } = this.properties.props
      if (nodes.length <= 0) return
      for (let i = 0; i < nodes.length; i++) {
        nodes[i].checked = 0
        if (nodes[i][children] && nodes[i][children].length > 0)
          nodes[i][children] = this._allCancel(nodes[i][children])
      }
      return nodes
    },
    /**
     * @method 更新tree
     * @param {Array} tree 节点树
     * @param {Object} newItem 需要替换新节点
     * @description 找到tree中目标进行替换
     */
    _updateTree(tree, newItem) {
      const { children, value } = this.properties.props
      if (!tree || tree.length <= 0) return
      for (let i = 0; i < tree.length; i++) {
        if (tree[i][value] === newItem[value]) {
          tree[i] = newItem
          break
        } else {
          if (tree[i][children] && tree[i][children].length > 0) {
            tree[i][children] = this._updateTree(tree[i][children], newItem)
          }
        }
      }
      return tree
    },
    /**
     * @method 获取子节点的状态
     * @param {Array} node 节点数组
     */
    getChildState(node) {
      let all = true
      let none = true
      for (let i = 0, j = node.length; i < j; i++) {
        const n = node[i]
        if (n.checked === 1 || n.checked === -1) {
          none = none && false
        }
        if (n.checked === 0 || n.checked === -1) {
          all = all && false
        }
      }
      return { all, none, half: !all && !none }
    },
    // 获取所有选中的节点id
    getAllChoiceId(nodes, res = []) {
      const { children, value, label } = this.properties.props
      for (let i = 0; i < nodes.length; i++) {
        if (nodes[i].checked === 1)
          res.push({
            id: nodes[i][value],
            label: nodes[i][label]
          })
        if (nodes[i][children] && nodes[i][children].length > 0) this.getAllChoiceId(nodes[i][children], res)
      }
      return res
    },

    //回显选中的
    showcheck(nodes) {
      const { children, value } = this.properties.props
      for (let i = 0; i < nodes.length; i++) {
        if (this.data.allChoiceIdList.indexOf(nodes[i][value]) > -1) {
          nodes[i].checked = 1
        } else {
          nodes[i].checked = 0
        }
        if (nodes[i][children] && nodes[i][children].length > 0) this.showcheck(nodes[i][children])
      }
      return nodes
    }
  }
})


tree.json

{
    "component": true,
    "usingComponents": {
        "tree": "/components/TreeSelectMut/tree/tree"
    }
}

tree.scss

.tree-item {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  padding: 6rpx 0;

  .tree-arrow {
    width: 36rpx;
    height: 36rpx;
    transition: all 0.3s;
  }

  .tree-expand {
    transform: rotate(90deg);
  }

  .tree-item-no-children {
    width: 20px;
    display: flex;
    flex-shrink: 0;
    justify-content: center;
    align-items: center;
  }

  .tree-item-name-warp {
    display: flex;
    align-items: center;
    flex: 1 0;
    min-width: 0;
    padding-right: 10px;

    .tree-item-name {
      margin-left: 24rpx;
      color: #606266;
      font-size: 32rpx;
      flex: 1 0;
      padding: 10rpx 0 10rpx 5rpx;
      min-width: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    .tree-check-box {
      height: 32rpx;
      width: 32rpx;
      margin-left: 30rpx;
      flex-shrink: 0;
    }

    .tree-item-name-select {
      color: #0079fe;
    }
  }
}

单选的树形选择

微信小程序树形选择器的使用

微信小程序树形选择器的使用

props属性

名称说明类型默认值
list传入的树型列表Array[]
isOpenAll是否展开全部节点Booleanfalse
props属性配置项Object{label: 'name',children: 'children',value: 'id',disabled: 'disabled'}
selectKey选中的节点idString''

props默认数组结构的字段为显示的名称为name,子项为children,绑定的值为id,disabled禁用点击,根据自己需要传入修改

事件

名称说明
select选项改变后触发,返回当前选择的item

使用举例

<tree-select
  isOpenAll
  bind:select="itemClick"
  list="{{options}}"
  selectKey="{{currentNode.id}}"
></tree-select>
{
    data:{
        options: [],
        currentNode: {}
    }
}

代码

tree.wxml

<view wx:for="{{tree}}" wx:key="index" class="tree">
  <view class="tree-item">
    <view
      class="tree-item-onOff"
      wx:if="{{item[props.children] && item[props.children].length > 0}}"
      bindtap="isOpen"
      data-index="{{index}}"
    >
      <view class="arrow-icon {{item.open ? 'tree-item-onOff-open' : ''}}">
        <t-icon name="play" color="#c4c1ce" size="22"></t-icon>
      </view>
    </view>
    <view class="tree-item-onOff" wx:else> </view>
    <view
      class="tree-item-name-warp {{selectKey === item[props.value] ? 'tree-item-name-select' : '' }}"
      bindtap="select"
      data-item="{{item}}"
      data-index="{{index}}"
    >
      <view class="tree-item-name {{item[props.disabled] ?'disabled-color':''}}">{{item[props.label]}} </view>
    </view>
  </view>
  <x-tree
    wx:if="{{item[props.children] && item[props.children].length > 0 && item.open }}"
    list="{{ item[props.children] }}"
    selectKey="{{selectKey}}"
    isOpenAll="{{isOpenAll}}"
    props="{{props}}"
  >
  </x-tree>
</view>


tree.js

Component({
  properties: {
    list: {
      type: Array,
      value: []
    },
    selectKey: {
      // 选中的节点id
      type: [String, Number],
      value: ''
    },
    isOpenAll: {
      //是否展开全部节点
      type: Boolean,
      value: false
    },
    props: {
      type: Object,
      value: {
        label: 'name',
        children: 'children',
        value: 'id',
        disabled: 'disabled'
      }
    }
  },
  observers: {
    list: function (params) {
      params.forEach((v) => {
        v.open = this.properties.isOpenAll // 是否展开
      })
      this.setData({
        tree: params
      })
    }
  },
  data: {
    tree: []
  },
  options: {
    styleIsolation: 'apply-shared'
  },
  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 (item[this.properties.props.disabled]) {
        this.isOpen(e)
        return
      }
      this.triggerEvent('select', item, { bubbles: true, composed: true })
    }
  }
})

tree.json

{
    "component": true,
    "usingComponents": {
        "x-tree": "/components/tree/tree"
    }
}

tree.scss

.tree {
  text-align: left;
  padding-left: 15px;
  font-size: 32rpx;

  .tree-item {
    display: flex;

    .tree-item-onOff {
      width: 30px;
      display: flex;
      justify-content: center;
      align-items: center;
    }

    .arrow-icon {
      display: flex;
      align-items: center;
      transition: all 0.3s;
    }

    .tree-item-onOff-open {
      transform: rotate(90deg);
    }

    .tree-item-name-warp {
      width: calc(100% - 40px);
      display: flex;
      padding: 10rpx 0 10rpx 5rpx;
      color: #606266;

      .tree-item-name {
        width: calc(100% - 50px);
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        display: flex;
        align-items: center;
        gap: 10rpx;
      }

      .disabled-color {
        color: #a8abb2;
      }
    }

    .tree-item-name-select {
      background: #ecf7fa;
      color: #0079fe;
      font-weight: 700;
      border-radius: 8rpx;
    }
  }
}

转载自:https://juejin.cn/post/7307105122962063394
评论
请登录