一个人撸码!之vue3+vite+element-plus后台管理(标签页组件)

一个后台管理常常需要一个标签页来管理已经打开的页面,这里我们单独写一个组件来展示标签页数组。

微信截图_20231127101338.png

该标签页组件只做展示不涉及操作数据。标签页数组可记录已打开的数组,还能定义什么页面需要缓存,是一个重要的功能呢。

首先,建立一个TagList.vue组件,里面代码如下

<template>
<div class="tag-list-cp-container"ref="TagListRef"><div class="left"@wheel="handleScroll"><el-scrollbar ref="ElScrollbarRef"height="100%"><draggable class="scrollbar-container"item-key="sign"v-model="tagListTrans"><template #item="{element}"><div:class="{'item':true,'active':dataContainer.activeSign==element.sign,}"@click="handleClick(element)"@contextmenu.prevent="e=>{handleClickContext(e,element);}"><SvgIconclass="sign icon-sign"v-if="element.showTagIcon && element.iconName":style="'width: 15px;min-width:15px;height: 15px;'":name="element.iconName"></SvgIcon><div class="sign"v-else-if="dataContainer.activeSign==element.sign"></div>{{element.title}}<divv-if="!element.fixed"@click.stop="handleRemove(element)" class="bt"><SvgIcon:style="'width:12px;height:12px;'"name="times"></SvgIcon></div><div v-if="element.isCache"class="cache"></div></div></template></draggable></el-scrollbar></div><div class="bt-list"><div class="bt"@click="handleOptionClick(5)"><SvgIcon:style="'width:15px;height:15px;'"name="redo"></SvgIcon></div><div class="bt"@click="handleToLeft()"><SvgIcon:style="'width:15px;height:15px;'"name="arrow-left"></SvgIcon></div><div class="bt"@click="handleToRight()"><SvgIcon:style="'width:15px;height:15px;'"name="arrow-right"></SvgIcon></div></div><divref="RightOptionRef" class="right"><div@click="()=>{dataContainer.show_1 = !dataContainer.show_1;}"class="bt"><SvgIcon:style="'width:20px;height:20px;'"name="icon-drag"></SvgIcon></div><divv-if="dataContainer.show_1" class="bt-list-container"><div v-if="dataContainer.tagList.length>1"class="item"@click="handleOptionClick(1)"><SvgIcon:style="'width:16px;height:16px;color:#f86464;'"name="times"></SvgIcon>关闭当前标签页</div><div v-if="dataContainer.tagList.length>1"class="item"@click="handleOptionClick(2)"><SvgIcon:style="'width:16px;height:16px;color:#f86464;'"name="borderverticle-fill"></SvgIcon>关闭其他标签页</div><div v-if="dataContainer.tagList.length>1"class="item"@click="handleOptionClick(3)"><SvgIcon:style="'width:16px;height:16px;color:#f86464;'"name="arrow-left"></SvgIcon>关闭左边标签页</div><div v-if="dataContainer.tagList.length>1"class="item"@click="handleOptionClick(4)"><SvgIcon:style="'width:16px;height:16px;color:#f86464;'"name="arrow-right"></SvgIcon>关闭右边标签页</div><div class="item re-bt"@click="handleOptionClick(5)"><SvgIcon:style="'width:16px;height:16px;color:#0072E5;'"name="redo"></SvgIcon>刷新当前标签页</div><div class="item"@click="handleOptionClick(6)"><SvgIcon:style="'width:16px;height:16px;color:#0072E5;'"name="expand-alt"></SvgIcon>视图全屏(Esc键退出)</div></div></div><div v-if="dataContainer.show":style="{'--location-x':`${dataContainer.location.x || 0}px`, '--location-y':`${dataContainer.location.y || 0}px`, }"class="bt-list-container"><div class="item"@click="handleSwitchCache()"><SvgIcon:style="'width:16px;height:16px;'"name="switch"></SvgIcon>切换缓存状态</div><div class="item"@click="handleSwitchFixed()"><SvgIcon:style="'width:16px;height:16px;'"name="nail"></SvgIcon>切换固定状态</div><div class="item re-bt"@click="handleRefresh()"><SvgIcon:style="'width:16px;height:16px;color:#0072E5;'"name="redo"></SvgIcon>刷新此标签页</div><div class="item"@click="handleOptionClick(6)"><SvgIcon:style="'width:16px;height:16px;color:#0072E5;'"name="expand-alt"></SvgIcon>视图全屏</div></div>
</div>
</template>
<script>
/** 标签切换按钮组件* 由外部指定数据*/
import { defineComponent,ref,reactive, computed,onMounted,watch,toRef,onUnmounted,nextTick,
} from "vue";
import SvgIcon from "@/components/svgIcon/index.vue";
import draggable from 'vuedraggable';export default {name: 'TagList',components: {SvgIcon,draggable,},props:{/** * 所显示的标签列表*  *//*** 一个tag例子的属性介绍*/// {//     title:'标签一',  //标签标题//     sign:'/main/index',  //唯一标识//     fullPath:'/main/index',  //跳转地址,完整地址//     isCache:true,  //该标签页面是否缓存//     fixed:false,  //是否固定,不可删除// }tagList:{type:Array,default:()=>{return [];},},/** 当前活动的唯一标识 */activeSign:{type:[Number,String],default:0,},},emits:['onChange','onClick','onRemove','onOptionClick','onSwitchCache','onSwitchFixed','onRefresh',],setup(props,{emit}){const ElScrollbarRef = ref(null);const TagListRef = ref(null);const RightOptionRef = ref(null);const dataContainer = reactive({tagList:toRef(props,'tagList'),activeSign:toRef(props,'activeSign'),show:false,location:{},show_1:false,});const otherDataContainer = {activeItem:null,};/** 用来排序转换的数组,由外部确定是否转换 */const tagListTrans = computed({get(){return dataContainer.tagList;},set(value){emit('onChange',value);},});/** 标签点击事件,向外部抛出 */function handleClick(item){emit('onClick',item);}/** 标签删除事件 */function handleRemove(item){emit('onRemove',item);}/** 操作事件 */function handleOptionClick(type){emit('onOptionClick',type);}/** * 鼠标滚动事件* 横向滚动标签页*  */function handleScroll(e){if(!ElScrollbarRef.value) return;/** shift + 鼠标滚轮可以横向滚动 */if(e.shiftKey) return;let el = ElScrollbarRef.value.wrapRef;let scrollLeft = el.scrollLeft;if(e.deltaY < 0){scrollLeft = scrollLeft - 30;}else{scrollLeft = scrollLeft + 30;}el.scrollLeft = scrollLeft;}/** * 自动滚动到相应标签* 防止标签没在视区*/function autoScroll(){nextTick(()=>{if(!ElScrollbarRef.value) return;let el = ElScrollbarRef.value.wrapRef;let target = el.querySelector('.item.active');if(!target) return;let rect = el.getBoundingClientRect();let rect_1 = target.getBoundingClientRect();if(rect_1.x < rect.x){// 表示在左边遮挡let scroll = rect.x - rect_1.x;el.scrollLeft = el.scrollLeft - scroll - 5;}if((rect_1.x + rect_1.width) > (rect.x + rect.width)){// 表示在右边遮挡let scroll = rect_1.x - (rect.x + rect.width);el.scrollLeft = el.scrollLeft + scroll + rect_1.width + 5;}});}watch(toRef(props,'activeSign'),()=>{autoScroll();});onMounted(()=>{autoScroll();});/** 鼠标右击,展示自定义右击面板 */function handleClickContext(e,item){if(!TagListRef.value) return;let el = TagListRef.value;let el_1 = e.target;let rect = el.getBoundingClientRect();let rect_1 = el_1.getBoundingClientRect();let location = {x:rect_1.x - rect.x,y:rect_1.y - rect.y + rect_1.height,};dataContainer.location = location;dataContainer.show = true;otherDataContainer.activeItem = item;}/** 初始化隐藏事件 */function initHiddenEvent(){function callbackFn(e){dataContainer.show = false;}document.addEventListener('click', callbackFn);onUnmounted(()=>{document.removeEventListener('click', callbackFn);});}initHiddenEvent();/** * 切换缓存状态* 由外部实现*  */function handleSwitchCache(){if(!otherDataContainer.activeItem) return;emit('onSwitchCache',otherDataContainer.activeItem);}/** * 切换固定状态* 由外部实现*  */function handleSwitchFixed(){if(!otherDataContainer.activeItem) return;emit('onSwitchFixed',otherDataContainer.activeItem);}/** * 刷新标签页* 由外部实现*  */function handleRefresh(){if(!otherDataContainer.activeItem) return;emit('onRefresh',otherDataContainer.activeItem);}/** 跳转到右侧 */function handleToRight(){let index = dataContainer.tagList.findIndex(item=>{return item.sign == dataContainer.activeSign;});if(index == -1) return;let target = dataContainer.tagList[index + 1];if(!target) return;handleClick(target);}/** 跳转到左侧 */function handleToLeft(){let index = dataContainer.tagList.findIndex(item=>{return item.sign == dataContainer.activeSign;});if(index == -1) return;let target = dataContainer.tagList[index - 1];if(!target) return;handleClick(target);}/** 初始化隐藏事件 */function initHiddenEvent_1(){function callbackFn(e){if(!RightOptionRef.value) return;if(!e || !e.target) return;if(RightOptionRef.value.contains(e.target)) return;dataContainer.show_1 = false;}document.addEventListener('click', callbackFn);onUnmounted(()=>{document.removeEventListener('click', callbackFn);});}initHiddenEvent_1();return {dataContainer,handleClick,handleRemove,handleOptionClick,tagListTrans,handleScroll,ElScrollbarRef,handleClickContext,TagListRef,handleSwitchCache,handleSwitchFixed,handleRefresh,handleToRight,handleToLeft,RightOptionRef,};},
}
</script>
<style scoped lang="scss">
.tag-list-cp-container {height: 100%;width: 100%;padding: 0;box-sizing: border-box;display: flex;flex-direction: row;justify-content: space-between;align-items: center;color: var(--text-color);>.left{flex: 1 1 0;width: 0;height: 100%;:deep(.el-scrollbar__bar){&.is-horizontal{height: 5px !important;opacity: 0.5;}}:deep(.el-scrollbar__view){height: 100%;}:deep(.scrollbar-container){display: flex;flex-direction: row;justify-content: flex-start;align-items: center;width: fit-content;height: 100%;.item{cursor: pointer;display: flex;flex-direction: row;justify-content: center;align-items: center;padding: 5px 8px;box-sizing: border-box;margin-left: 5px;font-size: 13px;height: 30px;width: max-content;border-radius: 3px;color: #606266;position: relative;transition: all 0.2s;&:last-child{margin-right: 5px;}&.active{background-color: #5240ff30;color: #5240ff;font-weight: bold;box-shadow: inset 0 1px 4px #00000034;// border:1px solid rgb(196, 196, 196);}&:hover{background-color: #5240ff30;color: #5240ff;}>.sign{width: 10px;height: 10px;border-radius: 50%;background-color: #5240ff;margin-right: 5px;&.icon-sign{background-color: transparent;}}>.bt{width: fit-content;height: fit-content;display: flex;flex-direction: row;justify-content: center;align-items: center;margin-left: 5px;}>.cache{width: 30%;max-width: 30px;min-width: 15px;height: 3px;border-radius: 999px;background-color: #5340ff34;position: absolute;bottom: 0;}}}}>.bt-list{display: flex;flex-direction: row;align-items: center;padding: 0 10px;box-sizing: border-box;border-left: 1px solid var(--border-color);box-shadow: inset 0 1px 4px #00000010;height: 100%;>*{margin: 0 10px 0 0;&:last-child{margin: 0;}}>.bt{cursor: pointer;transition: all 0.2s;height: 100%;display: flex;flex-direction: row;align-items: center;justify-content: center;&:hover{color: #5240ff;}}}>.right{width: 40px;height: 100%;border-left: 1px solid var(--border-color);box-sizing: border-box;display: flex;flex-direction: row;justify-content: center;align-items: center;position: relative;box-shadow: inset 0 1px 4px #00000010;>.bt{width: 100%;height: 100%;display: flex;flex-direction: row;justify-content: center;align-items: center;cursor: pointer;transition: all 0.2s;&:hover{color: #5240ff;}}>.bt-list-container{width: max-content;min-width: 150px;position: absolute;z-index: 9;top: calc(100% + 0px);right: 5px;background-color: rgb(255, 255, 255);box-shadow: 0 3px 8px 0 rgba(0, 0, 0, 0.5);padding: 10px 0;box-sizing: border-box;border-radius: 2px;overflow: hidden;transition: opacity 0.2s;font-size: 15px;>.item{cursor: pointer;width: auto;min-width: max-content;transition: all 0.2s;padding: 13px 15px;box-sizing: border-box;display: block;color: #6b7386;text-align: left;display: flex;flex-direction: row;align-items: center;justify-content: flex-start;>*{margin-right: 10px;}&:hover{box-shadow: inset 0 1px 4px #0000001f;background-color: #fef0f0;color: #f56c6c;}&.re-bt{background-color: rgba(194, 224, 255, 0.5);color: #0072E5;&:hover{background-color: rgba(194, 224, 255, 0.5);color: #0072E5;}}}}}>.bt-list-container{width: max-content;min-width: 150px;position: absolute;z-index: 9;top: var(--location-y);left: var(--location-x);background-color: rgb(255, 255, 255);box-shadow: 0 3px 8px 0 rgba(0, 0, 0, 0.5);padding: 10px 0;box-sizing: border-box;border-radius: 2px;overflow: hidden;opacity: 1;transition: opacity 0.2s;font-size: 15px;>.item{cursor: pointer;width: auto;min-width: max-content;transition: all 0.2s;padding: 13px 15px;box-sizing: border-box;display: block;color: #6b7386;text-align: left;display: flex;flex-direction: row;align-items: center;justify-content: flex-start;>*{margin-right: 10px;}&:hover{box-shadow: inset 0 1px 4px #0000001f;background-color: #fef0f0;color: #f56c6c;}&.re-bt{background-color: rgba(194, 224, 255, 0.5);color: #0072E5;&:hover{background-color: rgba(194, 224, 255, 0.5);color: #0072E5;}}}}
}
</style>

这里我们使用了el-scrollbar组件来管理滚动容器,SvgIcon来管理icon的展示,vuedraggable来管理拖拽排序。

该组件接受的数据源为 tagList,activeSign。
tagList:标签的数组。
activeSign:当前活动的标签的sign字符串,每个标签是一个对象,对象有sign唯一标识属性。

组件核心思想:该组件使用外部数据源保证组件灵活性,自身集合多种操作但不处理,抛出给外部处理。只做数据的展示。

源码地址

DEMO

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

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

相关文章

Java(九)(多线程,线程安全,实现线程的方法,线程同步,线程池,并发和并行,线程的六种状态)

目录 多线程 线程 实现线程的方法 方法一:继承Thread父类 方法二:实现Runnable接口 方法三:Callable接口和FutureTask类来实现 Thread方法 线程安全 线程同步 同步代码块 同步方法 Lock锁 线程池 线程池对象的创建方式一: 线程池处理Runnable任务 线程池处理Cal…

BGP综合实验

任务如下&#xff1a; 1.AS1存在两个环回&#xff0c;一个地址为192.168.1.0/24该地址不能在任何协议中宣告 AS3存在两个环回&#xff0c;一个地址为192.168.2.0/24该地址不能在任何协议中宣告&#xff0c;最终要求这两个环回可以互相通讯 2.整个AS2的IP地址为172.16.0.0/16&…

Sentaurus TCAD半导体器件入门常用案例合集

Sentaurus TCAD是用于模拟半导体器件和工艺的工具之一&#xff0c;可以帮助工程师设计电路元件&#xff0c;优化半导体工艺和器件性能。主要功能包括&#xff1a;半导体器件建模&#xff08;用于建立各种半导体器件的物理模型工艺模拟&#xff09;、半导体器件的制造工艺模拟&a…

Debian10安装VMware Tools

一、原系统 首先我在界面按CTRLALTT和CTRLSiftT都没有反应&#xff0c;没关系&#xff0c;我有办法 系统版本 管理员用户 步骤一&#xff1a;打开VMware Tools文件 步骤二、将文件复制到自己熟悉的文件内 步骤三、命令行查看文件是否复制成功存在 步骤四、解压VMware-tools…

宋仕强论道之华强北的商业配套(十三)

宋仕强论道之华强北的商业配套&#xff08;十三&#xff09;&#xff1a;金航标电子萨科微半导体总经理宋仕强先生发布“宋仕强论道”系列视频&#xff0c;分享多年学习、生活和工作经验和感悟&#xff0c;甚至涵盖了文学、艺术、哲学、宗教。这段时间发表的是对华强北&#xf…

程序设计基础中可能出现的简单编程题2(以郑大为主体)

我们在学习编程过程中往往不仅有C语言实验报告&#xff0c;还有程序设计实验报告。程序设计这一科目主要是为了培养我们写代码时的计算思维&#xff0c;养成从问题到代码实现逐步分析&#xff0c;逐步深入的好习惯。前面有一篇文章介绍了部分程序设计实验报告中的编程题&#x…

第二十章 -----多线程

20.1 线程简介 计算机完全可以将多种活动同时进行&#xff0c;这种思想在java中称为并发&#xff0c;将并发完成的每一件事情称为线程 线程的特点&#xff1a; 极小的单位 一个进程有很多个线程 线程共享进程的资源 20.2 创建线程 20.2.1 继承Thread类 Thread类是Java.l…

Python实现视频人脸检测识别功能

目录 一、引言 二、人脸检测识别技术概述 三、Python实现视频人脸检测识别功能的步骤 1、安装相关库和工具 2、加载视频文件 3、人脸检测和识别 4、保存视频结果 四、实验结果和讨论 五、结论 一、引言 在当今社会&#xff0c;人脸检测识别技术在安全监控、人机交互、…

全网日志智能聚合及问题根因分析

1 日志关联分析的挑战 随着各行各业数字化转型的不断深入&#xff0c;网络承载了人们日常生活所需的政务、金融、娱乐等多方面的业务系统&#xff0c;已经成为影响社会稳定运行、关系国计民生的重要基础设施资源。哪怕网络发生及其微小的故障&#xff0c;也可能带来难以估量的…

Java基础之原码,反码,补码,位运算符

文章目录 前言一、二进制在运算中介绍二、原码&#xff0c;反码&#xff0c;补码&#xff08;针对有符号的&#xff09;三、位运算符按位与&按位或 |按位异或 ^按位取反 ~算术右移>>算术左移<<逻辑右移>>> 总结 前言 原码&#xff0c;反码&#xff0…

【shell】文本三剑客之sed详解

目录 一、sed简介&#xff08;行编辑器&#xff09; 二、基本用法 三、sed脚本格式&#xff08;匹配地址 脚本命令&#xff09; 1、不给地址&#xff0c;那么就是针对全文处理 2、单地址&#xff0c;表示#&#xff0c;指定的行&#xff0c;$表示最后一行&#xff0c;/pattt…

牛客算法题 HJ100 等差数列 golang语言实现

算法题目 HJ100 等差数列 描述 等差数列 2&#xff0c;5&#xff0c;8&#xff0c;11&#xff0c;14。。。。 &#xff08;从 2 开始的 3 为公差的等差数列&#xff09; 输出求等差数列前n项和数据范围&#xff1a; 1 ≤ &#xfffd; ≤ 10001≤n≤1000 输入描述&#xff…

python与机器学习1,机器学习的一些基础知识概述(完善ing)

目录 1 AI ,ML,DL,NN 等等概念分类 1.1 人工智能、机器学习、深度学习、神经网络之间的关系&#xff1a; 1.2 人工智能的发展 2 ML机器学习的分类&#xff1a;SL, USL,RL 2.1 机器学习的分类 2.2 具体的应用举例 2.3 数据分类 3 关于阈值θ和偏移量b的由来 4 不同的激…

网站定制开发对企业的好处|软件app小程序搭建

网站定制开发对企业的好处|软件app小程序搭建 在当今数字化的时代&#xff0c;拥有一个专属于自己企业的网站已经成为了一种趋势。而与此同时&#xff0c;网站定制开发作为一种针对企业需求量身定制的解决方案&#xff0c;也越来越受到企业的关注和青睐。那么&#xff0c;网站定…

SSL证书实惠品牌——JoySSL

随着互联网的普及和发展&#xff0c;网络安全问题日益严重。为了保护网站数据的安全&#xff0c;越来越多的网站开始使用SSL证书。JoySSL证书作为一款高性价比的SSL证书&#xff0c;受到了广泛的关注和好评。 目前市面上主流的证书基本上都是国外证书&#xff0c;也就是说你在验…

HarmonyOS 后台任务管理开发指南上线!

为什么要使用后台任务&#xff1f;开发过程中如何选择合适的后台任务&#xff1f;后台任务申请时存在哪些约束与限制&#xff1f; 针对开发者使用后台任务中的疑问&#xff0c;我们上线了概念更明确、逻辑结构更清晰的后台任务开发指南&#xff0c;包含具体的使用场景、详细的开…

js实现鼠标拖拽

目录 css代码 html代码 js代码 完整代码 效果图&#xff1a; 需求&#xff1a; 鼠标在图片内按下时 图片可以跟随盒子动 鼠标弹起图片停下来 如果图片在box的盒子里面时鼠标弹起了 就把图片展示在box里面 并且让图片回到起始位置 css代码 .div {width: 100px;height: 10…

五分钟 k8s 实战-应用探针

Probe.png 今天进入 kubernetes 的运维部分&#xff08;并不是运维 kubernetes&#xff0c;而是运维应用&#xff09;&#xff0c;其实日常我们大部分使用 kubernetes 的功能就是以往运维的工作&#xff0c;现在云原生将运维和研发关系变得更紧密了。 今天主要讲解 Probe 探针相…

C语言——I /深入理解指针(三)

一、字符指针变量 在指针的类型中我们知道有⼀种指针类型为字符指针 char* ; ⼀般使⽤: int main() { char ch w; char *pc &ch; *pc w; return 0; } 还有⼀种使⽤⽅式如下&#xff1a; int main() { const char* pstr "hello bit.";//这⾥是把⼀个字…

jquery 地址四级联级显示 不默认选择

代码效果 <body class"bgca"><img src"./files/joinTooBg.png" style"width: 100%;object-fit: cover;" alt""><!--填写申请资料--><section><div class"zi-liao"><h3 class"zong-h…