<template>
  <div class="el-virtual-tree">
    <div v-if="filterable">
      <el-input
        :placeholder="placeholder"
        prefix-icon="bi-iconfont-search"
        clearable
        v-model="filterValue"
        :validate-event="false"
        size="small"
      >
      </el-input>
    </div>
    <div
      ref="list"
      class="virtual-tree"
      :style="{ height: height + 'px' }"
      @scroll="scrollEvent($event)"
    >
      <div
        class="virtual-tree-phantom"
        :style="{ height: listHeight + 'px' }"
      ></div>
      <div v-if="visibleData.length === 0">
        <el-empty></el-empty>
      </div>
      <div
        v-else
        class="virtual-tree-list"
        :style="{ transform: getTransform }"
      >
        <div
          ref="items"
          class="virtual-tree-item"
          v-for="(item, index) in visibleData"
          :key="item.treeStructureId"
          @dblclick="emitData(item)"
          @click.stop="lineSelect(item, index)"
          :style="{
            height: itemSize + 'px',
            lineHeight: itemSize + 'px',
            paddingLeft: itemPadding(item.treeStructureId) + 'px',
          }"
        >
          <div
            v-if="isLowerLevel"
            @click.stop="toggleExpand(item, index)"
            class="tree-expand-wrapper"
          >
            <div v-if="!item.isLeaf">
              <!--  -->

              <div
                :class="{
                  'el-tree-node__expand-icon bi-iconfont-caret-right': true,
                  rotate90: item.expand,
                  clickAble: !item.expand,
                }"
                icon
                pointer
              ></div>
            </div>
            <div v-else class="no-expand"></div>
          </div>
          <div
            v-if="isCheckbox"
            @click.stop="toggleCheck(item, index)"
            :class="{
              'no-check': item.selType === 1 || !item.selType,
              check: item.selType === 2,
              'half-check': item.selType === 3,
            }"
          >
            <div class="check-graphical" v-if="item.selType === 2"></div>
            <div class="half-check-graphical" v-if="item.selType === 3"></div>
          </div>
          <span>
            <span
              v-if="item.loadNode"
              class="el-tree-node__loading-icon el-icon-loading"
            ></span>
            <span v-html="highlight(item.label)"></span
          ></span>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'ElVirtualTree',
  props: {
    placeholder: { type: String, default: '输入表名搜索' },
    // 列表高度
    height: {
      type: Number,
      default: 300
    },
    // 所有列表数据
    list: {
      type: Array,
      default: () => []
    },
    // 每项高度
    itemSize: {
      type: Number,
      default: 36
    },
    // id分隔符
    separator: {
      type: String,
      default: '-'
    },
    // 是否有checkbox
    isCheckbox: {
      type: Boolean,
      default: false
    },
    // 是否展示下级
    isLowerLevel: {
      type: Boolean,
      default: true
    },
    // 是否通过点击展开下层节点
    isClickSelectNode: {
      type: Boolean,
      default: true
    },
    // 异步加载
    loadMore: Function,
    filterable: {
      type: Boolean,
      default: true
    }
  },
  computed: {
    // 列表总高度
    listHeight() {
      return this.listData.length * this.itemSize;
    },
    // 可显示的列表项数
    visibleCount() {
      return Math.ceil(this.screenHeight / this.itemSize);
    },
    // 偏移量对应的style
    getTransform() {
      return `translate3d(0,${this.startOffset}px,0)`;
    },
    // listData: {
    //   get:function () {
    //     return this.list;
    //   },
    //   set: function (v) {
    //     return v;
    //   }
    // },
    // 获取真实显示列表数据
    visibleData() {
      return this.listData.slice(
        this.start,
        Math.min(this.end, this.listData.length)
      );
    }
  },
  watch: {
    filterValue(val) {
      const keysList = [];
      this.originNodeMap.forEach((item) => {
        keysList.push(item);
      });
      val === ''
        ? this.storageAllNode(keysList)
        : (this.listData = this.filterToTree(keysList));
    },
    list: {
      handler(newVal, oldVal) {
        if (
          newVal.length !== 0 &&
          typeof oldVal === 'object' &&
          oldVal.length === 0
        ) {
          newVal.forEach((item) => {
            this.originNodeMap.set(item.treeStructureId, item);
          });
          this.listData = [].concat(newVal);
        }
      },
      deep: true
    }
  },
  mounted() {
    this.start = 0;
    this.end = this.start + this.visibleCount;
  },
  data() {
    return {
      filterValue: '',
      // 可视区域高度
      screenHeight: document.body.clientHeight,
      // 偏移量
      startOffset: 0,
      // 起始索引
      start: 0,
      // 结束索引
      end: null,
      listData: [],
      originNodeMap: new Map(),
      currentNodeMap: {}
    };
  },
  methods: {
    // treeToList(list) {

    //   const result = [];
    //   list.forEach((item)=>{
    //     if (item.parentId) {
    //       result.forEach(it=>{
    //         if (it.id === item.parentId) {
    //           if (!('children' in it)) it.children = [];
    //           it.children.push(item);
    //         }
    //       });
    //     } else {
    //       result.push(item);
    //     }
    //   });
    // },

    // treeToList(list) {
    //   const arr = [];
    //   list.forEach(item=>{
    //     arr.push(item);
    //     if (item.children) {
    //       arr.concat(this.treeToList(item.children));
    //       delete item.children;
    //     }
    //   });
    //   console.log(arr);
    //   return arr;
    // },
    /**
     * @description: 转化为含有children的结构
     * @param {*} data
     * @return {*}
     */
    toTree(data) {
      let obj = {}; // 使用对象重新存储数据
      let res = []; // 存储最后结果

      data.forEach((item) => {
        // 遍历原始数据data，构造obj数据，键名为id，值为数据
        obj[item['id']] = item;
      });
      data.forEach((item) => {
        let parentList = obj[item['parentId']]; // 通过每条数据的 parentId 去obj中查询
        if (parentList) {
          // 根据 parentId 找到父节点，item是子节点，
          if (!parentList['children']) parentList['children'] = []; // 这里对parentList是对obj的浅拷贝 同步到obj
          parentList['children'].push(item); // 将子节点插入父节点的 children 字段中
        } else {
          res.push(item); // parentId 找不到对应值，说明是根结点，直接插到根数组中
        }
      });
      return res;
    },

    /**
     * @description: 根据children结构来排子序
     * @param {*} tree
     * @return {*}
     */
    toList(tree) {
      let list = [];
      function deepToList(data) {
        data.forEach((item) => {
          list.push(item);
          if (item.children) deepToList(item.children);
          delete item.children;
        });
      }
      deepToList(tree);
      return list;
    },
    filterToTree(list) {
      const str = `\S*${this.filterValue}\S*`;
      const reg = new RegExp(str, 'i'); // 不区分大小写

      // 获取所有的符合标准的节点
      const getAllNode = list.filter((item) => reg.test(item.label));

      // 只针对两层进行处理 TODO
      const getAllParent = list
        .filter((item) => {
          return getAllNode
            .map(({ parentId }) => parentId)
            .includes(item.treeStructureId);
        })
        .concat(getAllNode);

      return this.toList(this.toTree(getAllParent));
    },
    /**
     * @description: 文本高亮
     * @param {*} text
     * @return {*}
     */
    highlight(text) {
      let value = '';
      if (this.filterable) value = this.filterValue;
      const reg = new RegExp(value, 'gi');
      return text.replace(reg, function($1) {
        return `<span class="el-cascader-highlight-text">${$1}</span>`;
      });
    },
    emitData(item) {
      this.$emit('getData', item);
    },
    scrollEvent() {
      // 当前滚动位置
      let scrollTop = this.$refs.list.scrollTop;
      // 此时的开始索引
      this.start = Math.floor(scrollTop / this.itemSize);
      // 此时的结束索引
      this.end = this.start + this.visibleCount;
      // 此时的偏移量
      this.startOffset = scrollTop - (scrollTop % this.itemSize);
    },
    // 每一列padding
    itemPadding(id) {
      return (id.split(this.separator).length - 1) * 14;
    },
    // 展开某一行
    lineSelect(item, index) {
      if (this.isClickSelectNode) {
        this.toggleExpand(item, index);
      }
    },
    // 点击节点
    toggleExpand(item, index) {
      if (item.expand) {
        // 收纳节点
        this.storageNode(item, index);
      } else {
        // 展开节点
        this.unfoldNode(item, index);
      }
    },
    // 筛选后收纳指定节点【数组】
    storageAllNode(list) {
      // 设置所有节点expand为false
      list.forEach((childItem) => {
        this.changeAttributes(
          this.originNodeMap,
          childItem.treeStructureId,
          'expand',
          false
        );
      });
      this.listData = [].concat(list.filter((node) => !node.parentId));
    },
    // 收纳节点
    storageNode(item, index) {
      console.log(index);
      // 设置所有子节点expand为false
      const childList = this.listData.filter(
        (node) =>
          node.treeStructureId.startsWith(item.treeStructureId) &&
          node.treeStructureId[item.treeStructureId.length] === this.separator
      );
      childList.forEach((childItem) => {
        this.changeAttributes(
          this.originNodeMap,
          childItem.treeStructureId,
          'expand',
          false
        );
      });
      this.listData = [].concat(
        this.listData.filter(
          (node) =>
            node.treeStructureId === item.treeStructureId ||
            !node.treeStructureId.startsWith(item.treeStructureId) ||
            node.treeStructureId[item.treeStructureId.length] !== this.separator
        )
      );
      this.$set(item, 'expand', !item.expand && true);
    },
    // 展开节点
    unfoldNode(item, index) {
      // 如果该节点正在展开/或者属于叶子结点直接返回
      if (item.loadNode || item.isLeaf) return;

      if ('expanded' in this.originNodeMap.get(item.treeStructureId)) {
        const startIndex = this.start;
        const cacheArrStart = this.listData.slice(0, startIndex + index + 1);
        const cacheArrEnd = this.listData.slice(startIndex + index + 1);
        // 如果已经展开过子节点 获取所有有记录的节点
        const keysList = [];
        this.originNodeMap.forEach((item, key) => keysList.push(key));

        const childKeys = keysList.filter(
          (keyItem) =>
            keyItem.startsWith(item.treeStructureId) &&
            keyItem[item.treeStructureId.length] === this.separator &&
            keyItem.split(this.separator).length -
              item.treeStructureId.split(this.separator).length ===
              1
        );
        let childrenList = [];
        childKeys.forEach((childKeysItem) => {
          childrenList.push(this.originNodeMap.get(childKeysItem));
        });
        this.listData = []
          .concat(cacheArrStart)
          .concat(childrenList)
          .concat(cacheArrEnd);
        this.$set(item, 'expand', !item.expand && true);
      } else {
        // 记录是否已经展开过
        // 进入loading状态
        this.$set(item, 'loadNode', !item.loadNode && true);
        const itemData = item;
        this.loadMore(itemData)
          .then((nodeChildList) => {
            // 设置为此id已经展开
            this.changeAttributes(
              this.originNodeMap,
              itemData.treeStructureId,
              'expanded',
              true
            );
            nodeChildList.forEach((nodeChildItem) => {
              nodeChildItem.parentId = item.treeStructureId;
            });
            if (nodeChildList.length === 0) {
              // 是否叶子节点
              this.$set(itemData, 'isLeaf', true);
            } else {
              // 1、如果该节点为半勾选状态则子节点的勾选状态是根据返回的数据来 2、如果该节点为勾选状态则返回的子节点全为勾选状态  3、如果改节点为为勾选状态则子节点全部未勾选
              if (item.selType === 1 || !item.selType) {
                // 该节点未勾选-所有子节点均不勾选
                nodeChildList.forEach((nodeChildItem) => {
                  nodeChildItem.selType = 1;
                });
              } else if (item.selType === 2) {
                // 该节点勾选-所有子节点均勾选
                nodeChildList.forEach((nodeChildItem) => {
                  nodeChildItem.selType = 2;
                });
              }
              // 将新节点加入map
              nodeChildList.forEach((childrenItem) => {
                this.originNodeMap.set(
                  childrenItem.treeStructureId,
                  childrenItem
                );
              });
              console.log(this.originNodeMap, '123123');
              // 找到索引 如果该节点已经收起则不更新数据
              if (
                this.listData.filter(
                  (childrenItem) =>
                    childrenItem.treeStructureId === itemData.treeStructureId
                ).length !== 0
              ) {
                const currentIndex = this.listData
                  .map((idItem) => idItem.treeStructureId)
                  .indexOf(itemData.treeStructureId);
                const cacheArrStart = this.listData.slice(0, currentIndex + 1);
                const cacheArrEnd = this.listData.slice(currentIndex + 1);
                this.listData = []
                  .concat(cacheArrStart)
                  .concat(nodeChildList)
                  .concat(cacheArrEnd);
                itemData.expand = !itemData.expand && true;
              }
            }
          })
          .finally(() => {
            this.$set(itemData, 'loadNode', false);
            // this.$forceUpdate();
          });
      }
    },
    // 修改某个属性的值 map的值和list的item指向同一个堆所以只用更改一次
    changeAttributes(map, mapkey, key, value) {
      let data = map.get(mapkey);
      data[key] = value;
      // map.set(mapkey, data);
    },
    // 勾选 selType: 1未勾选 2勾选 3半勾选
    toggleCheck(item) {
      const setChechFn = (selType) => {
        this.$set(item, 'selType', selType);
        // 处理子节点勾选状态
        this.setChildToggleCheck(item, selType);
        // 处理父节点勾选状态
        this.setParentToggleCheck(item);
      };
      const actions = {
        1: () => {
          // 未勾选变为勾选
          setChechFn(2);
        },
        2: () => {
          // 勾选变为未勾选
          setChechFn(1);
        },
        3: () => {
          // 半勾选变为勾选
          setChechFn(2);
        }
      };
      const action = actions[item.selType] || actions['1'];
      action();
    },
    // 改变所有子节点的勾选状态
    setChildToggleCheck(item, selType) {
      // 获取所有子节点key
      const keysList = [...this.originNodeMap.keys()];
      const childKeys = keysList.filter(
        (keyItem) =>
          keyItem.startsWith(item.treeStructureId) &&
          keyItem[item.treeStructureId.length] === this.separator
      );
      childKeys.forEach((childKeysItem) => {
        this.changeAttributes(
          this.originNodeMap,
          childKeysItem,
          'selType',
          selType
        );
      });
    },
    // 设置父节点的勾选状态
    setParentToggleCheck(item) {
      let pid = item.treeStructureId;
      const number = pid.split(this.separator).length - 1;
      for (let i = 0; i < number; i++) {
        if (pid.lastIndexOf(this.separator) > -1);
        // 截取每次父节点的值
        pid = pid.slice(0, pid.lastIndexOf(this.separator));
        // 获取该节点下级子节点
        const keysList = [...this.originNodeMap.keys()];
        const childKeys = keysList.filter(
          (keyItem) =>
            keyItem.startsWith(pid) &&
            keyItem.split(this.separator).length -
              pid.split(this.separator).length ===
              1
        );
        let [checkNumber, halfCheckNumber] = [0, 0];
        childKeys.forEach((childKeysItem) => {
          if (this.originNodeMap.get(childKeysItem).selType === 2) {
            checkNumber = checkNumber + 1;
          } else if (this.originNodeMap.get(childKeysItem).selType === 3) {
            halfCheckNumber = halfCheckNumber + 1;
          }
        });
        if (childKeys.length === checkNumber) {
          // 全选
          this.changeAttributes(this.originNodeMap, pid, 'selType', 2);
        } else if (checkNumber === 0 && halfCheckNumber === 0) {
          // 全没选
          this.changeAttributes(this.originNodeMap, pid, 'selType', 1);
        } else {
          this.changeAttributes(this.originNodeMap, pid, 'selType', 3);
        }
      }
    },
    // 获取所有叶子结点选择状态
    getLeafStatus() {
      // 筛选叶子结点
      console.time();
      const keysList = [...this.originNodeMap.keys()];
      const list = new Set();
      keysList.forEach((key) => {
        if (this.originNodeMap.get(key).parentId) {
          list.add(this.originNodeMap.get(key).parentId);
        }
      });
      const parentIdList = Array.from(list);
      const leafKeyList = keysList
        .concat(parentIdList)
        .filter((v) => !parentIdList.includes(v));
      let [chechKeyList, halfCheckKeyList] = [[], []];
      leafKeyList.forEach((key) => {
        const obj = this.originNodeMap.get(key);
        if (obj.selType === 2) {
          chechKeyList.push(key);
        } else if (obj.selType === 3) {
          halfCheckKeyList.push(key);
        }
      });
      console.timeEnd();
      return {
        chechKeyList,
        halfCheckKeyList
      };
    }
  }
};
</script>
