el-tree树状穿梭框+条件筛选

475 阅读1分钟

实现效果:一级和二级树状穿梭,可以筛选,展示列表已选数量,总共数量,选中数据保存后递归遍历原始数组,返回已选中数据的id和label

Snipaste_2023-08-12_20-48-13.png

微信截图_20230807214622.png

子组件

<template>
  <div class="tree-transfer">
    <div class="transfer-panel unselect">
      <div class="transfer-panel__header">
        <el-checkbox
          v-model="leftCheckAll"
          @change="clickLeftCheckAll"
          :disabled="leftAlltNum === 0"
          >待选</el-checkbox
        >
        <div>{{ leftSelectNum }}/{{ leftAlltNum }}</div>
      </div>
      <div class="transfer-panel__content">
        <el-input
          size="mini"
          placeholder="输入关键字进行搜索"
          v-model="leftFilterText"
        >
        </el-input>
        <el-tree
          filter
          ref="tree"
          :data="leftNodeData"
          show-checkbox
          node-key="id"
          default-expand-all
          :filter-node-method="filterNode"
          @check="nodeClick"
          :props="defaultProps"
        >
        </el-tree>
      </div>
    </div>
    <div class="button">
      <el-button
        type="primary"
        size="mini"
        :disabled="rightSelectNum === 0"
        icon="el-icon-arrow-left"
        @click="removeToLeft"
      >
      </el-button>
      <el-button
        type="primary"
        :disabled="leftSelectNum === 0"
        icon="el-icon-arrow-right"
        size="mini"
        @click="removeToRight"
      >
      </el-button>
    </div>
    <div class="transfer-panel selected">
      <div class="transfer-panel__header">
        <el-checkbox
          ref="selectRadio"
          v-model="rightCheckAll"
          @change="clickRightCheckAll"
          :disabled="rightAlltNum === 0"
          >已选</el-checkbox
        >
        <div>{{ rightSelectNum }}/{{ rightAlltNum }}</div>
      </div>
      <div class="transfer-panel__content">
        <el-input
          size="mini"
          placeholder="输入关键字进行搜索"
          v-model="rightFilterText"
        >
        </el-input>
        <el-tree
          ref="selectedTree"
          :data="rightNodeData"
          show-checkbox
          default-expand-all
          node-key="id"
          :filter-node-method="filterNode"
          @check="selectedClick"
          :props="defaultProps"
        >
        </el-tree>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    defaultProps: {
      type: Object,
      default: () => {
        return {
          children: "children",
          label: "name",
        };
      },
    },
    treeData: {
      type: Array,
      default: () => {
        return [];
      },
    },
    defaultKeys: {
      type: Array,
      default: () => {
        return [];
      },
    },
  },
  watch: {
    leftFilterText(val) {
      this.$refs.tree.filter(val);
    },
    rightFilterText(val) {
      this.$refs.selectedTree.filter(val);
    },
    rightKeys(val) {
      this.$emit("changeKeys", val);
    },
    leftNodeData(val) {
      this.leftAlltNum = this.getAllIds(val).length;
    },
    rightNodeData(val) {
      this.rightAlltNum = this.getAllIds(val).length;
    },
  },
  data() {
    return {
      leftCheckAll: false,
      rightCheckAll: false,
      nodeData: this.treeData,
      leftNodeData: [],
      rightNodeData: [],
      rightKeys: this.defaultKeys,
      rightFilterText: "",
      leftFilterText: "",
      leftSelectNum: 0,
      rightSelectNum: 0,
      leftAlltNum: 0,
      rightAlltNum: 0,
    };
  },
  mounted() {
    this.leftNodeData = this.nodeData;
    if (this.rightKeys.length > 0) {
      this.rightNodeData = this.rightKeys;
    }
  },
  methods: {
    getAllIds(arr) {
      let ids = [];
      arr.forEach((item) => {
        ids.push({ id: item.id, label: item.label });
        if (item.children) {
          ids = ids.concat(this.getAllIds(item.children));
        }
      });
      return ids;
    },
    clickLeftCheckAll(v) {
      //左侧全选
      if (v) {
        let keys = this.getChildNodeKeys(
          this.leftNodeData,
          this.leftFilterText
        );
        // 左侧已选
        this.leftSelectNum = keys.length ? keys.length : 0;
        this.$refs.tree.setCheckedKeys(keys);
      } else {
        this.$refs.tree.setCheckedKeys([]);
      }
    },
    clickRightCheckAll(v) {
      //右侧全选
      if (v) {
        let keys = this.getChildNodeKeys(
          this.rightNodeData,
          this.rightFilterText
        );
        // 右侧已选
        this.rightSelectNum = keys.length ? keys.length : 0;
        this.$refs.selectedTree.setCheckedKeys(keys);
      } else {
        this.$refs.selectedTree.setCheckedKeys([]);
      }
    },
    //获取某个节点的id值
    getChildNodeKeys(node, filterText) {
      const keys = this.getAllIds(node).map((item) => {
        if (item.label.includes(filterText)) {
          return item.id;
        }
      });
      return keys;
    },
    filterNode(value, data) {
      let labelName = this.defaultProps.label;
      if (!value) return true;
      return data[labelName].indexOf(value) !== -1;
    },
    nodeClick() {
      let keys = this.$refs.tree.getCheckedKeys(false);
      this.leftSelectNum = keys.length ? keys.length : 0;
    },
    selectedClick() {
      let keys = this.$refs.selectedTree.getCheckedKeys(false);
      this.rightSelectNum = keys.length ? keys.length : 0;
    },
    //向右移动
    removeToRight() {
      //获取此时左边树状结构中被选中的节点,使用原始数据结构删除不包含在选中数组中的节点,创建新的数组,渲染在右侧
      //获取此时左侧中被选中的节点数组id
      this.leftSelectNum = 0;
      this.leftCheckAll = false;
      this.leftFilterText = "";
      let keys = this.$refs.tree.getCheckedKeys(false);
      let checkedKeys = [...this.rightKeys, ...keys];
      this.rightKeys = checkedKeys;
      this.rerenderData(this.rightKeys);
      this.$refs.tree.setCheckedKeys([]);
    },
    //向左移动的时候
    removeToLeft() {
      //说明有已选择的数据改变,此时判断右边选中的数据重新渲染左边树状的结构
      //渲染逻辑:使用原始数据结构删除包含在右边选中数组中的数组
      //找到目前被选中的keys,从rightKeys中去掉,在使用rightKeys左右两侧数组
      this.rightSelectNum = 0;
      this.rightCheckAll = false;
      this.rightFilterText = "";
      let keys = this.$refs.selectedTree.getCheckedKeys(false);
      this.rightKeys = this.rightKeys.filter((v) => {
        return !keys.includes(v);
      });
      let checkedKeys = this.rightKeys;
      this.rerenderData(checkedKeys);
      this.$refs.selectedTree.setCheckedKeys([]);
    },
    // 渲染逻辑:右侧列表,删除未选中节点;左侧列表,删除已选中节点
    rerenderData(checkedKeys) {
      const childrenName = this.defaultProps.children;
      const leftNodeData = JSON.parse(JSON.stringify(this.nodeData));
      const rightNodeData = JSON.parse(JSON.stringify(this.nodeData));
      this.removeUncheckedNodes(rightNodeData, checkedKeys, childrenName);
      this.removeCheckedNodes(leftNodeData, checkedKeys, childrenName);
      this.rightNodeData = rightNodeData;
      this.leftNodeData = leftNodeData;
    },
    //  右侧移除未选中节点
    removeUncheckedNodes(data, checkedKeys, childrenName) {
      for (let i = 0; i < data.length; i++) {
        if (data[i][childrenName]) {
          data[i][childrenName] = data[i][childrenName].filter((child) =>
            checkedKeys.includes(child.id)
          );
          if (
            data[i][childrenName].length === 0 &&
            !checkedKeys.includes(data[i].id)
          ) {
            data.splice(i, 1);
            i--;
          }
        } else if (!checkedKeys.includes(data[i].id)) {
          data.splice(i, 1);
          i--;
        }
      }
    },
    // 左侧移除选中节点
    removeCheckedNodes(data, checkedKeys, childrenName) {
      for (let i = 0; i < data.length; i++) {
        if (data[i][childrenName]) {
          data[i][childrenName] = data[i][childrenName].filter(
            (child) => !checkedKeys.includes(child.id)
          );
          if (
            data[i][childrenName].length === 0 &&
            checkedKeys.includes(data[i].id)
          ) {
            data.splice(i, 1);
            i--;
          }
        } else if (checkedKeys.includes(data[i].id)) {
          data.splice(i, 1);
          i--;
        }
      }
    }
  },
};
</script>

