vue3实现系统tab标签页面切换

功能:

  • 支持刷新当前、关闭其他、关闭全部、关闭当前
  • 支持打开多个相同path不同路由参数的页面,将fullPath作为路由页面唯一值

UI组件:

        使用的是element-plus中的el-tab组件,结构目录如下

        

代码实现: 

下面是 TagsView中的 index.vue

<template><div class="m-tags-view" ref="containerDom"><div class="tags-view"><el-tabsv-model="activeTabsValue"@contextmenu.prevent.stop="openMenu($event)"type="card"@tab-click="tabClick"@tab-remove="removeTab"><!-- && item.meta.affix --><el-tab-panev-for="item in routerTabList":key="item.fullPath":path="item.fullPath":label="item.title":name="item.fullPath":closable="!(item.meta && routerTabList.length == 1)"><template #label>{{ item.meta.title }}</template></el-tab-pane></el-tabs></div><div class="right-btn"><MoreButton ref="moreBtnRef" /></div></div>
</template>
<script lang="ts" setup>
import { computed, watch, ref, onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import { TabsPaneContext } from "element-plus";
import MoreButton from "./components/MoreButton.vue";
import { useRouterTab } from "@/store/routeTab";
const route = useRoute();
const router = useRouter();
const routerTabList: any = computed(() => useRouterTab().routerTabList);const addTags = () => {const { name } = route;if (name === "login") {return;}if (name) {useRouterTab().addView(route);}return false;
};
const moreBtnRef = ref();
const containerDom = ref();
const openMenu = (e: any) => {let tabFullPath;if (e.srcElement.id) {tabFullPath = e.srcElement.id.split("-")[1];}moreBtnRef.value.open(tabFullPath);
};
let affixTags = ref([]);
function filterAffixTags(routes: any, basePath = "/") {let tags: any = [];routes.forEach((route: any) => {if (route.meta && route.meta.affix) {tags.push({fullPath: basePath + route.fullPath,path: basePath + route.path,name: route.name,meta: { ...route.meta },});}if (route.children) {const tempTags = filterAffixTags(route.children, route.path);if (tempTags.length >= 1) {tags = [...tags, ...tempTags];}}});return tags;
}
const initTags = () => {let routesNew = routerTabList.value;let affixTag = (affixTags.value = filterAffixTags(routesNew));for (const tag of affixTag) {if (tag.name) {useRouterTab().addRouterList(tag);}}
};
onMounted(() => {initTags();addTags();
});
watch(route, () => {addTags();
});
const activeTabsValue = computed({get: () => {return useRouterTab().activeTabsValue;},set: (val) => {useRouterTab().setTabsMenuValue(val);},
});
const tabClick = (tabItem: TabsPaneContext) => {let path = tabItem.props.name as string;router.push(path);
};const isActive = (path: any) => {return path === route.fullPath;
};
const removeTab = async (activeTabPath: string) => {await useRouterTab().delView(activeTabPath, isActive(activeTabPath));
};
</script>
<style lang="scss" scoped>
:deep(.el-tabs__item) {min-width: 100px !important;
}
.m-tags-view {display: flex;justify-content: space-between;align-items: center;padding-left: 10px;padding-right: 10px;background: white;.right-btn {height: 100%;flex-shrink: 0;}
}
.tags-view {flex: 1;overflow: hidden;box-sizing: border-box;
}.tags-view {.el-tabs--card :deep(.el-tabs__header) {box-sizing: border-box;height: 40px;padding: 0 10px;margin: 0;}:deep(.el-tabs) {.el-tabs__nav {border: none;}.el-tabs__header .el-tabs__item {border: none;color: #cccccc;}.el-tabs__header .el-tabs__item.is-active {color: blue;border-bottom: 2px solid blue;}}
}
</style>

右键显示功能菜单,写成了一个组件,在上面文件中进行引用

moreButton.vue

<template><transition name="el-zoom-in-top"><ul v-show="visible" :style="getContextMenuStyle" class="contextmenu"><li @click="refresh"><el-icon :size="14"><Refresh /></el-icon> <span>刷新当页</span></li><li @click="closeCurrentTab"><el-icon :size="14"><FolderRemove /></el-icon> <span>关闭当前</span></li><li @click="closeOtherTab"><el-icon :size="14"><Close /></el-icon> <span>关闭其他</span></li><!-- <li @click="closeAllTab"><el-icon :size="14"><FolderDelete /></el-icon> <span>关闭所有</span></li> --></ul></transition>
</template>
<script lang="ts" setup>
import { computed, type CSSProperties } from "vue";
import { useRouter, useRoute } from "vue-router";
import { useRouterTab } from "@/store/routeTab";
const router = useRouter();
const route = useRoute();
const visitedViews: any = computed(() => useRouterTab().visitedViews);
const { x, y } = useMouse();
let visible = ref(false);
const left = ref(0);
const top = ref(0);
const currentPath = ref("");
const open = (fullPath: string) => {visible.value = true;currentPath.value = fullPath;left.value = x.value;top.value = y.value;
};
const closeMenu = () => {visible.value = false;
};const getContextMenuStyle = computed((): CSSProperties => {return { left: left.value + 20 + "px", top: top.value + 10 + "px" };
});
watch(visible, (value) => {if (value) {document.body.addEventListener("click", closeMenu);} else {document.body.removeEventListener("click", closeMenu);}
});
defineExpose({ open });
// 关闭当前
const closeCurrentTab = (event: any) => {useRouterTab().toLastView(currentPath.value);useRouterTab().delView(currentPath.value);
};
// 关闭其他
const closeOtherTab = async () => {useRouterTab().delOtherViews(currentPath.value, route.fullPath);
};
// 刷新当前
const refresh = () => {useRouterTab().setReload(currentPath.value);
};
// 关闭所有 去模型管理
const closeAllTab = async () => {await useRouterTab().delAllViews();useRouterTab().goHome();
};
</script>
<style lang="scss" scoped>
.contextmenu {margin: 0;background: #fff;z-index: 3000;position: absolute;list-style-type: none;padding: 5px 0;border-radius: 4px;font-size: 12px;font-weight: 400;color: #333;box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);li {margin: 0;padding: 7px 16px;cursor: pointer;display: flex;align-items: center;span {padding-left: 4px;}&:hover {background: #eee;color: #4248f4 !important;}}
}
.more {background-color: blue;color: white;.tags-view-item {display: flex;align-items: center;}
}
</style>
@/store/tagsView

用到的store里面的ts

routeTab.ts

import router from "@/router";
import { defineStore } from "pinia";
import { useRequest } from "@/hooks/use-request";
import { messageError, messageConfirm } from "@/utils/element-utils/notification-common";
import { isBtnPermission } from "@/utils/permission";
import { ElMessageBox } from "element-plus";export const useRouterTab = defineStore("routerList", {state: () => ({routerTabList: [] as any,isReload: true,activeTabsValue: "/",}),getters: {showTabList(state: any) {return state.routerTabList.filter((e: any) => !e.hidden);},},actions: {setTabsMenuValue(val: any) {this.activeTabsValue = val;},addView(view: any) {this.addRouterList(view);},addRouterList(view: any) {this.setTabsMenuValue(view.fullPath);if (this.routerTabList.some((v: any) => v.fullPath === view.fullPath)) return; // 不重复添加if (JSON.stringify(view.meta) != "{}") {this.routerTabList.push(Object.assign({}, view));}},// 删除当前,过滤掉本身的标签delView(activeTabPath: any, flag?: boolean) {return new Promise((resolve) => {this.routerTabList = this.routerTabList.filter((v: any) => {return v.fullPath !== activeTabPath || v.meta.affix;});this.toLastView(activeTabPath)resolve({routerTabList: [...this.routerTabList],});});},// 关闭标签后,跳转标签toLastView(activeTabPath: any) {const index = this.routerTabList.findIndex((item: any) => item.fullPath === activeTabPath);const nextTab: any = this.routerTabList[index + 1] || this.routerTabList[index - 1];if (!nextTab) return;router.push(nextTab.fullPath);this.addRouterList(nextTab);},// 刷新setReload(fullPath: any) {const index = this.routerTabList.findIndex((item: any) => item.fullPath === fullPath);this.routerTabList[index].code = Date.now(); //用于改变keep-alive中的key值实现刷新},clearVisitedView() {this.delAllViews();},// 删除全部delAllViews() {return new Promise((resolve) => {this.routerTabList = this.routerTabList.filter((v: any) => v.meta.affix);resolve([...this.routerTabList]);});},// 删除其他delOtherViews(fullPath: any, currentPath: any) {// affix是用来设置路由表中的meta,表示该路由是否是固定路由,固定则不删const op = () => {this.routerTabList = this.routerTabList.filter((item: any) => {return item.fullPath === fullPath || item.meta.affix;});router.push(fullPath);};this.isDelViews("other", op);},// 判断是否可以关闭全部和关闭其他isDelViews(type: string, callback: () => void) {if (useRouterTab().SaveRoutes?.length != 0) {messageConfirm(`存在未保存的模型建模,无法${type == "all" ? "全部关闭" : "关闭其他"},是否继续?`,{confirmButtonText: `${type == "all" ? "全部关闭" : "关闭其他"}`,cancelButtonText: `取消`,},() => {callback();});} else {callback();}},goHome() {this.activeTabsValue = "/";router.push({ path: "/" }); //semantic/manage/model},},// persist: {//   enabled: true,//   strategies: [//     {//       key: "routerTabList1",//       storage: sessionStorage,//       paths: ["routerTabList"],//     },//   ],// },
});

 注意:

        由于本系统的keepAlive实现没有用页面的name,而是用v-if条件判断哪些页面缓存就套个keepalive的壳,否则正常展示,因此,上面刷新的逻辑原理,需要配合下面的key值设置

 如果,你们是通过设置页面name,结合include实现的缓存,就像下面这种形式,那么可以用v-if实现刷新

 刷新的逻辑就是,在store里面存一个isReload的变量。通过设置true false来实现刷新。

 // 刷新setReload() {this.isReload = falsesetTimeout(() => {this.isReload = true}, 50)},

有问题评论区留言或私信哦~ 

 

        

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

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

相关文章

缺失ffmpeg.dll要用什么修复方法?快速恢复丢失的ffmpeg.dll文件

多媒体软件用户常常会遭遇一个提示&#xff1a;系统无法找到ffmpeg.dll文件。这类情况经常在启动视频编辑软件、流媒体播放应用或其他音视频处理工具时出现&#xff0c;导致相关程序无法正确加载和执行。ffmpeg.dll是一种关键的动态链接库文件&#xff0c;负责处理复杂的视频和…

【实战教程】一键升级CentOS 7.9.2009至OpenSSL 1.0.2u:加固你的Linux服务器安全防线!

文章目录 【实战教程】一键升级CentOS 7.9.2009至OpenSSL 1.0.2u&#xff1a;加固你的Linux服务器安全防线&#xff01;一、 背景二、 升级步骤2.1 检查 OpenSSL 版本2.2 安装 OpenSSL 依赖包2.3 下载 OpenSSL 的新版本2.4 解压缩下载的文件2.5 编译并安装 OpenSSL2.5.1 切换到…

linux系统编程:网络通信

1.网络 1.粘包 tcp特点 面向连接 字节流&#xff08;TCP 将数据视为连续的字节流&#xff0c;没有明确的消息边界。会发生粘包问题。 避免粘包 特殊分隔符&#xff1a;在消息间加入特殊的分隔符&#xff08;如换行符或其他特殊字符&#xff09;&#xff0c;接收方根据分…

大模型时代的AI应用开发,可以不用,但必须会

成熟的“格子衫”和年轻的“脸庞”&#xff0c;与开发者有关的大会总是少不了这两种元素&#xff0c;Create 2024百度AI开发者大会也不例外。 过去几十年&#xff0c;层出不穷的编程语言、框架等新技术&#xff0c;把一代又一代年轻的脸庞&#xff0c;塑造为成熟的格子衫&…

技术前沿:WebRTC与H.265编码的兼容性挑战与应对策略

WebRTC&#xff08;Web Real-Time Communication&#xff09;是一种支持网页浏览器进行实时语音通话、视频聊天以及P2P文件共享的技术。然而&#xff0c;标准的WebRTC API在大多数浏览器中默认并不支持H.265&#xff08;也称为HEVC&#xff0c;高效视频编码&#xff09;编码。这…

3D打印的模具镶件性能究竟如何?

随着模具制造业的快速发展&#xff0c;3D打印技术凭借其独特优势&#xff0c;在模具随形水路设计、异形模具制造及模具排气结构优化等方面大放异彩&#xff0c;赢得了注塑、压铸等行业的广泛关注。然而&#xff0c;新技术带来的材料变革让不少人对3D打印模具的性能持观望态度—…

超全大模型训练流程,教你如何训练自己的大模型

“大模型的核心主要有两部分&#xff0c;一是训练数据&#xff0c;二是机器学习模型。” 现在大模型发展得如火如荼&#xff0c;但是没有学过人工智能技术的开发者&#xff0c;只会调用其接口&#xff0c;但不清楚怎么训练一个大模型。 今天就简单介绍一下自己的理解&#xf…

算法日记day 46(单调栈之下一个更大元素|柱状图中最大图形)

一、下一个更大元素1 题目&#xff1a; nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。 给你两个 没有重复元素 的数组 nums1 和 nums2 &#xff0c;下标从 0 开始计数&#xff0c;其中nums1 是 nums2 的子集。 对于每个 0 …

【C语言】进程和线程详解

目录 C语言进程和线程详解1. 进程和线程的对比2. 进程的基本概念2.1 进程的定义2.2 进程的特点2.3 进程的生命周期 3. 进程管理3.1 进程创建3.2 进程间通信&#xff08;IPC&#xff09;3.2.1 管道&#xff08;Pipe&#xff09; 4. 线程的基本概念4.1 线程的定义4.2 线程的特点 …

正则表达式匹配成对括号

匹配一对括号&#xff0c;用于在一个html文本中提取JSon 文本。例如 { “duration”:7599,"minBufferTime{second bracket }{third bracket} } 一对加粗的{} &#xff0c;而不要中间的{}。简单写法会出现错误匹配。 在.Net Framework的正则表达式中&#xff0c;提供了”…

大数据-100 Spark 集群 Spark Streaming DStream转换 黑名单过滤的三种实现方式

喜大普奔&#xff01;破百了&#xff01; 点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&a…

Java框架Shiro、漏洞工具利用、复现以及流量特征分析

Shiro流量分析 前言 博客主页&#xff1a; 靶场&#xff1a;Vulfocus 漏洞威胁分析平台 Shiro&#xff08;Apache Shiro&#xff09;是一个强大且灵活的开源安全框架&#xff0c;专为Java应用程序提供安全性解决方案。它由Apache基金会开发和维护&#xff0c;广泛应用于企业级…

毛利率承压连亏三年后一季度业绩暴增,百利天恒谋求A+H双上市

《港湾商业观察》施子夫 7月10日&#xff0c;四川百利天恒药业股份有限公司&#xff08;以下简称&#xff0c;百利天恒&#xff09;递表港交所主板&#xff0c;联席保荐机构高盛、摩根大通和中信证券。 此次递表港交所系百利天恒第二次谋求上市&#xff0c;若上市成功&#x…

PyTorch升级之旅——安装与基本知识

目录 一、安装 二、张量 创建tensor 张量的操作 广播机制 三、自动求导 四、并行计算 &#xff08;一&#xff09;网络结构分布到不同的设备中(Network partitioning) &#xff08;二&#xff09;同一层的任务分布到不同数据中(Layer-wise partitioning) &#xff08;…

GoModule

GOPATH 最早的就是GOPATH构建模式&#xff0c; go get下载的包都在path中的src目录下 src目录是源代码存放目录。 package mainimport ("net/http""github.com/gorilla/mux" )func main() {r : mux.NewRouter()r.HandleFunc("/hello", func(w h…

解决使用matplotlib不显示中文的问题

某季度某城市某天11点到12点气温变化图 import random x range(60) y_BeiJing [random.uniform(15,18) for i in x] plt.figure(figsize(20,8),dpi80) plt.plot(x,y_BeiJing) x_label ["11点{}分".format(i) for i in x] plt.xticks(x[::5],x_label[::5]) plt.yt…

【精选】基于微信小程序的地铁站点查询系统(全网独一无二,阿龙原创设计)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

C# x Unity面向对象补全计划 设计模式 之 实现一个简单的有限状态机

一个简单的有限状态机可以有如下内容 1.状态基类&#xff08;定义基本状态的方法&#xff0c;如进入&#xff08;Enter&#xff09;、执行&#xff08;Execute&#xff09;和退出&#xff08;Exit&#xff09;&#xff0c;同时可以在此声明需要被管理的对象&#xff09; 2.具体…

【精选】基于python的影片数据爬取与数据分析

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

软件设计师教程(第5版)第5章 软件工程基础知识(更新中)

5.1 软件工程概述 【软件工程】是指应用计算机科学、数学及管理科学等原理,以工程化的原则和方法来解决软件问题的工程&#xff0c;其目的是提高软件生产率、提高软件质量、降低软件成本。P239 5.1.1 计算机软件 计算机软件是指计算机系统中的【程序】及其【文档】。P240 【…