el-tree结合el-tree-transfer实现穿梭框里展示树形数据

发布于:2025-05-20 ⋅ 阅读:(13) ⋅ 点赞:(0)

参考文章:我把他的弹框单拉出来一个独立文件作为组件方便使用,遇到一些问题记录一下。
在这里插入图片描述

testComponet.vue

<template>
    <div class="per_container">
        <div class="per_con_left">
            <div class="per_con_title">未选</div>
            <div class="check_all">
                <el-checkbox
                    :indeterminate="config.left.isIndeterminate"
                    v-model="config.left.checkAll"
                    @change="handleCheckAll($event, 'left')"
                >全选/全不选</el-checkbox>
            </div>
            <div class="tree">
                <el-tree
                    ref="treeLeft"
                    :data="treeDataArr"
                    :props="propsDefault"
                    node-key="id"
                    show-checkbox
                    :filter-node-method="filterNodeLeft"
                    @check-change="handleCheckChange('left')"
                />
            </div>
        </div>
        <div class="operation">
            <el-button type="primary" @click="toRight()">
                移入
                <i class="el-icon-d-arrow-right"></i>
            </el-button>
            <el-button type="primary" icon="el-icon-d-arrow-left" @click="toLeft()">
                移除
            </el-button>
        </div>
        <div class="per_con_right">
            <div class="per_con_title">已选</div>
            <div class="check_all">
                <el-checkbox
                    :indeterminate="config.right.isIndeterminate"
                    v-model="config.right.checkAll"
                    @change="handleCheckAll($event, 'right')"
                >全选/全不选</el-checkbox>
            </div>
            <div class="tree">
                <el-tree
                    ref="treeRight"
                    :data="treeDataArr"
                    :props="propsDefault"
                    node-key="id"
                    show-checkbox
                    :filter-node-method="filterNodeRight"
                    @check-change="handleCheckChange('right')"
                />
            </div>
        </div>
    </div>
</template>
<script>
export default {
    props: ['treeData'],
    mounted() {
	    this.treeDataArr = this.treeData;
	    this.allParentKeys = this.treeDataArr.map((item) => {
	        return item.id;
	    });
	    if (this.$refs.treeLeft && this.$refs.treeRight) {
	        this.$nextTick(() => {
	            this.setTreeFilter();
	        });
	    }
  },
    created() {
        // this.treeDataArr = this.treeData;
        // this.allParentKeys = this.treeDataArr.map((item) => {
        //     return item.id;
        // });
        // if (this.$refs.treeLeft && this.$refs.treeRight) {
        //     this.$nextTick(() => {
        //         this.setTreeFilter();
        //     });
        // }
    },  
    data() {
        return {
            propsDefault: {
                label: "name"
            },
            isIndeterminateL: false,
            isIndeterminateR: false,
            checkAllLeft: false,
            checkAllRight: false,
            treeDataArr: [],
            checkedKeys: [],
            halfCheckedKeys: [],
            checkedNodes: [],
            config: {
                left: {
                    isIndeterminate: false,
                    checkAll: false,
                    ref: "treeLeft"
                },
                right: {
                    isIndeterminate: false,
                    checkAll: false,
                    ref: "treeRight"
                }
            }
        };
    },
    methods: {
        setTreeFilter() {
            this.$refs.treeLeft.filter();
            this.$refs.treeRight.filter();
        },
        toLeft() {
            this.checkedKeys = this.$refs.treeRight.getCheckedKeys();
            this.halfCheckedKeys = this.$refs.treeRight.getHalfCheckedKeys();
            this.settreeDataArr(this.treeDataArr, false);
            this.setTreeFilter();
            this.$refs.treeLeft.setCheckedKeys(this.checkedKeys);
            this.$refs.treeRight.setCheckedKeys([]);
        },
        toRight() {
            this.checkedKeys = this.$refs.treeLeft.getCheckedKeys();
            this.halfCheckedKeys = this.$refs.treeLeft.getHalfCheckedKeys();
            this.settreeDataArr(this.treeDataArr, true);
            this.setTreeFilter();
            this.$refs.treeRight.setCheckedKeys(this.checkedKeys);
            this.$refs.treeLeft.setCheckedKeys([]);
        },
        filterNodeLeft(value, data) {
            console.log('filterNodeLeft',data);
            return !data.selected;
        },
        filterNodeRight(value, data) {
            console.log('filterNodeRight',data);
            return data.selected;
        },
        // 递归设置数据选中状态
        settreeDataArr(tree, type) {
            const setTree = (treeDataArr) => {
                treeDataArr.forEach((item, index) => {
                    if (this.checkedKeys.includes(item.id)) {
                        treeDataArr[index].selected = type;
                    }
                    if (item.children && item.children.length) {
                        setTree(item.children);
                        // 判断半选框是否需要移动
                        if (this.halfCheckedKeys.includes(item.id)) {
                            if (type) {
                                treeDataArr[index].selected = type;
                            } else {
                                const target = treeDataArr[index].children.find((it) => {
                                    return it.selected;
                                });
                                if (!target) {
                                    treeDataArr[index].selected = type;
                                }
                            }
                        }
                    }
                });
            };
            setTree(tree);
        },
        submitEdit() {
            this.$emit("permissionData", this.treeDataArr);
        },
        // 勾选树结构时判断是否勾选上面的全选
        handleCheckChange(type) {
            this.checkedNodes = this.$refs[this.config[type].ref].getCheckedNodes();
            const pIds = this.checkedNodes.filter((item) => {
                return !item.pId;
            });
            if (!pIds.length) {
                this.config[type].checkAll = false;
                this.config[type].isIndeterminate = false;
                return;
            }
            if (pIds.length === this.allParentKeys.length) {
                this.config[type].checkAll = true;
                this.config[type].isIndeterminate = false;
            } else {
                this.config[type].isIndeterminate = true;
                this.config[type].checkAll = false;
            }
        },
        // 全选
        handleCheckAll(value, type) {
            const keys = value
                ? this.treeDataArr.map((item) => {
                    return item.id;
                })
                : [];
            this.$refs[this.config[type].ref].setCheckedKeys(keys, false);
        }
    }
};
</script>
<style lang="scss" scoped>
.per_container {
    display: flex;
    height: 500px;
    justify-content: space-between;
    align-items: center;
}
.per_con_left,
.per_con_right {
    width: 45%;
    height: 100%;
}
.operation {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 20px;
}
.operation .el-button {
    margin-left: 0;
    margin-bottom: 10px;
}
.per_con_title {
    height: 42px;
    line-height: 26px;
    border-radius: 8px 8px 0 0;
    padding: 8px;
    align-self: stretch;
    background: #f2f6f9;
    font-size: 16px;
    box-sizing: border-box;
    border: 1px solid #d8d8d8;
    font-weight: 700;
    text-align: left;
}
.check_all {
    height: 42px;
    line-height: 42px;
    padding: 0 5px;
    border: 1px solid #d8d8d8;
    border-top: none;
    text-align: left;
}
.tree {
    height: calc(100% - 82px);
    border: 1px solid #d8d8d8;
    border-top: none;
    overflow: auto;
}
</style>

