Bootstrap

element 实现左侧为树形结构、右侧无层级结构的穿梭框

1.实现一下功能

将左侧数据移到右侧,将右侧数据移到左侧
请添加图片描述

设计思路,
1.获取左侧选中的树(过滤掉根节点),点中间的箭头时,遍历树,将选中的数据通过匹配id,进行删除,同时将选中的数据,push到右边的树,此时:将左边选中的数据添加到右边的树
2.将右边选中部分,在点击中间左侧箭头时,删除右侧选中数据,同时,将左侧树遍历删除右侧剩余数据,此时:将右边选中的数据添加到左边的树
3.向后台返回数据时,使用dataRight就可以了

<template>
  <!-- 自定义树形穿梭框组件、理论上左右均可选择是否为树形结构,目前固定为左侧树形、右侧无层级结构 -->
  <div class="tree-transfer">
    <!-- 穿梭框左侧 -->
    <div class="tree-transfer-left">
      <!-- 左侧采用element-ui的el-tree -->
      <el-input placeholder="输入关键字进行过滤" v-model="filterText" style="padding:10px;" />
      <div v-show="dataLeft.length>0" style="width:95%;height:230px;overflow-y:auto;margin:0 auto;">
        <el-tree ref="treeLeft" :data="dataLeft" show-checkbox node-key="id" :props="defaultProps" :filter-node-method="filterNode" @check-change="leftCheckChange" default-expand-all>
        </el-tree>
      </div>
      <div v-show="!dataLeft.length>0" style="margin:120px auto 0;width:100%;height:30px;text-align:center;">
        暂无数据
      </div>

    </div>
    <!-- 穿梭框中间按钮区域 -->
    <div class="tree-transfer-middle">
      <!--  -->
      <el-button circle type="info" icon="el-icon-arrow-left" :style="selectRightList.length>0?'background:#409EFF;border-color:#409EFF':''" @click="removeClick"></el-button>
      <el-button circle type="info" icon="el-icon-arrow-right" :style="selectLeftList.length>0?'background:#409EFF;border-color:#409EFF':''" @click="addClick"></el-button>
    </div>
    <!-- 穿梭框右侧 -->
    <div class="tree-transfer-right">
      <!-- 右侧直接放置结果 -->
      <!-- 这里也采用tree结构,默认是对数据进行处理使得没有树形结构,也可以选择有树形结构 -->
      <div style="height:50px;width:100%;background:#f2f2f2;line-height:50px;padding-left:10px;margin-bottom:10px;"> 已选择 </div>
      <div v-show="dataRight.length>0">
        <el-tree ref="treeRight" :data="dataRight" show-checkbox node-key="id" :props="defaultProps" @check-change="rightCheckChange">
        </el-tree>
      </div>
      <div v-show="!dataRight.length>0" style="margin:120px auto 0;width:100%;height:30px;text-align:center;">
        暂无数据
      </div>
    </div>
  </div>
</template>

<script>

