【sgLazyTree】自定义组件:动态懒加载el-tree树节点数据,实现增删改、懒加载及局部数据刷新。

特性 

  1. 可以自定义主键、配置选项
  2. 支持预定义节点图标:folder文件夹|normal普通样式
  3. 多个提示文本可以自定义
  4. 支持动态接口增删改节点
  5. 可以自定义根节点Id
  6. 可以设置最多允许添加的层级深度

sgLazyTree源码

<template>
    <div :class="$options.name" v-loading="rootLoading">
        <div class="tree-header" v-if="!(readonly || readonly === '')">
            <div class="sg-left ">
                <template v-if="uploadData">
                    <el-tooltip popper-class="sg-el-tooltip" :enterable="false" effect="dark" :content="`支持拖拽到树上传文件`"
                        placement="top-start">
                        <el-button type="text" icon="el-icon-upload" size="mini"
                            @click="d => $refs.sgUpload.triggerUploadFile()">
                            批量导入
                        </el-button>
                    </el-tooltip>
                    <el-button type="text" icon="el-icon-download" size="mini" @click="downloadTpl">
                        下载模板
                    </el-button>
                </template>
            </div>
            <div class="sg-right ">
                <el-button type="text" size="mini" @click.stop="addRoot">{
   
   { (data.text || {}).addRootButtonText
                    || `添加根节点` }}<i class="el-icon-circle-plus-outline"></i></el-button>
            </div>
        </div>
        <div class="tree-container">
            <el-tree :load="loadNode" lazy ref="tree" :node-key="mainKey" :props="data.props || { label: 'label' }"
                :icon-class="`${data.iconType}-tree-node`" :indent="data.indent || 25" @current-change="current_change"
                @node-click="nodeClick" highlight-current>
                <el-popover popper-class="tree-el-popover" placement="right" trigger="hover" title="" content=""
                    :disabled="readonly || readonly === ''" slot-scope="{ node, data }">
                    <span class="right">
                        <el-button title="添加" type="text" size="" icon="el-icon-circle-plus-outline"
                            @click.stop="addNode(node, data)" v-if="showAddButton(node)">添加</el-button>
                        <el-button title="删除" type="text" size="" icon="el-icon-remove-outline"
                            @click.stop="remove(node, data)">删除</el-button>
                    </span>
                    <div slot="reference" class="node-label">
                        <label class="left" :title="node.label">
                            {
   
   { node.label }}
                        </label>
                    </div>
                </el-popover>
            </el-tree>

            <!-- 上传组件 -->
            <sgUpload drag ref="sgUpload" :data="uploadData" @success="uploadSuccess" @error="uploadError" hideUploadTray />
        </div>
    </div>
</template>
    
<script>
import sgUpload from "@/vue/components/admin/sgUpload";
export default {
    name: 'sgLazyTree',
    components: {
        sgUpload,
    },
    data() {
        return {
            // 动态树:增删改_________________________________________________________
            rootNode: null,//根节点
            rootResolve: null,//根节点
            focusNodeId: null,//聚焦高亮新添加ID
            rootLoading: false,//根节点列表加载
            mainKey: 'id',//默认主键
            defaultRootId: 'root',//默认根节点ID就是root
            maxAddLevel: null,// 最多允许添加的层级
            // _________________________________________________________
        }
    },
    props: [
        "data",
        "readonly",
        "uploadData",
        /* 例子 uploadData: {
             accept: '.xls,.xlsx',
             actionUrl: `${this.$d.API_ROOT_URL}/core/resource/upload`,
         }, */
    ],

    watch: {
        data: {
            handler(d) {
                d.nodeKey && (this.mainKey = d.nodeKey);//主键
                d.rootId && (this.defaultRootId = d.rootId);//根节点ID
                d.maxAddLevel && (this.maxAddLevel = d.maxAddLevel);// 最多允许添加的层级
            }, deep: true, immediate: true,
        },
    },
    methods: {
        showAddButton(node) {
            if (this.maxAddLevel) {
                return node.level < this.maxAddLevel; // 最多允许添加的层级
            } else return true;
        },
        downloadTpl(d) {
            this.$emit(`downloadTpl`);
        },
        uploadSuccess(d, f) {
            this.$emit(`uploadSuccess`, d, f);
        },
        uploadError(d, f) {
            this.$emit(`uploadError`, d, f);
        },
        // 动态懒加载树:增删改_________________________________________________________
        // 加载根节点
        loadRootNode() {
            this.rootNode.childNodes = [];
            this.loadNode(this.rootNode, this.rootResolve);
        },
        // 加载常规节点
        loadNode(node, resolve) {
            let data = {};
            if (node.level === 0) {
                data = { [this.mainKey]: this.defaultRootId };
                this.rootNode = node;//记录根节点
                this.rootResolve = resolve;//记录根节点
            } else {
                data = node.data;
            }
            this.loadNodeData(data, d => {
                resolve(d);
                this.rootLoading = false;
                this.$nextTick(() => {
                    this.focusNode(this.focusNodeId)
                });
            });
        },
        // 加载节点数据(通过接口向后台获取数据)
        loadNodeData(data, cb) {
            let resolve = d => { cb && cb(d) };
            this.$emit(`loadNode`, data, resolve);
        },
        // 聚焦到某一个节点
        focusNode(id) {
            if (!id) return;
            this.$nextTick(() => {
                this.$refs.tree.setCurrentKey(id);//高亮显示某个节点
                this.$emit(`currentChange`, this.$refs.tree.getCurrentNode());
                this.$nextTick(() => {
                    let dom = document.querySelector(`.el-tree-node.is-current`);
                    dom && dom.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" });//缓慢滚动
                });
            });
        },
        // 添加根节点
        addRoot() { this.addNode(this.$refs.tree.root, { [this.mainKey]: this.defaultRootId }) },
        // 添加节点
        addNode(node, data) {
            let isRootNode = data[this.mainKey] === this.defaultRootId;
            isRootNode && (this.rootLoading = true);
            let resolve = d => {
                this.focusNodeId = d[this.mainKey];//记录加载完毕后需要聚焦的节点ID
                if (isRootNode) {
                    this.loadRootNode();//触发根节点加载
                } else {
                    node.loaded = false; //必须要设置loaded=false,否则第二次展开节点不会触发加载数据
                    node.expanded ? node.loadData() : node.expand();//如果已展开→触发加载,否则就先展开→触发加载 
                }
            };
            this.$emit(`addNode`, data, resolve);
        },
        // 删除节点
        remove(node, data) {
            this.$confirm(
                (this.data.text || {}).removeConfirmTip || `此操作将永久删除该节点及其下面的节点,是否继续?`,
                (this.data.text || {}).removeConfirmTitle || `提示`,
                {
                    dangerouslyUseHTMLString: true,
                    confirmButtonText: `确定`,
                    cancelButtonText: `取消`,
                    type: "warning",
                }).then(() => {
                    this.removeNodeData(node, data)
                }).catch(() => { });
        },
        // 删除节点数据(通过接口向后台删除数据)
        removeNodeData(node, data) {
            node.loading = true;//出现加载动画
            let resolve = d => {
                node.loading = false;
                this.$message.success(`删除成功`);
                // 从显示界面删除节点
                let childNodes = node.parent.childNodes;
                childNodes.splice(childNodes.findIndex(d => d.data[this.mainKey] === data[this.mainKey]), 1);
            };
            this.$emit(`removeNode`, data, resolve);
        },
        // 当前选中节点变化时触发的事件
        current_change(d) { this.$emit(`currentChange`, d); },
        //点击节点
        nodeClick(d) { this.focusNodeId = null; this.$emit(`nodeClick`, d); },
    }
};
</script>
    
