elementplus el-tree 二次封装支持配置删除后展示展开或折叠编辑复选框懒加载功能

本文介绍了基于 ElementPlus 的 el-tree 组件进行二次封装的 TreeView 组件,使用 Vue3 和 JavaScript 实现。TreeView 组件通过 props 接收树形数据、配置项等,支持懒加载、节点展开/收起、节点点击、删除、编辑等操作。组件内部通过 ref 管理树实例,并提供了 clearCurrentNode、setCurrentKey、setExpandedKeys 等方法供父组件调用。renderContent 方法用于自定义节点内容,支持根据配置显示删除和编辑按钮。事件处理函数通过 emit 将节点操作传递给父组件,实现了组件与父组件的交互。样式部分通过 scoped 样式隔离,确保组件样式独立。

准备组件 TreeView treeUtils方法

  1. TreeView组件
<template><div class="tree-container"><div v-if="isShowHeader" class="tree-header"><slot name="header"></slot></div><el-tree ref="treeRef" :data="treeData" :props="treeProps" highlight-current node-key="id":render-content="renderContent" :lazy="lazy" :load="lazy ? loadNode : undefined":default-expanded-keys="expandedKeys" :show-checkbox="showCheckbox" :check-strictly="checkStrictly"@node-click="handleNodeClick" @node-expand="handleNodeExpand" @node-collapse="handleNodeCollapse"@check="handleCheck" /></div>
</template><script setup>
import { defineProps, defineEmits, ref } from 'vue'
import { Delete, Edit } from '@element-plus/icons-vue'
import { handleNodeExpand as handleNodeExpandUtil, handleNodeCollapse as handleNodeCollapseUtil } from '@/utils/treeUtils'// 接收父组件传来的数据
const props = defineProps({treeData: {type: Array,required: true,},treeProps: {type: Object,default: () => ({children: 'children',label: 'label',isLeaf: 'isLeaf'})},showDelete: {type: Boolean,default: false},showEdit: {type: Boolean,default: false},lazy: {type: Boolean,default: false},isShowHeader: {type: Boolean,default: false},showCheckbox: {type: Boolean,default: false},checkStrictly: {type: Boolean,default: false}
})
const applicationYear = ref('')// 接收父组件传来的事件
const emit = defineEmits(['nodeClick', 'loadChildren', 'deleteNode', 'nodeExpand', 'nodeCollapse','check'
])// 使用props中的treeProps
const { treeProps } = props// 添加treeRef
const treeRef = ref(null)// 展开的节点keys
const expandedKeys = ref([])// 添加取消选中节点的方法
const clearCurrentNode = () => {if (treeRef.value) {treeRef.value.setCurrentKey(null)}
}// 设置当前选中的节点
const setCurrentKey = (key) => {if (treeRef.value) {treeRef.value.setCurrentKey(key)}
}// 设置展开的节点
const setExpandedKeys = (keys) => {expandedKeys.value = [...keys]
}// 获取当前展开的节点
const getExpandedKeys = () => {return expandedKeys.value
}// 处理复选框选中事件
const handleCheck = (data, { checkedKeys, checkedNodes }) => {emit('check', {data,checkedKeys,checkedNodes})
}// 获取选中的节点
const getCheckedKeys = () => {return treeRef.value?.getCheckedKeys() || []
}// 获取半选中的节点
const getHalfCheckedKeys = () => {return treeRef.value?.getHalfCheckedKeys() || []
}// 设置选中的节点
const setCheckedKeys = (keys) => {treeRef.value?.setCheckedKeys(keys)
}// 暴露方法给父组件
defineExpose({clearCurrentNode,setCurrentKey,setExpandedKeys,getExpandedKeys,getCheckedKeys,getHalfCheckedKeys,setCheckedKeys
})const renderContent = (hFn, { node, data }) => {const content = [hFn('span', data[props.treeProps.label] || data.label)]// 根据showDelete配置决定是否显示删除按钮if (props.showDelete) {content.push(hFn('el-button',{type: 'danger',size: 'small',class: 'delete-btn',onClick: () => handleDeleteNode(node, data),},[hFn(Delete)]))}// 根据showDelete配置决定是否显示修改按钮if (props.showEdit) {content.push(hFn('el-button',{type: 'danger',size: 'small',class: 'edit-btn',onClick: () => handleEditNode(data),},[hFn(Edit)]))}return hFn('div',{ class: 'tree-node' },content)
}// 加载子节点数据
const loadNode = (node, resolve) => {if (!props.lazy) {return resolve([])}if (node.level === 0) {// 根节点直接返回初始数据return resolve(props.treeData)}// 触发父组件的事件来获取子节点数据emit('loadChildren', {node,resolve: (children) => {// 确保children是数组const childNodes = Array.isArray(children) ? children : []// 将子节点数据设置到当前节点的children属性中if (node.data) {node.data.children = childNodes}resolve(childNodes)}})
}// 处理节点点击事件
const handleNodeClick = (data, node) => {emit('nodeClick', data)
}// 处理删除节点事件
const handleDeleteNode = (node, data) => {emit('deleteNode', { node, data })
}// 处理修改节点事件
const handleEditNode = (nodeData) => {emit('editNode', nodeData)
}// 处理节点展开
const handleNodeExpand = (data, node) => {expandedKeys.value = handleNodeExpandUtil({data,node,expandedKeys: expandedKeys.value,onExpand: (data) => emit('nodeExpand', data)})
}// 处理节点收起
const handleNodeCollapse = (data, node) => {expandedKeys.value = handleNodeCollapseUtil({data,expandedKeys: expandedKeys.value,onCollapse: (data) => emit('nodeCollapse', data)})
}
</script><style scoped>
.tree-container {height: 100%;border: 1px solid #e4e7ed;padding: 10px;overflow: auto;
}::v-deep(.tree-node .delete-btn) {display: none !important;
}::v-deep(.tree-node .edit-btn) {display: none !important;
}::v-deep(.tree-node:hover) {color: skyblue;
}::v-deep(.tree-node:hover .delete-btn) {width: 14px;display: inline-block !important;color: red;margin-left: 5px;transform: translateY(2px);
}::v-deep(.tree-node:hover .edit-btn) {width: 14px;display: inline-block !important;color: rgb(17, 0, 255);margin-left: 5px;transform: translateY(2px);
}.tree-header {border-bottom: 1px solid #e4e7ed;margin-bottom: 10px;
}
</style>
  1. treeUtils.js文件
import { nextTick } from 'vue'/*** 处理树节点展开* @param {Object} options 配置选项* @param {Object} options.data 节点数据* @param {Object} options.node 节点对象* @param {Array} options.expandedKeys 展开节点数组* @param {Function} options.onExpand 展开回调函数* @returns {Array} 更新后的展开节点数组*/
export const handleNodeExpand = ({data,node,expandedKeys,onExpand
}) => {// 如果节点ID不在展开数组中,则添加if (!expandedKeys.includes(data.id)) {expandedKeys.push(data.id)}// 确保父节点也保持展开状态let parent = node.parentwhile (parent && parent.data && parent.data.id) {if (!expandedKeys.includes(parent.data.id)) {expandedKeys.push(parent.data.id)}parent = parent.parent}// 调用展开回调if (onExpand) {onExpand(data)}return expandedKeys
}/*** 处理树节点收起* @param {Object} options 配置选项* @param {Object} options.data 节点数据* @param {Array} options.expandedKeys 展开节点数组* @param {Function} options.onCollapse 收起回调函数* @returns {Array} 更新后的展开节点数组*/
export const handleNodeCollapse = ({data,expandedKeys,onCollapse
}) => {// 从展开数组中移除节点IDconst index = expandedKeys.indexOf(data.id)if (index > -1) {expandedKeys.splice(index, 1)}// 调用收起回调if (onCollapse) {onCollapse(data)}return expandedKeys
}/*** 处理树节点删除后的展开状态* @param {Object} options 配置选项* @param {Object} options.node 要删除的节点* @param {Object} options.data 节点数据* @param {Array} options.treeData 树数据* @param {Function} options.getExpandedKeys 获取展开节点的方法* @param {Function} options.setExpandedKeys 设置展开节点的方法* @param {Function} options.clearCurrentNode 清除当前选中节点的方法* @returns {Promise<void>}*/
export const handleTreeDelete = async ({node,data,treeData,getExpandedKeys,setExpandedKeys,clearCurrentNode
}) => {const parent = node.parentconst children = parent.data.children || parent.dataconst index = children.findIndex((d) => d.id === data.id)// 获取当前展开的节点const currentExpandedKeys = getExpandedKeys()// 删除节点children.splice(index, 1)// 强制刷新treeDatatreeData.value = JSON.parse(JSON.stringify(treeData.value))// 重新设置展开状态await nextTick()// 确保父节点保持展开状态if (parent && parent.data && parent.data.id) {if (!currentExpandedKeys.includes(parent.data.id)) {currentExpandedKeys.push(parent.data.id)}}clearCurrentNode()setExpandedKeys(currentExpandedKeys)return currentExpandedKeys
} 

父组件使用

  1. 导入组件
import TreeView from '@/components/basicComponents/TreeView'
  1. 使用组件
<TreeView ref="treeViewRef":treeData="treeData" :treeProps="customTreeProps" :showDelete="true" :lazy="true":default-expanded-keys="expandedKeys"@nodeClick="handleNodeClick" @deleteNode="handleNodeDelete"@loadChildren="handleLoadChildren"@nodeExpand="handleNodeExpand"@nodeCollapse="handleNodeCollapse"/>
  1. 父组件里使用方法
// 定义treeViewRef
const treeViewRef = ref(null)
const treeData = ref([]) //树数据
const expandedKeys = ref([]) // 添加展开节点的key数组
// 自定义树形配置
const customTreeProps = {children: 'children',  // 子节点字段名label: 'label',        // 使用label字段作为显示文本isLeaf: 'isLeaf'       // 是否为叶子节点字段名
}
const handleLoadChildren = async ({ node, resolve }) => {try {const children = await fetchTreeChildrenData(node.data.id)resolve(children)} catch (error) {console.error('加载子节点失败:', error)resolve([]) // 加载失败时返回空数组}
}
// 获取树子节点数据 懒加载 格式化数据
const fetchTreeChildrenData = async (id = '') => {const { data } = await getZhuangBeiCategory( id )const formattedChildren = data.map(item => ({id: item.id,label: item.label,  // 添加label字段isLeaf: item.sonNum > 0 ? false : true,  // 修正isLeaf的逻辑children: [] // 初始化为空数组,等待后续加载}))if(id) return formattedChildrentreeData.value = formattedChildren
}
//删除子节点
const handleNodeDelete = ({node, data}) => {ElMessageBox.confirm(`<div style="text-align: center;">确定要删除【${data.label}】吗?</div>'提示',{dangerouslyUseHTMLString: true,confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning',}).then(async() => {try{await deleteZhuangBeiCategory(data.id)ElMessage({  type: 'success',  message: '删除成功!'})await handleTreeDelete({node,data,treeData,getExpandedKeys: () => treeViewRef.value.getExpandedKeys(),setExpandedKeys: (keys) => treeViewRef.value.setExpandedKeys(keys),clearCurrentNode: () => treeViewRef.value.clearCurrentNode()})}catch{ElMessage({  type: 'error',  message: '删除失败!'})}}).catch(() => {// 取消了,不做处理})
}
// 处理节点展开
const handleNodeExpand = (data) => {if (!expandedKeys.value.includes(data.id)) {expandedKeys.value.push(data.id)}
}// 处理节点收起
const handleNodeCollapse = (data) => {const index = expandedKeys.value.indexOf(data.id)if (index > -1) {expandedKeys.value.splice(index, 1)}
}// 处理节点点击
const handleNodeClick = (nodeData) => {
}
  • 其他方法比如复选框,编辑不在示例,感兴趣的可以去试试

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

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

相关文章

2025年渗透测试面试题总结-安恒[实习]安全工程师(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 安恒[实习]安全工程师 一面 1. 自我介绍 2. 前两段实习做了些什么 3. 中等难度的算法题 4. Java的C…

网络编程中的直接内存与零拷贝

本篇文章会介绍 JDK 与 Linux 网络编程中的直接内存与零拷贝的相关知识&#xff0c;最后还会介绍一下 Linux 系统与 JDK 对网络通信的实现。 1、直接内存 所有的网络通信和应用程序中&#xff08;任何语言&#xff09;&#xff0c;每个 TCP Socket 的内核中都有一个发送缓冲区…

TransmittableThreadLocal使用场景

&#x1f680; 为什么要用 TransmittableThreadLocal&#xff1f;一文读懂线程上下文传递问题 在 Java Web 开发中&#xff0c;我们经常用 ThreadLocal 来保存每个请求的用户信息&#xff0c;例如 userId。但当我们使用线程池或异步方法&#xff08;如 Async&#xff09;时&am…

Milvus(24):全文搜索、文本匹配

1 全文搜索 全文搜索是一种在文本数据集中检索包含特定术语或短语的文档&#xff0c;然后根据相关性对结果进行排序的功能。该功能克服了语义搜索的局限性&#xff08;语义搜索可能会忽略精确的术语&#xff09;&#xff0c;确保您获得最准确且与上下文最相关的结果。此外&…

2000 元以下罕见的真三色光源投影仪:雷克赛恩Cyber Pro1重新定义入门级投影体验

当性价比遇上技术瓶颈 在 2000元以下的1080P投影仪&#xff0c;单LCD 技术长期主导。而三色光源的DLP和3LCD真1080P都在4000元以上。 单LCD投影为纯白光光源&#xff0c;依赖CF滤光膜导致光效低下&#xff0c;普遍存在" 色彩失真 " 等问题。数据显示&#xff0c;该价…

Maven 下载安装与配置教程

## 1. Maven 简介 Maven 是一个项目管理和构建自动化工具&#xff0c;主要用于 Java 项目。Maven 可以帮助开发者管理项目的构建、报告和文档&#xff0c;简化项目依赖管理。 ## 2. 下载 Maven 1. 访问 Maven 官方网站 [https://maven.apache.org/download.cgi](https://maven.…

C# 深入理解类(从类的外部访问静态成员)

从类的外部访问静态成员 在前一章中&#xff0c;我们看到使用点运算符可以从类的外部访问public实例成员。点运算符由实 例名、点和成员名组成。 就像实例成员&#xff0c;静态成员也可以使用点运算符从类的外部访问。但因为没有实例&#xff0c;所以最常 用的访问静态成员的方…

Java在微服务架构中的最佳实践:从设计到部署

在2025年的云计算和分布式系统时代&#xff0c;微服务架构已成为构建高可扩展、高可用系统的标准方法&#xff0c;广泛应用于电商、金融和物联网等领域。Java凭借其成熟的生态系统、强大的并发支持和跨平台能力&#xff0c;是微服务开发的首选语言。例如&#xff0c;我们的订单…

文件读取漏洞路径与防御总结

文件读取漏洞路径与防御总结 文件读取漏洞允许攻击者通过路径遍历等手段访问未授权的文件。以下是Linux和Windows系统中常见敏感路径的归纳及防御建议&#xff1a; Linux 系统常见敏感路径 系统关键文件&#xff1a; /etc/passwd&#xff1a;用户账户信息&#xff08;可被用来…

react-router基本写法

1. 创建项目并安装所有依赖 npx create-react-app react-router-pro npm i 2. 安装所有的 react router 包 npm i react-router-dom 3. 启动项目 npm run start router/index.js // 创建路由实例 绑定path elementimport Layout from "/pages/Layout"; import…

uni-app 开发HarmonyOS的鸿蒙影视项目分享:从实战案例到开源后台

最近&#xff0c;HBuilderX 新版本发布&#xff0c;带来了令人兴奋的消息——uni-app 现在支持 Harmony Next 平台的 App 开发。这对于开发者来说无疑是一个巨大的福音&#xff0c;意味着使用熟悉的 Vue 3 语法和开发框架&#xff0c;就可以为鸿蒙生态贡献自己的力量。 前言 作…

纯css实现蜂窝效果

<!DOCTYPE html><html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>蜂窝效果</title><style>body {margin: 0…

JAVA EE_HTTP

为什么意气风发的少年&#xff0c;总是听不进去别人的劝解。 ​​​​​​​ ​​​​​​​ ----------陳長生. ❀主页&#xff1a;陳長生.-CSDN博客❀ &#x1f4d5;上一篇&#xff1a;JAVA EE_网络原理_数据链路层-CSDN博客 1.HTTP 1.1.HTTP是什么 H…

存储扇区分配表:NAND Flash与SD NAND(贴片式SD卡)的架构差异

NAND Flash 和 SD 卡&#xff08;SD NAND&#xff09;的存储扇区分配表在原理上有相似之处&#xff0c;但由于二者的结构和应用场景不同&#xff0c;也存在一些差异。 相同点&#xff1a; 基本功能&#xff1a;NAND Flash 和 SD 卡&#xff08;SD NAND&#xff09;的存储扇区分…

界面控件DevExpress WinForms中文教程:Banded Grid View - API

DevExpress WinForms拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForms能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜…

4G物联网模块实现废气处理全流程数据可视化监控配置

一、项目背景 随着工业化进程的加速&#xff0c;工业废气的排放对环境造成了严重影响&#xff0c;废气处理厂应运而生。然而&#xff0c;废气处理厂中的设备众多且分散&#xff0c;传统的人工巡检和数据记录方式效率低下&#xff0c;难以及时发现问题。为了实现对废气处理设备…

Kubernetes控制平面组件:Kubelet详解(四):gRPC 与 CRI gRPC实现

云原生学习路线导航页&#xff08;持续更新中&#xff09; kubernetes学习系列快捷链接 Kubernetes架构原则和对象设计&#xff08;一&#xff09;Kubernetes架构原则和对象设计&#xff08;二&#xff09;Kubernetes架构原则和对象设计&#xff08;三&#xff09;Kubernetes控…

【数据结构】线性表--队列

【数据结构】线性表--队列 一.什么是队列二.队列的实现1.队列结构定义&#xff1a;2.队列初始化函数&#xff1a;3.队列销毁函数&#xff1a;4.入队列函数&#xff08;尾插&#xff09;&#xff1a;5.出队列函数&#xff08;头删&#xff09;&#xff1a;6.取队头元素&#xff…

C语言—再学习(结构体)

一、建立结构体 用户自己建立由不同类型数据组成的组合型的数据结构&#xff0c;它称为结构体。 struct Student { int num; //学号char name[20]; //名字为字符串char sex; //性别int age; //年纪float score; //分数char addr[30]; 地址为字符…

【前端基础】10、CSS的伪元素(::first-line、::first-letter、::before、::after)【注:极简描述】

一、伪元素的作用 选取某个特定的元素。 二、::first-line、::first-letter ::first-line&#xff1a;针对首行文本设置属性 ::first-letter&#xff1a;针对首字母设置属性 三、::before、::after 在一个元素之前&#xff08;::before&#xff09;或者之后&#xff08;…