export default {
  props: [],
  data() {
    return {
      defaultProps: {
        children: "child",
        label: "name",
      },
      yuanlaiData: [], //保存一个原始树

      dataLeft: [], //左侧树--数据
      selectLeftList: [], //左侧树-勾选
      filterText: "", //左侧的树进行过滤

      dataRight: [], //右侧树--数据
      selectRightList: [], //右侧树-勾选

      setUserstreeData: [], //获取的一个完整树
      currentData: [], //这个树是剔除右侧数据的树
      dataListData: {
        id: "1036304891099578311",
        name: "美丽集团有限公司",
        child: [
          {
            id: "1363068427315736522",
            name: "测试部门",
            child: null,
          },
          {
            id: "1406942927553146833",
            name: "12412412",
            child: null,
          },
          {
            id: "1353979515791900644",
            name: "漂亮工程有限公司",
            child: [
              {
                id: "1451382359171833855",
                name: "测试",
                child: null,
              },
            ],
          },
          {
            id: "1353981813561643066",
            name: "美容研究院",
            child: [
              {
                id: "1368809908881891377",
                name: "健康西分院",
                child: null,
              },
              {
                id: "1406942760036839488",
                name: "财富统计处",
                child: [
                  {
                    id: "1353985339042029599",
                    name: "财务有限公司",
                    child: [
                      {
                        id: "1425622408449269700",
                        pid: "1353985339042029569",
                        name: "富强",
                        child: null,
                      },
                    ],
                  },
                ],
              },
            ],
          },
          {
            id: "1353982603181314012",
            name: "民主技术研究院",
            child: null,
          },
        ],
      },
    };
  },
  mounted() {
    this.getReViewOfficeTree();
  },
  // 监控data中的数据变化
  watch: {
    filterText(val) {
      this.$nextTick(() => {
        this.$refs.treeLeft.filter(val);
      });
    },
  },
  methods: {
    // 获取左侧的树
    getReViewOfficeTree() {
      this.setUserstreeData = this.dataListData;
      if (Object.keys(this.setUserstreeData).length > 0) {
        this.dataLeft = [this.setUserstreeData];
        this.dataLeft.forEach((item, index) => {
          if (item.child && item.child.length > 0) {
            item.disabled = true;
            this.getTreeDisable(item.child);
          }
        });
        let data = JSON.parse(JSON.stringify(this.setUserstreeData));
        this.yuanlaiData = [data];
      }
    },
    addClick() {
      // 定义一个递归过滤的方法,用来删掉父级中给的元素
      // 获取所有选中的项并且去掉父级
      let list = this.$refs.treeLeft.getCheckedNodes(); //勾选的节点

      // 走原始数据中删掉已经选择的
      // 1.父级的删除
      const parList = list.filter((item) => {
        return item.child;
      });
      parList.forEach((item) => {
        let index = this.dataLeft.findIndex((itema) => {
          return itema.id == item.id;
        });
        if (index >= 0) {
          this.dataLeft.splice(index, 1);
        }
      });
      // 2.子级的删除
      list = list.filter((item) => {
        return !item.child || !item.child.length;
        //return !item.child||!item.child.length;//当发现返回的孩子是一个为空的数组时,使用这个返回
      });
      // 选中的数据list,遍历树,找到对应的id,将这条数据进行删除
      list.forEach((item, index) => {
        this.getTreeName(this.dataLeft, item.id);
      });

      // 3.将选择的项添加到右侧
      this.dataRight.push(...list);
      this.selectLeftList = this.$refs.treeLeft.getCheckedNodes(); //用于控制右侧按钮颜色
      this.selectRightList = this.$refs.treeRight.getCheckedNodes(); //用于控制左侧按钮颜色
    },
    // 循环遍历树形结构,删除符合条件的叶子节点
    getTreeName(list, id) {
      let _this = this;
      for (let i = 0; i < list.length; i++) {
        let a = list[i];
        if (a.id === id) {
          const index = list.findIndex((item) => item.id === id);
          list.splice(index, 1);
          return a.name;
        } else {
          if (a.child && a.child.length > 0) {
            let res = _this.getTreeName(a.child, id);
            if (res) {
              return res;
            }
          }
        }
      }
    },
    // 将所有根节点禁选
    getTreeDisable(list) {
      let _this = this;
      for (let i = 0; i < list.length; i++) {
        let a = list[i];
        if (a.child && a.child.length > 0) {
          a.disabled = true;
          _this.getTreeDisable(a.child);
        }
      }
    },
    removeClick() {
      // 从右侧移除时的方法
      // 1.从右侧删除选中的数据
      let list = this.$refs.treeRight.getCheckedNodes();
      for (let item of list) {
        let index = this.dataRight.findIndex((item2) => {
          return item.id == item2.id;
        });
        if (index >= 0) {
          this.dataRight.splice(index, 1);
        }
      }
      // 2.遍历右边的数据,将左边树对应的数据删除---右边存在的数据不能在左边树存在
      if (this.dataRight.length > 0) {
        this.currentData = JSON.parse(JSON.stringify(this.yuanlaiData));
        this.dataRight.forEach((item, index) => {
          this.getTreeName(this.currentData, item.id);
          this.dataLeft = this.currentData;
        });
      } else {
        this.dataLeft = JSON.parse(JSON.stringify(this.yuanlaiData));
      }
      this.selectLeftList = this.$refs.treeLeft.getCheckedNodes(); //用于控制右侧按钮颜色
      this.selectRightList = this.$refs.treeRight.getCheckedNodes(); //用于控制左侧按钮颜色
    },
    getResult() {
      return this.dataRight;
    },
    leftCheckChange(data, checked, indeterminate) {
      console.log("1212dsageddsa", data, checked, indeterminate);
      this.selectLeftList = this.$refs.treeLeft.getCheckedNodes(); //用于控制右侧按钮颜色
      this.selectRightList = this.$refs.treeRight.getCheckedNodes(); //用于控制左侧按钮颜色
    },
    rightCheckChange(data, checked, indeterminate) {
      this.selectRightList = this.$refs.treeRight.getCheckedNodes(); //用于控制左侧按钮颜色
      this.selectLeftList = this.$refs.treeLeft.getCheckedNodes(); //用于控制右侧按钮颜色
    },
    //  左侧树  支持过滤
    filterNode(value, data) {
      if (!value) return true;
      return data.name.indexOf(value) !== -1;
    },
  },
};
</script>

