一个人撸码!之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.hqwc.cn/news/234807.html

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

相关文章

C语言——写一个简单函数,找两个数中最大者

#include <stdio.h>int max( int a, int b ) { return a>b ? a:b; }int main() { int a, b;printf("输入两个数:\n");scanf("%d %d", &a, &b);printf("max %d\n", max(a, b));return 0; }输出结果&#xff1a;

算法:Java计算二叉树从根节点到叶子结点的最大路径和

要求从根节点到叶子结点的最大路径和&#xff0c;可以通过递归遍历二叉树来实现。对于二叉树中的每个节点&#xff0c;我们都可以考虑包含该节点的最大路径和。在递归的过程中&#xff0c;我们需要不断更新全局最大路径和。 具体的思路 递归函数设计&#xff1a; 设计一个递归函…

用 LangChain 搭建基于 Notion 文档的 RAG 应用

如何通过语言模型查询 Notion 文档&#xff1f;LangChain 和 Milvus 缺一不可。 在整个过程中&#xff0c;我们会将 LangChain 作为框架&#xff0c;Milvus 作为相似性搜索引擎&#xff0c;用二者搭建一个基本的检索增强生成&#xff08;RAG&#xff09;应用。在之前的文章中&a…

Flutter使用flutter_gen管理资源文件

pub地址&#xff1a; https://pub.dev/packages/flutter_gen 1.添加依赖 在你的pubspec.yaml文件中添加flutter_gen作为开发依赖 dependencies:build_runner:flutter_gen_runner: 2.配置pubspec.yaml 在pubspec.yaml文件中&#xff0c;配置flutter_gen的参数。指定输出路…

京东秒杀之秒杀实现

1 登录判断 用户在未登录状态下可以查看商品列别以及秒杀商品详情&#xff0c;但不可以在未登录状态进行秒杀商品的操作&#xff0c;当用户点击开始秒杀时&#xff0c;进行登陆验证 <!DOCTYPE html> <head><title>商品详情</title><meta http-eq…

three.js结合vue

作者&#xff1a;baekpcyyy&#x1f41f; 1.搭建环境 ps&#xff1a;这里要按照node.js在之前有关vue搭建中有介绍 新建文件夹并在vsc终端中打开 1.输入vite创建指令 npm init vitelatest然后我们cd进入刚才创建的目录下 npm install安装所需依赖 npm run dev启动该项目 …

【Java案例】用户登录注册

案例介绍&#xff1a; 编写程序实现简单的用户登录注册功能。程序包含以下4个功能&#xff1a; &#xff08;1&#xff09;登录功能&#xff0c;用户输入正确的账号密码登录成功&#xff1b; &#xff08;2&#xff09;注册功能&#xff0c;输入用户名和密码进行注册&#x…

【C++】异常处理 ② ( 异常捕获类型 | 异常捕获机制 - 严格匹配异常类型 | 未知异常捕获 - 不知道异常类型 )

文章目录 一、异常捕获机制 - 严格匹配异常类型1、异常捕获机制 - 严格匹配异常类型2、代码示例 - 异常捕获严格匹配异常类型 二、异常捕获机制 - 未知异常捕获1、未知异常捕获 - 不知道异常类型2、代码示例 - 未知异常捕获 一、异常捕获机制 - 严格匹配异常类型 1、异常捕获机…

[vue3] 使用 vite 创建vue3项目的详细流程

一、vite介绍 Vite&#xff08;法语意为 “快速的”&#xff0c;发音 /vit/&#xff0c;发音同 “veet”) 是一种新型前端构建工具&#xff0c;能够显著提升前端开发体验&#xff08;热更新、打包构建速度更快&#xff09;。 二、使用vite构建项目 【学习指南】学习新技能最…

3dsMax插件Datasmith Exporter安装使用方法

3dsMax插件Datasmith Exporter安装使用方法 某些文件格式无法用Datasmith直接导入虚幻引擎&#xff0c;这些数据必须先被转换为Datasmith能够识别的文件格式。Datasmith Exporter插件就可以帮助您的软件导出可以被Datasmith导入虚幻引擎的.udatasmith格式文件。 在开始使用虚幻…

css 字体倾斜

css 字体倾斜 //左右倾斜 transform: skew(40deg, 0deg);//上下倾斜 transform: skew(0deg, 16deg);

在Rust中编写自动化测试

1.摘要 Rust中的测试函数是用来验证非测试代码是否是按照期望的方式运行的, 测试函数体通常需要执行三种操作:1.设置任何所需的数据或状态;2.运行需要测试的代码;3.断言其结果是我们所期望的。本篇文章主要探讨了Rust自动化测试的几种常见场景。 2.测试函数详解 在Rust项目工…