Uniapp微信开发树形选择组件

发布于:2025-03-29 ⋅ 阅读:(25) ⋅ 点赞:(0)

1.本组件依据ba-tree-picker树形层级选择器(支持单选、多选、父级选择、映射) - DCloud 插件市场
原组件进行修改,修复了样式,新增了默认展开全部树形和数据回显的功能,详细功能可能需要改造原组件
2.组件代码

<!-- 树形层级选择器-->
<!-- 1、支持单选、多选 -->
<template>
  <view class="tree-dialog show">
    <view class="tree-view">
      <scroll-view class="tree-list" :scroll-y="true">
        <block v-for="(item, index) in treeList" :key="index">
          <view
            class="tree-item"
            :style="[
              {
                paddingLeft: item.level * 30 + 'rpx',
              },
            ]"
            :class="{
              itemBorder: border === true,
              show: item.isShow,
            }"
          >
            <view class="item-label">
              <view
                class="item-icon uni-inline-item"
                @tap.stop="_onItemSwitch(item, index)"
              >
                <view
                  v-if="!item.isLastLevel && item.isShowChild"
                  class="switch-on"
                  :style="{ 'border-left-color': switchColor }"
                >
                </view>
                <view
                  v-else-if="!item.isLastLevel && !item.isShowChild"
                  class="switch-off"
                  :style="{ 'border-top-color': switchColor }"
                >
                </view>
                <view
                  v-else
                  class="item-last-dot"
                  :style="{ 'border-top-color': switchColor }"
                >
                </view>
              </view>
              <view
                class="uni-flex-item uni-inline-item"
                @tap.stop="_onItemSelect(item, index)"
              >
                <view class="item-name">
                  <div class="name">
                    {{
                      item.name +
                      (item.childCount ? "(" + item.childCount + ")" : "")
                    }}
                  </div>
                  <div class="name" v-if="item.childCount == 0">
                    {{ item.phone }}
                  </div>
                </view>
                <view
                  class="item-check"
                  v-if="selectParent ? true : item.isLastLevel"
                >
                  <view
                    class="item-check-yes"
                    v-if="item.checkStatus == 1"
                    :class="{ radio: !multiple }"
                    :style="{ 'border-color': confirmColor }"
                  >
                    <view
                      class="item-check-yes-part"
                      :style="{ 'background-color': confirmColor }"
                    >
                    </view>
                  </view>
                  <view
                    class="item-check-yes"
                    v-else-if="item.checkStatus == 2"
                    :class="{ radio: !multiple }"
                    :style="{ 'border-color': confirmColor }"
                  >
                    <view
                      class="item-check-yes-all"
                      :style="{ 'background-color': confirmColor }"
                    >
                    </view>
                  </view>
                  <view
                    class="item-check-no"
                    v-else
                    :class="{ radio: !multiple }"
                    :style="{ 'border-color': confirmColor }"
                  ></view>
                </view>
              </view>
            </view>
          </view>
        </block>
      </scroll-view>
    </view>

    <view class="tree-bar">
      <view
        class="tree-bar-cancel"
        :style="{ color: cancelColor }"
        hover-class="hover-c"
        @tap="_cancel"
        >重置
      </view>
      <view
        class="tree-bar-confirm"
        :style="{ color: confirmColor }"
        hover-class="hover-c"
        @tap="_confirm"
      >
        确定
      </view>
    </view>
  </view>
</template>