思路
(1)思路二:利用elementUI的filter API对选中节点进行筛选,左侧筛选出未选中的,右侧筛选出选中的,用的还是同一棵树,用一个属性来区分是否选择,好处是子节点选中,父节点会跟随保存,不用重新构建树结构。
(2)通过监听treeData值变化,调用setTreeFilter也就是this.$refs.treeLeft.filter(); this.$refs.treeRight.filter();
里的filter 方法,filter 是 el-tree 组件用于动态过滤树节点,配合 filter-node-method 属性可以实现节点过滤,自定义左右侧树过滤的规则分别是是filterNodeLeft(value, data) { return !data.selected;},filterNodeRight(value, data) {return data.selected;},是否被选中(左移右移来设置selected属性)

testIndex.vue

<template>
  <div>
    <!-- 其他页面内容 -->
    <TestComponent :treeData="myTreeData" />
  </div>
</template>

<script>
import TestComponent from '@/views/test/test.vue';

export default {
  components: {
    TestComponent
  },
  data() {
    return {
      myTreeData: [
        // 这里放置你的树形数据
        {
          id: 1,
          name: '父节点1',
          children: [
            {
              id: 2,
              name: '子节点1'
            }
          ]
        }
      ]
    };
  },
  // ... existing code ...
};
</script>

关于监听treeData变化后给树赋数据这里遇到问题:我把原文监听treeData对树进行初始化一开始写在created里,导致左右都有树,然后改为mounted就没有这个问题。
原因:(操作DOM元素用mounted
依赖 DOM 元素 : setTreeFilter 方法可能依赖于 this.$refs.treeLeftthis.$refs.treeRight 这两个 DOM 引用。在 created 阶段,DOM 元素还未挂载, this.$refs 无法正确获取到对应的 DOM 元素,从而导致过滤逻辑无法正常执行。而在 mounted 阶段,DOM 元素已经挂载完成, this.$refs 可以正常获取到对应的 DOM 元素,过滤逻辑就能正常工作了。

treeData: {
      handler(val) {
        this.treeDataArr = val;
        this.allParentKeys = this.treeDataArr.map((item) => {
          return item.id;
        });
        if (this.$refs.treeLeft && this.$refs.treeRight) {
          this.$nextTick(() => {
            this.setTreeFilter();
          });
        }
      },
      deep: true,
    },
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/Error_ABC/article/details/136877442

在这里插入图片描述

 created() {
        // this.treeDataArr = this.treeData;
        // this.allParentKeys = this.treeDataArr.map((item) => {
        //     return item.id;
        // });
        // if (this.$refs.treeLeft && this.$refs.treeRight) {
        //     this.$nextTick(() => {
        //         this.setTreeFilter();
        //     });
        // }
    },  

网站公告

今日签到

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