<style scoped lang="scss">
    .tree-transfer{
        display: flex;
        min-height: 250px;
        .tree-transfer-left{
            width:45%;
            border:1px #E5E5E5 solid;
            border-radius: 10px;
            height:330px;
            overflow-y:auto;
           
        }
        .tree-transfer-middle{
            display: flex;
            justify-content: center;
            align-items: center;
            width:14%;
        }
        .tree-transfer-right{
            width:40%;
            border:1px #E5E5E5 solid;
            border-radius: 10px;
            height:330px;
            overflow-y:auto;
        }
    }
</style>

2.关于树形结构数据的知识点

在这里插入图片描述
实现一下功能
表单—树形结构进行多选,选择器与树形结构两两进行绑定

2.1获取节点

 <el-tree class="filter-tree" show-checkbox node-key="id" :default-expanded-keys="[0]" :data="treeData" :props="defaultProps" ref="popoverTree" :highlight-current="true" @check-change='getCheckedNode'>
                </el-tree>
 getCheckedNode(e) {
      // 获取勾选的所有关联节点(所有的半选节点除外)
      let res = this.$refs.popoverTree.getCheckedNodes();
      console.log("res", res);
      // 获取勾选的所有关联节点(所有的半选节点也一并获取)
       let res2 = this.$refs.popoverTree.getCheckedNodes(false, true);
       //获取所有叶子节点
      var allNodes = this.$refs.popoverTree.store._getAllNodes();
      var leafNodes = allNodes.filter((item) => item.isLeaf);
      var leafNodesChecked = allNodes.filter((item) => item.isLeaf&&item.checked);//获取所有叶子节点,同时被选中
       console.log("leafNodes",leafNodes);
//获取被选中的id List
      this.ruleForm.taskId2 = leafNodesChecked .map((item, index) => {
        return item.key;
      });
    },

2.2获取所有的叶子节点,将数据转换成选择器所需要的数据