<script>
export default {
  emits: ["select-change"],
  name: "ba-tree-picker",
  props: {
    valueKey: {
      type: String,
      default: "id",
    },
    textKey: {
      type: String,
      default: "name",
    },
    childrenKey: {
      type: String,
      default: "children",
    },
    localdata: {
      type: Array,
      default: function () {
        return [];
      },
    },
    localTreeList: {
      //在已经格式化好的数据
      type: Array,
      default: function () {
        return [];
      },
    },
    selectedData: {
      type: Array,
      default: function () {
        return [];
      },
    },
    title: {
      type: String,
      default: "",
    },
    multiple: {
      // 是否可以多选
      type: Boolean,
      default: true,
    },
    selectParent: {
      //是否可以选父级
      type: Boolean,
      default: true,
    },
    confirmColor: {
      // 确定按钮颜色
      type: String,
      default: "", // #0055ff
    },
    cancelColor: {
      // 取消按钮颜色
      type: String,
      default: "", // #757575
    },
    titleColor: {
      // 标题颜色
      type: String,
      default: "", //
    },
    switchColor: {
      // 节点切换图标颜色
      type: String,
      default: "", // #666
    },
    border: {
      // 是否有分割线
      type: Boolean,
      default: false,
    },
    Users: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {
      showDialog: true,
      treeList: [],
    };
  },
  computed: {},
  methods: {
    _show() {
      this.showDialog = true;
    },
    _hide() {
      this.showDialog = false;
    },
    _cancel() {
      this._hide();
      this.$emit("cancel", "");
    },
    _confirm() {
      //多选
      let selectedList = []; //如果子集全部选中,只返回父级 id
      let selectedNames;
      let currentLevel = -1;
      this.treeList.forEach((item, index) => {
        if (currentLevel >= 0 && item.level > currentLevel) {
          console.log("itemid", item.id);
          selectedList.push(item.id);
        } else {
          if (item.checkStatus === 2) {
            if (item.level == 1) {
              selectedList.push(item.id);
            }
            currentLevel = item.level;
            selectedNames = selectedNames
              ? selectedNames + " / " + item.name
              : item.name;
          } else {
            currentLevel = -1;
          }
        }
      });
      //console.log('_confirm', selectedList);
      this._hide();
      this.$emit("select-change", selectedList, selectedNames);
    },
    //格式化原数据(原数据为tree结构)
    _formatTreeData(list = [], level = 0, parentItem, isShowChild = true) {
      let nextIndex = 0;
      let parentId = -1;
      let initCheckStatus = 0;
      if (parentItem) {
        nextIndex =
          this.treeList.findIndex((item) => item.id === parentItem.id) + 1;
        parentId = parentItem.id;
        if (!this.multiple) {
          //单选
          initCheckStatus = 0;
        } else initCheckStatus = parentItem.checkStatus == 2 ? 2 : 0;
      }
      list.forEach((item) => {
        let isLastLevel = true;
        if (item && item[this.childrenKey]) {
          let children = item[this.childrenKey];
          if (Array.isArray(children) && children.length > 0) {
            isLastLevel = false;
          }
        }

        let itemT = {
          id: item[this.valueKey],
          name: item[this.textKey],
          phone: item.phone,
          type: item.type,
          level,
          isLastLevel,
          isShow: isShowChild,
          isShowChild: false,
          checkStatus: initCheckStatus,
          orCheckStatus: 0,
          parentId,
          children: item[this.childrenKey],
          childCount: item[this.childrenKey]
            ? item[this.childrenKey].length
            : 0,
          childCheckCount: 0,
          childCheckPCount: 0,
        };

        if (this.selectedData.indexOf(itemT.id) >= 0) {
          itemT.checkStatus = 2;
          itemT.orCheckStatus = 2;
          itemT.childCheckCount = itemT.children ? itemT.children.length : 0;
          this._onItemParentSelect(itemT, nextIndex);
        }

        this.treeList.splice(nextIndex, 0, itemT);
        nextIndex++;
      });
      //console.log(this.treeList);
    },
    // 节点打开、关闭切换
    _onItemSwitch(item, index) {
      // console.log(item)
      //console.log('_itemSwitch')
      if (item.isLastLevel === true) {
        return;
      }
      item.isShowChild = !item.isShowChild;
      if (item.children) {
        this._formatTreeData(item.children, item.level + 1, item);
        item.children = undefined;
      } else {
        this._onItemChildSwitch(item, index);
      }
      console.log("thissssssssss", this.treeList);
    },
    //展开全部
    showAll() {
      console.log("333", this.treeList);
      let Tree = JSON.parse(JSON.stringify(this.treeList));
      Tree.forEach((item, index) => {
        console.log("item.children", item.children);
        if (item.children) {
          /*  console.log("children", item.name); */
          this._formatTreeData(item.children, item.level + 1, item);
        } else {
          this._onItemChildSwitch(item, index);
        }
      });
      console.log("Tree", Tree);

      let YuanList = this.treeList;
      /*  this.treeList.forEach((item) => {
        if (item.children) {
          YuanList.push({ ...item });
          YuanList.push(...item.children);
        } else {
          YuanList.push({ ...item });
        }
      }); */
      console.log("YuanList", YuanList);

      YuanList.forEach((item, index) => {
        let find = this.Users.find((user) => {
          return user == item.id;
        });
        if (find) {
          console.log("find", item, index);

          this._onItemSelect(item, index + 1);
        }
      });
    },
    _onItemChildSwitch(item, index) {
      //console.log('_onItemChildSwitch')
      const firstChildIndex = index + 1;
      if (firstChildIndex > 0)
        for (var i = firstChildIndex; i < this.treeList.length; i++) {
          let itemChild = this.treeList[i];
          if (itemChild.level > item.level) {
            if (item.isShowChild) {
              if (itemChild.parentId === item.id) {
                itemChild.isShow = item.isShowChild;
                if (!itemChild.isShow) {
                  itemChild.isShowChild = false;
                }
              }
            } else {
              itemChild.isShow = item.isShowChild;
              itemChild.isShowChild = false;
            }
          } else {
            return;
          }
        }
    },
    // 节点选中、取消选中
    _onItemSelect(item, index) {
      console.log("_onItemSelect", item, index);

      //console.log('_onItemSelect')
      //console.log(item)
      if (!this.multiple) {
        //单选
        item.checkStatus = item.checkStatus == 0 ? 2 : 0;

        this.treeList.forEach((v, i) => {
          if (i != index) {
            this.treeList[i].checkStatus = 0;
          } else {
            this.treeList[i].checkStatus = 2;
          }
        });

        let selectedList = [];
        let selectedNames;
        selectedList.push(item.id);
        selectedNames = item.name;
        this._hide();
        this.$emit("select-change", selectedList, selectedNames);
        return;
      }

      let oldCheckStatus = item.checkStatus;
      switch (oldCheckStatus) {
        case 0:
          item.checkStatus = 2;
          item.childCheckCount = item.childCount;
          item.childCheckPCount = 0;
          break;
        case 1:
        case 2:
          item.checkStatus = 0;
          item.childCheckCount = 0;
          item.childCheckPCount = 0;
          break;
        default:
          break;
      }
      //子节点 全部选中
      this._onItemChildSelect(item, index);
      //父节点 选中状态变化
      this._onItemParentSelect(item, index, oldCheckStatus);
    },
    _onItemChildSelect(item, index) {
      //console.log('_onItemChildSelect')
      let allChildCount = 0;
      if (item.childCount && item.childCount > 0) {
        index++;
        while (
          index < this.treeList.length &&
          this.treeList[index].level > item.level
        ) {
          let itemChild = this.treeList[index];
          itemChild.checkStatus = item.checkStatus;
          if (itemChild.checkStatus == 2) {
            itemChild.childCheckCount = itemChild.childCount;
            itemChild.childCheckPCount = 0;
          } else if (itemChild.checkStatus == 0) {
            itemChild.childCheckCount = 0;
            itemChild.childCheckPCount = 0;
          }
          // console.log('>>>>index:', index, 'item:', itemChild.name, '  status:', itemChild
          // 	.checkStatus)
          index++;
        }
      }
    },
    _onItemParentSelect(item, index, oldCheckStatus) {
      //console.log('_onItemParentSelect')
      //console.log(item)
      const parentIndex = this.treeList.findIndex(
        (itemP) => itemP.id == item.parentId
      );
      //console.log('parentIndex:' + parentIndex)
      if (parentIndex >= 0) {
        let itemParent = this.treeList[parentIndex];
        let count = itemParent.childCheckCount;
        let oldCheckStatusParent = itemParent.checkStatus;

        if (oldCheckStatus == 1) {
          itemParent.childCheckPCount -= 1;
        } else if (oldCheckStatus == 2) {
          itemParent.childCheckCount -= 1;
        }
        if (item.checkStatus == 1) {
          itemParent.childCheckPCount += 1;
        } else if (item.checkStatus == 2) {
          itemParent.childCheckCount += 1;
        }

        if (
          itemParent.childCheckCount <= 0 &&
          itemParent.childCheckPCount <= 0
        ) {
          itemParent.childCheckCount = 0;
          itemParent.childCheckPCount = 0;
          itemParent.checkStatus = 0;
        } else if (itemParent.childCheckCount >= itemParent.childCount) {
          itemParent.childCheckCount = itemParent.childCount;
          itemParent.childCheckPCount = 0;
          itemParent.checkStatus = 2;
        } else {
          itemParent.checkStatus = 1;
        }
        //console.log('itemParent:', itemParent)
        this._onItemParentSelect(itemParent, parentIndex, oldCheckStatusParent);
      }
    },
    // 重置数据
    _reTreeList() {
      this.treeList.forEach((v, i) => {
        this.treeList[i].checkStatus = v.orCheckStatus;
      });
    },
    _initTree() {
      this.treeList = [];
      this._formatTreeData(this.localdata);
    },
  },
  watch: {
    localdata() {
      this._initTree();
    },
    localTreeList() {
      this.treeList = this.localTreeList;
    },
  },
  mounted() {
    this._initTree();
    setTimeout(() => {
      this.showAll();
    }, 300);
    console.log("treeList", this.treeList);
  },
};
</script>

