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

news2024/11/16 5:58:31

特性 

  1. 可以自定义主键、配置选项
  2. 支持预定义节点图标:folder文件夹|normal普通样式
  3. 多个提示文本可以自定义
  4. 支持动态接口增删改节点

sgLazyTree源码

<template>
    <div :class="$options.name" v-loading="rootLoading">
        <div class="tree-header">
            <div class="sg-left "></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" width="120" trigger="hover" title="" content=""
                    :disabled="false" slot-scope="{ node, data }">
                    <span class="right">
                        <el-button title="添加" type="text" size="" icon="el-icon-circle-plus-outline"
                            @click.stop="addNode(node, data)">添加</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>
        </div>
    </div>
</template>
    
<script>
export default {
    name: 'sgLazyTree',
    data() {
        return {
            // 动态树:增删改_________________________________________________________
            rootNode: null,//根节点
            rootResolve: null,//根节点
            focusNodeId: null,//聚焦高亮新添加ID
            rootLoading: false,//根节点列表加载
            mainKey: 'id',//默认主键
            defaultRootId: 'root',//默认根节点ID就是root
            // _________________________________________________________
        }
    },
    props: ["data"],
    watch: {
        data: {
            handler(d) {
                d.nodeKey && (this.mainKey = d.nodeKey);
                d.rootId && (this.defaultRootId = d.rootId);
            }, deep: true, immediate: true,
        },
    },
    methods: {
        // 动态懒加载树:增删改_________________________________________________________
        // 加载根节点
        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;

    }

    .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> 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/963723.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

wireshark抓包分析

题目一&#xff1a;Cephalopod(图片提取) 打开下载好的数据包&#xff1a;CtrlF 按照如图选择分组字节流&#xff0c;选择字符串&#xff0c;输入‘flag’筛选出数据包&#xff1b; 点击筛选出来的一条数据包&#xff0c;右键选择追踪tcp流&#xff1b; 然后可以看到png的字样…

2.92-KFKG射频微波同轴连接器的电气特性

2.92mm连接器的名称是以其外导体内径命名的&#xff0c;采用空气介质工作频率高达40GHz,可与SMA和3.5mm连接器互换对插。优越的电性能、可靠的连接尤其适用于测试系统和武*装备&#xff0c;成为国际上应用最为广泛的毫米微波连接器之一。 电气特性&#xff1a; 特性阻抗&…

解读《生成式人工智能服务管理暂行办法》

《生成式人工智能服务管理暂行办法》 第一章 总 则第二章 技术发展与治理第三章 服务规范第四章 监督检查和法律责任第五章 附 则 以ChatGPT为代表的现象级互联网应用的出现&#xff0c;掀起了人工智能领域新一轮技术浪潮。作为新一代信息技术&#xff0c;生成式人工智能通过…

正点原子I.MX6ull应用编程 看门狗实验 /dev/watchdog: Device or resource busy

记录自己学习正点原子I.Mx6ull应用编程教程中遇到的坑点和问题 按着正点原子<<I.MX6U嵌入式Linux C应用编程指南>>学习看门狗应用编程&#xff0c;在运行程序的时候出现 open error: /dev/watchdog: Device or resource busy 可以看到watchdog忙碌&#xff0c;看起…

Python所有方向的学习路线图!!

学习路线图上面写的是某个方向建议学习和掌握的知识点汇总&#xff0c;举个例子&#xff0c;如果你要学习爬虫&#xff0c;那么你就去学Python爬虫学习路线图上面的知识点&#xff0c;这样学下来之后&#xff0c;你的知识体系是比较全面的&#xff0c;比起在网上找到什么就学什…

MyBatis——MyBatis数据源与连接池

摘要 博文主要介绍MyBatis数据源与连接池&#xff0c;更好的理解MyBatis数据源与连接池。 一、MyBatis数据源DataSource分类 MyBatis把数据源DataSource分为三种&#xff1a; UNPOOLED 不使用连接池的数据源POOLED 使用连接池的数据源JNDI 使用JNDI实现的数据源 MyBatis内…

ESP32C3 LuatOS RC522①写入数据并读取M1卡

LuatOS RC522官方示例 官方示例没有针对具体开发板&#xff0c;现以ESP32C3开发板为例。 选用的RC522模块 ESP32C3-CORE开发板 注意ESP32C3的 SPI引脚位置&#xff0c;SPI的id2 示例代码 -- LuaTools需要PROJECT和VERSION这两个信息 PROJECT "helloworld" VERSIO…

网络地址转换技术NAT(第九课)

一 什么是NAT&#xff1f; NAT是网络地址转换的缩写&#xff0c;是一种在计算机网络中使用的技术&#xff0c;可以将私有地址转换为公共地址&#xff0c;从而实现本地网络与公共网络的互联。NAT工作在网络层&#xff0c;可以隐藏内部网络中的IP地址和端口号&#xff0c;从而增…

在数字时代中,该怎么保留温暖的传统

在我们现代快节奏的数字时代&#xff0c;许多人会不禁思考&#xff0c;传统的壁炉是否还有存在的必要。毕竟&#xff0c;现代家庭通常配备了先进的暖气系统和电子设备&#xff0c;能够在寒冷的日子里提供温暖。然而&#xff0c;尽管科技的进步使得家庭取暖变得更加便捷&#xf…

SpringCloudAlibaba常用组件

SpringCloudAlibaba常用组件 微服务概念 1.1 单体、分布式、集群 单体 ⼀个系统业务量很⼩的时候所有的代码都放在⼀个项⽬中就好了&#xff0c;然后这个项⽬部署在⼀台服务器上就 好了。整个项⽬所有的服务都由这台服务器提供。这就是单机结构。 单体应⽤开发简单,部署测试…

Axure RP暗黑色高保真中后台原型组件模板库及组件库素材

Axure RP暗黑色高保真中后台原型组件模板库及组件库素材&#xff0c;黑色一直以来就可以给人以高级、神秘的语义象征&#xff0c;相比于浅色模式&#xff0c;暗色模式藏着更多可能性。色彩具有层级关系&#xff0c;深色会在视觉感官上自动后退&#xff0c;浅色部分则会向前延展…

docker 笔记1

目录 1.为什么有docker ? 2.Docker 的核心概念 3.容器与虚拟机比较 3.1传统的虚拟化技术 3.2容器技术 3.3Docker容器的有什么作用&#xff1f; 3.4应用案例 4. docker 安装下载 4.1CentOS Docker 安装 4.2 Docker的基本组成 &#xff1f;&#xff08;面试&#xff09…

MLC-LLM 部署RWKV World系列模型实战(3B模型Mac M2解码可达26tokens/s)

0x0. 前言 我的 ChatRWKV 学习笔记和使用指南 这篇文章是学习RWKV的第一步&#xff0c;然后学习了一下之后决定自己应该做一些什么。所以就在RWKV社区看到了这个将RWKV World系列模型通过MLC-LLM部署在各种硬件平台的需求&#xff0c;然后我就开始了解MLC-LLM的编译部署流程和…

分公司电脑访问总部服务器突然不通了走的是SSL隧道,如何排查处理?

环境: 1.总部:AF做为PN AF-2000-FH2130B-SC v8.0.4 2.分部:SSL设备做对接为PN SSL VPN V7.0 单臂架设 出口AF v8.0.75 核心交换机 RUIYI NBS5710-24GT4SFP-E 问题描述: 1.分部下面pc其中一个网段(192.168.8.0)ping总部服务器(172.16.10.10)不通,长ping 98%丢包,…

Java- 虚拟机学习总结

Java文件编译&#xff0c;加载过程 写好java文件&#xff0c;jdk会通过javac编译class文件&#xff0c;classLaoder通过classpath将字节码文件加载进入jre jvm数据区 包含栈&#xff0c;堆&#xff0c;程序计数器&#xff0c;方法区&#xff0c;本地方法栈 JAVA里的常量&…

‘XXX‘ module: ‘XXX‘ facet resources问题解决(已解决)

问题描述&#xff1a;XXX module: XXX facet resources 在创建web工程的时候&#xff0c;出现说模块没有创建&#xff0c;但是我们返回我们的模块&#xff0c;web模块是添加好的。 通过多次实验&#xff0c;解决问题&#xff1a;就是web框架它的名称不能是一样的&#xff0c;必…

用迅为i.MX6ULL开发板同一个网段概念

使用 nfs 之前&#xff0c;开发板、虚拟机 ubuntu、windows 电脑三者要互相 ping 通&#xff0c;这就涉及到了同一个网段 的概念。 概念&#xff1a;同一个网段是指 IP 地址和子网掩码相与得到的相同的网络地址。 快速判断同一个网段&#xff1a; &#xff08;1&#xff09…

一个面向MCU的小型前后台系统

JxOS简介 JxOS面向MCU的小型前后台系统&#xff0c;提供消息、事件等服务&#xff0c;以及软件定时器&#xff0c;低功耗管理&#xff0c;按键&#xff0c;led等常用功能模块。 gitee仓库地址为&#xff08;复制到浏览器打开&#xff09;&#xff1a; https://gitee.com/jer…

访问 GitHub 方法

访问 GitHub 方法 方法一&#xff1a;最常见的就是 fq&#xff0c;但这个是违法的行为&#xff0c;自己私下搞可以&#xff0c;不能教你们。 方法二&#xff1a;利用加速器&#xff0c;这是正规合法操作。这里推荐一个免费的加速器&#xff0c;下载安装 Watt Toolkit加速器,原名…

【Apollo学习笔记】——规划模块TASK之SPEED_HEURISTIC_OPTIMIZER

文章目录 前言SPEED_BOUNDS_PRIORI_DECIDER功能简介SPEED_BOUNDS_PRIORI_DECIDER相关配置SPEED_BOUNDS_PRIORI_DECIDER流程1. 对路程和时间进行采样以及速度限制2. 设计状态转移方程&#xff08;cost计算&#xff09;2.0 CalculateCostAt代价计算2.1 GetObstacleCost障碍物cost…