clickQuery() {
 let res = {
        code: "200",
        data: {
          id: "1036304891099571",
          name: "集团有限公司",
          child: [
            {
              id: "13630684273157318",
              name: "测试部门",
              child: null,
            },
            {
              id: "1368809263143937",
              name: "124",
              child: null,
            },
            {
              id: "140694353146882",  
              name: "12412412",
              child: null,
            },
            {
              id: "135397941900674",
              name: "美丽公司",
              child: [
                {
                  id: "14517171833857",
                  name: "测试",
                  child: null,
                },
              ],
            },
            {
              id: "13539818443010",
              name: "技术研究院",
              child: [
                {
                  id: "136886881891329",
                  name: "分院",
                  child: null,
                },
                {
                  id: "14069427839426",
                  name: "统计处",
                  child: null,
                },
                {
                  id: "140713948787842",
                  name: "分析处",
                  child: null,
                },
                {
                  id: "14071089877249",
                  name: "审核处",
                  child: null,
                },
              ],
            },
            {
              id: "1353982671314049",
              name: "技术研究院",
              child: null,
            },
            {
              id: "135396971778",
              name: "测试",
              child: null,
            },
            {
              id: "13539862029569",
              name: "有限公司",
              child: [
                {
                  id: "14255449269761",
                  name: "三江二级",
                  child: null,
                },
              ],
            },
            {
              id: "13539855312897",
               name: "动力技术研究院",
              child: null,
            },
            {
              id: "1353986959042",
              name: "投资有限公司",
              child: [
                {
                  id: "1354868555010",
                  name: "测试部门",
                  child: null,
                },
              ],
            },
          ],
        },
      };
      this.treeData = [res.data];
      this.treeData.forEach((item, index) => {
        if (item.child && item.child.length > 0) {
          item.disabled = true;
          this.getTreeDisable(item.child);
        }
      });
      this.treeDataList = [];
      this.getTreeList(this.treeData);
  },
 // 将所有根节点禁选
    getTreeDisable(list) {
      let _this = this;
      for (let i = 0; i < list.length; i++) {
        let a = list[i];
        if (a.child && a.child.length > 0) {
          a.disabled = true;
          _this.getTreeDisable(a.child);
        }
      }
    },
  //获取树形数据转换成选择列表需要的数据
 getTreeList(list) {
      let _this = this;
      for (let i = 0; i < list.length; i++) {
        let a = list[i];
        if (a.child && a.child.length > 0) {
          _this.getTreeList(a.child);
        } else {
          this.treeDataList.push({ name: a.name, id: a.id });
        }
      }
    },

2.3

<el-form-item label="所属单位名称">
              <el-popover placement="bottom-start" trigger="click">
                <el-select v-model="ruleForm.taskId2" slot="reference" style="width: 100%" placeholder="" multiple @change="treeDataListChange" @remove-tag="treeDataListRemoveTag" popper-class="riskDoubtsListClass">
                  //下拉框列表必须存在,只能用v-show="false"
                  <div v-show="false">
                    <el-option v-for="item in treeDataList" :key="item.id" :label="item.name" :value="item.id"> </el-option>
                  </div>
                </el-select>
                <el-tree class="filter-tree" show-checkbox node-key="id" :default-expanded-keys="[0]" :data="treeData" :props="defaultProps" ref="popoverTree" :highlight-current="true" @node-click="officeIdHandleNodeClick" @check-change='getCheckedNode'>
                </el-tree>
              </el-popover>
            </el-form-item>

// 多选 选择器 e为删除后剩下的列表
    treeDataListChange(e) {
      console.log("eChange", e, this.ruleForm.taskId2);
      this.$refs.popoverTree.setCheckedKeys(this.ruleForm.taskId2);//此操作是将保留的数据在树形结构上进行选中
    },
    // 多选 选择器 e为删除的某项,此操作仅供学习
    treeDataListRemoveTag(e) {
      console.log("eTag", e);
    },
    //此操作将在树形结构选中的项,在选择器上进行回显
    getCheckedNode(e) {
      var allNodes = this.$refs.popoverTree.store._getAllNodes();
      var leafNodes = allNodes.filter((item) => item.isLeaf && item.checked);
      this.ruleForm.taskId2 = leafNodes.map((item, index) => {
        return item.key;
      });
    },
    // 项目单位点击数回显到input,此操作仅供学习
    officeIdHandleNodeClick(data) {
      console.log("datadatadata", data);
    },
<style lang='scss'>
//将选择器的下拉框列表进行隐藏,但是必须存在
.riskDoubtsListClass {
  display: none;
}  
</style>

将选择器上的数据同步到树形结构上

this.$refs.popoverTree.setCheckedKeys(this.ruleForm.taskId2);//此操作是将保留的数据在树形结构上进行选中

将树形结构上的数据同步到选择器上

 var allNodes = this.$refs.popoverTree.store._getAllNodes();
      var leafNodes = allNodes.filter((item) => item.isLeaf && item.checked);
      this.ruleForm.taskId2 = leafNodes.map((item, index) => {
        return item.key;
      });
;