<style scoped lang="scss">
.tree-dialog {
  height: 100%;
  background-color: #fff;
  border-top-left-radius: 10px;
  border-top-right-radius: 10px;
  /* #ifndef APP-NVUE */
  display: flex;
  /* #endif */
  flex-direction: column;
  z-index: 102;
  font-size: 28upx;
  top: 20%;
  transition: all 0.3s ease;
  transform: translateY(100%);
}

.tree-dialog.show {
  transform: translateY(0);
}

.tree-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  /* background-color: #fff; */
  height: 90rpx;
  padding-left: 25rpx;
  padding-right: 25rpx;
  display: flex;
  justify-content: space-between;
  align-items: center;
  box-sizing: border-box;
  border-bottom-width: 1rpx !important;
  border-bottom-style: solid;
  border-bottom-color: #f5f5f5;
  font-size: 32rpx;
  color: #757575;
  line-height: 1;
}

.tree-bar-confirm {
  display: flex;
  align-items: center;
  justify-content: center;
  color: #0055ff;
  background-color: $cc-theme-color;
  color: #fff;
  height: 80upx;
  border-radius: 40upx;
  line-height: 80upx;
  flex: 1;
  flex-shrink: 0;
}

.tree-bar-cancel {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 1;
  flex-shrink: 0;
  margin-right: 20upx;
  color: $cc-theme-color;
  border: 1px solid $cc-theme-color;
  height: 80upx;
  border-radius: 40upx;
  line-height: 80upx;
}