<style scoped>
.tree-transfer {
  display: flex;
  justify-content: center;
}
.transfer-panel {
  box-sizing: border-box;
  width: 200px;
  height: 300px;
  border: 1px solid #ebeef5;
  border-radius: 5px;
}

.transfer-panel__header {
  display: flex;
  justify-content: space-between;
  box-sizing: border-box;
  height: 40px;
  border-bottom: 1px solid #ebeef5;
  background-color: rgb(245, 247, 250);
  padding-left: 10px;
  line-height: 40px;
  padding-right: 20px;
}
.title {
  font-size: 16px;
  margin-left: 5px;
}
.transfer-panel__content {
  height: calc(100% - 40px);
  overflow-y: scroll;
}
.button {
  width: 120px;
  display: flex;
  justify-content: center;
  align-items: center;
}
</style>

父组件及使用

<template>
  <div>
    <el-button type="primary" @click="dialogVisible = true">+添加</el-button>
    <el-dialog title="提示" :visible.sync="dialogVisible" width="50%">
      <Transfer
        :treeData="data"
        :defaultProps="{ children: 'children', label: 'label' }"
        @changeKeys="getKeys($event)"
      />
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="confirm">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import Transfer from "./transfer.vue";

export default {
  components: { Transfer },
  data() {
    return {
      dialogVisible: false,
      data: [
        {
          id: 1,
          label: "一级 1"
        },
        {
          id: 2,
          label: "一级 2",
          children: [
            {
              id: 5,
              label: "二级 2-1",
            },
            {
              id: 6,
              label: "二级 2-2",
            },
          ],
        },
        {
          id: 3,
          label: "一级 3",
          children: [
            {
              id: 7,
              label: "二级 3-1",
            },
            {
              id: 8,
              label: "二级 3-2",
            },
          ],
        },
      ],
    };
  },
  methods: {
    getKeys(val) {
      let result = [];
      for (let id of val) {
        let item = this.findItemById(this.data, id);
        if (item) {
          result.push(item);
        }
      }
      this.chooseOrg = result;
      console.log(this.chooseOrg, "  this.chooseOrg");
    },
    findItemById(data, targetId) {
      for (let item of data) {
        if (item.id === targetId) {
          return { id: item.id, label: item.label };
        } else if (item.children) {
          let result = this.findItemById(item.children, targetId);
          if (result) {
            return result;
          }
        }
      }
      return null;
    },

    confirm() {
      // console.log()
    },
  },
};
</script>