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

Vue3 + Element Plus 实现可搜索、可折叠、可拖拽的部门树组件

Vue3 + Element Plus 实现可搜索、可折叠、可拖拽的部门树组件

在后台管理系统中,左侧树型菜单是常见的 UI 组件。本文将手把手教你使用 Vue3 + Element Plus 来实现一个美观、可搜索、可折叠、可拖拽的部门树组件


功能特点

我们的树组件具有以下功能:

  1. 可搜索:输入部门名称实时过滤树节点。
  2. 选中高亮:点击节点高亮显示,选中状态可切换。
  3. 可折叠/展开:可以收起左侧树,也可以展开。
  4. 可拖拽调整宽度:鼠标拖拽可以改变树的宽度。
  5. 美观风格:简洁清爽,箭头和叶子图标统一。

组件目录结构

假设组件命名为 DeptTree.vue,父组件使用 el-container 布局:


---## 代码实现下面是完整的 `DeptTree.vue` 代码:```vue
<!-- DeptTree.vue -->
<template><el-aside :style="{ width: leftWidth + 'px', height: props.height }" class="left-pane"><!-- 搜索 --><el-inputv-if="props.showFilter"v-model="deptName"placeholder="请输入部门名称"clearablesize="small"prefix-icon="Search"class="filter-tree"/><!----><el-treeref="deptTreeRef":data="deptOptions":props="{ label: 'name', children: 'children' }"node-key="id"highlight-current:default-expanded-keys="expandedKeys"@node-click="onNodeClick":filter-node-method="filterNode":expand-on-click-node="false"><template #default="{ node }"><divclass="tree-node":class="{ 'is-current': node.isCurrent }"@click.stop="selectNode(node)"><el-icon v-if="node.children && node.children.length" class="arrow"><component :is="node.expanded ? ArrowDown : ArrowRight" /></el-icon><el-icon v-else class="leaf"><Tickets /></el-icon><span class="label">{{ node.label }}</span></div></template></el-tree></el-aside><!-- 拖拽栏 --><div class="resize-bar" @mousedown="startResize"><el-icon class="collapse-icon" @click.stop="toggleCollapse"><component :is="leftWidth === 0 ? ArrowRight : ArrowLeft" /></el-icon></div>
</template><script setup>
import { ref, defineProps, defineEmits, watch } from "vue";
import { ArrowLeft, ArrowRight, ArrowDown, Tickets, Search } from "@element-plus/icons-vue";const props = defineProps({deptOptions: Array,leftWidth: { type: Number, default: 280 },height: { type: String, default: "100%" },showFilter: { type: Boolean, default: true },defaultExpand: { type: Boolean, default: false },
});
const emit = defineEmits(["node-click", "update:leftWidth"]);const deptName = ref("");
const deptTreeRef = ref(null);
const leftWidth = ref(props.leftWidth);
const expandedKeys = ref([]);// 默认展开一级
watch(() => props.deptOptions, val => {if (val?.length) expandedKeys.value = val.map(i => i.id);
}, { immediate: true });// 过滤节点
const filterNode = (val, data) => !val || data.name.includes(val);
watch(deptName, val => deptTreeRef.value?.filter(val));// 拖拽
let startX = 0, isResizing = false;
const startResize = e => {isResizing = true;startX = e.clientX;document.addEventListener("mousemove", updateResize);document.addEventListener("mouseup", stopResize);
};
const updateResize = e => {if (!isResizing) return;leftWidth.value += e.clientX - startX;startX = e.clientX;
};
const stopResize = () => {isResizing = false;document.removeEventListener("mousemove", updateResize);document.removeEventListener("mouseup", stopResize);
};// 折叠/展开
const toggleCollapse = () => {leftWidth.value = leftWidth.value === 0 ? 280 : 0;emit("update:leftWidth", leftWidth.value);
};// 节点选中逻辑
const clearCurrent = nodes => nodes?.forEach(n => {n.isCurrent = false;n.children && clearCurrent(n.children);
});
const selectNode = node => {clearCurrent(props.deptOptions);node.isCurrent = true;emit("node-click", node);
};
const onNodeClick = node => selectNode(node);
</script><style scoped lang="scss">
.left-pane { background:#fff; overflow:hidden; border-right:1px solid #eee; padding:12px; }
.filter-tree { margin-bottom:12px; }
.tree-node {display:flex; align-items:center; padding:4px 8px; border-radius:4px; cursor:pointer; transition: all .2s;&.is-current { background:#f0f5ff; color:var(--el-color-primary); }&:hover { background:#f5f7fa; }.arrow, .leaf { font-size:14px; margin-right:6px; }.label { flex:1; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; font-size:14px; }
}
.resize-bar { width:8px; cursor:ew-resize; background:#f0f2f5; display:flex; align-items:center; justify-content:center; }
.collapse-icon { font-size:20px; color:#aaa; cursor:pointer; padding:4px; }
</style>

功能分析

1. 树结构渲染

  • 使用 el-tree 渲染数据。
  • props 指定标签和子节点。
  • 使用 node-key 保证每个节点唯一。
  • 通过 highlight-current 高亮选中节点。

2. 搜索过滤

  • 使用 filter-node-method 实现关键字过滤。
  • 搜索框绑定 deptName,实时调用 treeRef.filter(val)

3. 节点选中高亮

  • 点击节点触发 selectNode
  • 使用递归函数清空其他节点的 isCurrent,保证只有当前节点高亮。

4. 可折叠与拖拽

  • 左侧宽度 leftWidth 可通过拖拽调整。
  • 点击折叠图标可以收起或展开树。
  • 鼠标拖动监听 mousemove,动态更新宽度。

5. 样式优化

  • 父容器 el-aside 背景白色,树节点圆角和 hover 高亮。
  • 箭头和叶子节点图标统一,界面简洁现代。
  • 选中节点颜色与 Element Plus 主色调一致。

6. 父组件使用示例

<template><el-container style="height: 100vh"><DeptTree:deptOptions="deptOptions":leftWidth="leftWidth"ref="deptTreeRef"@node-click="handleNodeClick"/><el-main><h3>右侧内容区域</h3></el-main></el-container>
</template><script setup>
import { ref } from "vue";
import DeptTree from "./components/DeptTree.vue";const deptOptions = ref([{ id: 1, name: "技术部", children: [{ id: 11, name: "前端组" }, { id: 12, name: "后端组" }] },{ id: 2, name: "市场部" },
]);
const leftWidth = ref(280);const handleNodeClick = node => console.log("点击节点", node);
</script>

总结与效果

通过 Vue3 + Element Plus,我们实现了一个可搜索、可选中高亮、可折叠、可拖拽的部门树组件

组件特点:

  • 逻辑清晰,代码优雅
  • 样式现代美观
  • 可复用性强,可快速应用到后台系统

效果展示

  1. 搜索过滤效果
    输入部门名称即可实时过滤树节点,便于快速定位目标部门。

  2. 选中高亮效果
    点击节点高亮显示,之前选中的节点高亮会自动取消,保证视觉清晰。

  3. 折叠与展开效果
    点击左侧折叠按钮即可收起或展开树,界面简洁灵活。

  4. 拖拽调整宽度效果
    鼠标拖拽左侧树边缘可以调整宽度,适应不同屏幕布局。

在这里插入图片描述

http://www.lryc.cn/news/621513.html

相关文章:

  • 【Redis】Redis典型应用——缓存
  • Redis 官方提供免费的 30 MB 云数据库
  • AI客户维护高效解决方案
  • [Chat-LangChain] 前端用户界面 | 核心交互组件 | 会话流管理
  • 制造装配、仓储搬运、快递装卸皆适配!MinkTec 弯曲形变传感器助力,让人体工学改变劳动生活
  • 测试工程师应当具备的能力
  • 专题三_二分_在排序数组中查找元素的第一个和最后一个位置
  • 手机分身空间:空间自由切换,一机体验双重生活!
  • FCC认证三星XR头显加速全球量产,微美全息AI+AR技术引领智能眼镜硬件创新
  • FreeRTOS多核支持
  • PaddleNLP进行Bart文本摘要训练
  • JavaScript 流程控制语句详解
  • 稳定且高效:GSPO如何革新大型语言模型的强化学习训练?
  • SpringCloud -- Nacos详细介绍
  • 跨网络 SSH 访问:借助 cpolar 内网穿透服务实现手机远程管理 Linux
  • 搭建前端开发环境 安装nvm nodejs pnpm 配置环境变量
  • Spark03-RDD01-简介+常用的Transformation算子
  • SQL:生成日期序列(填补缺失的日期)
  • 完整技术栈分享:基于Hadoop+Spark的在线教育投融资大数据可视化分析系统
  • 【Docker】关于hub.docker.com,无法打开,国内使用dockers.xuanyuan.me搜索容器镜像、查看容器镜像的使用文档
  • 关于截屏时实现游戏暂停以及本地和上线不同步问题
  • Java研学-SpringCloud(四)
  • Flink Stream API 源码走读 - keyBy
  • 转换一个python项目到moonbit,碰到报错输出:编译器对workflow.mbt文件中的类方法要求不一致的类型注解,导致无法正常编译
  • Vue响应式系统在超大型应用中的性能瓶颈
  • 中年海尔,是时候押注新方向了
  • 训练大模型的前提:数据治理工程:从原始数据到高质量语料的系统化治理实践
  • 抽奖程序web程序
  • 小迪安全v2023学习笔记(六十二讲)—— PHP框架反序列化
  • 实战 AI8051U 音视频播放:USART-SPI→DMA-P2P→SPI+I2S 例程详解