.tree-view {
  flex: 1;
  padding: 20rpx;
  /* #ifndef APP-NVUE */
  display: flex;
  /* #endif */
  flex-direction: column;
  overflow: hidden;
  height: 100%;
  margin-bottom: 100upx;
}

.tree-list {
  flex: 1;
  height: 100%;
  overflow: hidden;
}

.tree-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  line-height: 1;
  height: 0;
  opacity: 0;
  transition: 0.2s;
  overflow: hidden;
}

.tree-item.show {
  height: 90rpx;
  opacity: 1;
}

.tree-item.showchild:before {
  transform: rotate(90deg);
}

.tree-item.last:before {
  opacity: 0;
}

.switch-on {
  width: 0;
  height: 0;
  border-left: 10rpx solid transparent;
  border-right: 10rpx solid transparent;
  border-top: 15rpx solid #666;
}

.switch-off {
  width: 0;
  height: 0;
  border-bottom: 10rpx solid transparent;
  border-top: 10rpx solid transparent;
  border-left: 15rpx solid #666;
}

.item-last-dot {
  position: absolute;
  width: 10rpx;
  height: 10rpx;
  border-radius: 100%;
  background: #666;
}

.item-icon {
  width: 26rpx;
  height: 26rpx;
  padding-left: 20rpx;
}

.item-label {
  flex: 1;
  display: flex;
  align-items: center;
  height: 100%;
}
.uni-flex-item {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.item-name {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* .name {
    margin-right: 20upx;
  } */
}

.item-check {
  width: 40px;
  height: 40px;
  display: flex;
  justify-content: center;
  align-items: center;
}

.item-check-yes,
.item-check-no {
  width: 20px;
  height: 20px;
  border-top-left-radius: 20%;
  border-top-right-radius: 20%;
  border-bottom-right-radius: 20%;
  border-bottom-left-radius: 20%;
  border-top-width: 1rpx;
  border-left-width: 1rpx;
  border-bottom-width: 1rpx;
  border-right-width: 1rpx;
  border-style: solid;
  border-color: #0055ff;
  display: flex;
  justify-content: center;
  align-items: center;
  box-sizing: border-box;
}

.item-check-yes-part {
  width: 12px;
  height: 12px;
  border-top-left-radius: 20%;
  border-top-right-radius: 20%;
  border-bottom-right-radius: 20%;
  border-bottom-left-radius: 20%;
  background-color: #0055ff;
}

.item-check-yes-all {
  margin-bottom: 5px;
  border: 2px solid #007aff;
  border-left: 0;
  border-top: 0;
  height: 12px;
  width: 6px;
  transform-origin: center;
  /* #ifndef APP-NVUE */
  transition: all 0.3s;
  /* #endif */
  transform: rotate(45deg);
}

.item-check .radio {
  border-top-left-radius: 50%;
  border-top-right-radius: 50%;
  border-bottom-right-radius: 50%;
  border-bottom-left-radius: 50%;
}

.item-check .radio .item-check-yes-b {
  border-top-left-radius: 50%;
  border-top-right-radius: 50%;
  border-bottom-right-radius: 50%;
  border-bottom-left-radius: 50%;
}

.hover-c {
  opacity: 0.6;
}

.itemBorder {
  border-bottom: 1px solid #e5e5e5;
}
</style>

2.使用代码

 <baTreePicker
            ref="treePicker"
            :multiple="true"
            @select-change="selectChange"
            :localdata="listData"
            :Users="Users"
            valueKey="id"
            textKey="name"
            childrenKey="children"
          />

3.结构


网站公告

今日签到

点亮在社区的每一天
去签到