当前位置: 首页 > news >正文

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.xdnf.cn/news/450253.html

相关文章:

  • js对象原型,原型链
  • 制作一款打飞机游戏48:敌人转向
  • 嵌入式学习笔记 D20 :单向链表的基本操作
  • 3DMAX脚本病毒Spy CA查杀方法
  • 计算机网络笔记(二十八)——4.10软件定义网络SDN简介
  • 【0415】Postgres内核 释放指定 memory context 中所有内存 ④
  • 5.14 BGP作业
  • Linux操作系统实战:进程创建的底层原理(转)
  • 朱老师, 3518e系列,第三季
  • 【Python】杂乱-[代码]Python 替换字符串中相关字符的方法
  • 容器安全-核心概述
  • OpenCV人脸识别LBPH算法原理、案例解析
  • Codeforces Round 1003 (Div. 4)
  • 分布式一致性协议Raft
  • 动物乐园-第16届蓝桥第5次STEMA测评Scratch真题第5题
  • 11-SGM41299-TEC驱动芯片--40℃至+125℃-3A
  • 1. Go 语言环境安装
  • 数据清洗的艺术:如何为AI模型准备高质量数据集?
  • 《Python星球日记》 第71天:命名实体识别(NER)与关系抽取
  • 拓展篇、github的账号创建
  • Oracle中的select1条、几条、指定范围的语句
  • 【证书与信任机制​】证书透明度(Certificate Transparency):如何防止恶意证书颁发?​​
  • 【1000以内具有12个以上因子的整数并输出它的因子】2021-12-27
  • 如何在Mac电脑上的VScode去配置C/C++环境
  • 生成式AI:人工智能的新纪元
  • 请求内存算法题
  • 综述:拓扑材料的热磁性质
  • WordPress 和 GPL – 您需要了解的一切
  • 【leetcode】349. 两个数组的交集
  • WindTerm终端工具功能与优缺点分析