<style lang="scss" scoped>
@import "~@/css/sg";

.sgLazyTree {
    $treeHeaderHeight: 30px;
    width: 100%;
    height: 100%;
    display: flex;
    flex-wrap: nowrap;
    flex-direction: column;
    white-space: nowrap;
    flex-shrink: 0;
    flex-grow: 1;
    position: relative;

    .tree-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        height: $treeHeaderHeight;

        &>.sg-left {}

        &>.sg-right {}

    }

    .tree-container {
        position: relative;
        overflow: auto;
        box-sizing: border-box;
        height: calc(100% - #{$treeHeaderHeight});
        user-select: none;
        @include scrollbarHover();

        >>>.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content {
            background-color: #409EFF22; // 高亮当前选中节点背景
        }

        >>>.el-tree {
            * {
                transition: none;
            }


            .el-tree-node__children {
                min-width: max-content; //这样才会出现水平滚动条
            }

            .normal-tree-node,
            .folder-tree-node {
                flex-shrink: 0;
                display: block;
                padding: 0 !important;
                margin: 0;
                width: 20px;
                height: 20px;
                margin-right: 5px;
                background: transparent url("/static/img/fileType/folder/folder.svg") no-repeat center / contain;

                margin-left: 20px;

                &~span:not(.el-icon-loading) {
                    width: 100%;

                    .node-label {
                        height: 40px;
                        display: flex;
                        align-items: center;
                    }
                }

                &.expanded,
                &.is-leaf {
                    flex-shrink: 0;
                    transform: rotate(0deg);
                    background-image: url("/static/img/fileType/folder/folder-open.svg");
                }
            }
        }
    }
}

.tree-el-popover {
    .el-button {
        padding-top: 0;
        padding-bottom: 0;
    }
}
</style>

用例

<template>
    <div style="width: 300px;padding-right: 100px;">
        <sgLazyTree :data="lazyTreeData" @currentChange="currentChange" @loadNode="loadNode"
            @addNode="addNode" @removeNode="removeNode" />
    </div>
</template>
    
<script>
import sgLazyTree from "@/vue/components/admin/sgLazyTree";
export default {
    components: { sgLazyTree, },
    data() {
        return {
            autoId: 0,//自增编号
            lazyTreeData: {
                nodeKey: `ID`,//主键
                props: { label: 'MC' },//配置选项
                iconType: 'folder',//节点图标:folder文件夹|normal普通样式
                text: {
                    addRootButtonText: '添加根目录',//添加根节点按钮文本
                    removeConfirmTitle: '警告!!!',//删除节点提示标题
                    removeConfirmTip: '此操作将永久删除该文件夹及其下面的文件,是否继续?',//删除节点提示内容
                },
            },
        }
    },
    methods: {
        // 获取当前聚焦节点的数据
        currentChange(d) {
            console.log(`currentChange`, d);
        },
        // 加载节点数据
        loadNode(data, resolve) { this.$d.column_queryByPid({ data: { PID: data.ID }, doing: { s: d => resolve(d) } }); },
        // 添加节点
        addNode(data, resolve) {
            this.$d.column_save({
                data: {
                    MC: `新增栏目名称(${++this.autoId})`,
                    LX: 0, 
                    PID: data.ID,//上一级id
                }, doing: { s: d => resolve(d) }
            });
        },
        // 删除节点
        removeNode(data, resolve) {
            this.$d.column_delete({ data: { ID: data.ID }, doing: { s: d => resolve(d) } });
        },
    },

};
</script> 

猜你喜欢

转载自blog.csdn.net/qq_37860634/article/details/132639389