vue3【实战】多页签【组件封装】PageTabs (含右键快捷菜单组件封装 Contextmenu -- 关闭其他页签,关闭所有页签)

效果预览

在这里插入图片描述

技术方案

vue3 ( vite | TS | vueUse | AutoImport | pinia) + Element Plus + UnoCSS

技术要点

  • 需开启 pinia 持久化
  • 右键菜单组件借助了 Element Plus 的样式

代码实现

src/components/PageTabs.vue

<script setup lang="ts">
import { usePageTabsStore } from '@/stores/pageTabs'const PageTabsStore = usePageTabsStore()// 导入自定义的数据类型
import type { MenuProps, menu } from '@/types/menu'/** 父组件传参* @param menu_list 菜单列表*/
const { menu_list, homePath } = defineProps<MenuProps>()const route = useRoute()/*** 通过路径获取菜单项** @param menu_list 菜单列表* @param path 菜单路径* @returns 返回匹配的菜单项,如果没有找到则返回null*/
function getMenuByPath(menu_list: menu[], path: string) {let finalResult = nullfor (const item of menu_list) {if (item.path === path) {finalResult = itembreak} else if (item.children && item.children.length > 0) {let result: any = getMenuByPath(item.children, path)if (result) {finalResult = resultbreak}}}return finalResult
}watch(() => route.path,() => {let newMenu = getMenuByPath(menu_list, route.path)if (newMenu && newMenu.path) {PageTabsStore.current_pageTab = newMenu.pathPageTabsStore.addTab(newMenu)} else {PageTabsStore.current_pageTab = homePath}},{ immediate: true }
)// 获取全局路由
const router = useRouter()/*** 根据新的标签页名称更改当前路由** @param newTab 新的标签页名称*/
function tabChange(newTab: string) {router.push(newTab)
}/*** 移除指定的标签页** @param targetTab 要移除的标签页的路径*/
function tabRemove(targetTab: string) {if (targetTab === PageTabsStore.current_pageTab) {let targetIndex = 0for (const [index, tab] of PageTabsStore.pageTabs.entries()) {if (tab.path === targetTab) {targetIndex = indexbreak}}if (targetIndex === 0) {PageTabsStore.current_pageTab = homePath} else {PageTabsStore.current_pageTab = PageTabsStore.pageTabs[targetIndex - 1].path}router.push(PageTabsStore.current_pageTab)}PageTabsStore.delTab(targetTab)
}const menuInfo = reactive({x: 0,y: 0,menuItems: [{label: '关闭其他页签',onClick: (targetPath: string) => {PageTabsStore.closeOtherTabs(targetPath)router.push(targetPath)}},{label: '关闭全部页签',onClick: () => {PageTabsStore.closeAllTabs()router.push(homePath)}}],visible: false,targetPath: ''
})/*** 右键点击事件处理函数** @param event 鼠标事件对象* @param targetPath 目标路径*/
const onContextmenu = (event: MouseEvent, targetPath: string) => {event.preventDefault()menuInfo.x = event.clientXmenuInfo.y = event.clientYmenuInfo.visible = truemenuInfo.targetPath = targetPath
}
</script><template><el-tabs@tab-change="tabChange"@tab-remove="tabRemove"v-model="PageTabsStore.current_pageTab"class="demo-tabs"type="border-card"><el-tab-pane :name="homePath"><template #label><Icon icon="icon-park-outline:mind-mapping" /></template><slot></slot></el-tab-pane><el-tab-pane closable v-for="item in PageTabsStore.pageTabs" :key="item.name" :name="item.path"><template #label><!-- 仅页签名称上响应右键菜单 --><span @contextmenu.prevent="onContextmenu($event, item.path)">{{ item.name }}</span></template><slot></slot></el-tab-pane></el-tabs><!-- 右键菜单 --><Contextmenu :menuInfo="menuInfo" @closeMenu="menuInfo.visible = false" />
</template>

src/components/Contextmenu.vue

<template><transition name="el-zoom-in-top"><divv-show="visible"class="menu el-dropdown__popper el-popper is-light is-pure":style="{ top: y + 10 + 'px', left: x - 10 + 'px' }"data-popper-placement="bottom"><div class="el-popper__arrow" style="left: 10px"></div><ul class="el-dropdown-menu"><liclass="el-dropdown-menu__item"v-for="item in menuItems":key="item.label"@click="handleClick(item)">{{ item.label }}</li></ul></div></transition>
</template><script setup lang="ts">
interface MenuProps {menuInfo: {x: numbery: numbermenuItems: { label: string; onClick: () => void }[]visible: booleantargetPath: string}
}const props = defineProps<MenuProps>()
const { x, y, menuItems, visible, targetPath } = toRefs(props.menuInfo)const handleClick = (item: { label: string; onClick: (arg1: string) => void }) => {if (item.onClick) {item.onClick(targetPath.value)}
}const container = ref<HTMLElement | null>(null)const emit = defineEmits<{(e: 'closeMenu'): void
}>()/*** 页面其他位置点击时隐藏菜单** @param event 鼠标事件对象*/
function handleGlobalClick(event: MouseEvent) {if (!container.value?.contains(event.target as Node)) {emit('closeMenu')}
}onMounted(() => {window.addEventListener('click', handleGlobalClick)
})onUnmounted(() => {window.removeEventListener('click', handleGlobalClick)
})
</script><style scoped lang="scss">
.menu {position: fixed;z-index: 99999;.el-dropdown-menu__item {font-size: 12px !important;white-space: nowrap;}
}
</style>

src/stores/pageTabs.ts

import { ref } from 'vue'
import { defineStore } from 'pinia'
import type { menu } from '@/types/menu'
export const usePageTabsStore = defineStore('pageTabs', () => {const pageTabs = ref<menu[]>([])const current_pageTab = ref('')function addTab(newTab: menu) {if (pageTabs.value.some((tab) => tab.path === newTab.path)) returnpageTabs.value.push(newTab)}function delTab(targetPath: string) {pageTabs.value = pageTabs.value.filter((tab) => tab.path !== targetPath)}function closeOtherTabs(targetPath: string) {pageTabs.value = pageTabs.value.filter((tab) => tab.path === targetPath)}function closeAllTabs() {pageTabs.value = []current_pageTab.value = ''}return {pageTabs,current_pageTab,addTab,delTab,closeOtherTabs,closeAllTabs}
})

src/types/menu.ts

export interface menu {id: numbername: stringpath: stringmenuHide?: booleannickName?: stringicon?: stringindex?: stringchildren?: menu[]
}export interface MenuProps {menu_list: menu[]default_openeds?: string[]collapse?: booleanhomePath: string
}

页面使用

  • 用 PageTabs 组件包裹页面内容
  • 传入菜单数组 menu_list
  • 传入主页 homePath (默认展示主页,且不会被关闭)
      <PageTabs :menu_list="menu_list" homePath="/notes"><el-scrollbar height="calc(100vh - 236px)"><divv-if="showMindMap"class="flex justify-center items-center p-10"style="margin: auto"><Mindmapclass="dark:color-white!":active="activeLabel":data="mapData"@activeChange="activeChange"/></div><router-view v-else class="p-4 markdown_views mdViews max-w-full"></router-view></el-scrollbar></PageTabs>

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

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

相关文章

Scala的正则表达式

package hfdobject Test35_3 {def main(args: Array[String]): Unit {println("a\tb")//定义一个规则 正则表达式//1. .表示除了换行之外的其他的任意单个字符//2. \d等于[0-9] 匹配一个数字//3. \D除了\d之外的其他的任意字符&#xff0c;表示非数字//4. \w等价于[…

java的几种排序算法(详细)

冒泡排序&#xff08;Bubble Sort&#xff09; 基本原理&#xff1a; 冒泡排序是一种简单的比较排序算法。它重复地走访要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换&#xff0c;也…

vue项目env文件的使用(vue cli2和vue cli3)

Vue CLI 2 环境 在 Vue CLI 2 中&#xff0c;需要安装 dotenv 包来加载和使用环境变量。 步骤&#xff1a; 安装 dotenv&#xff1a;首先安装 dotenv 包 npm install dotenv --save创建 .env 文件&#xff1a;在项目的根目录下创建一个 .env 文件&#xff0c;并在其中定义你的…

全面解析 Transformer:改变深度学习格局的神经网络架构

目录 一、什么是 Transformer&#xff1f; 二、Transformer 的结构解析 1. 编码器&#xff08;Encoder&#xff09; 2. 解码器&#xff08;Decoder&#xff09; 3. Transformer 模型结构图 三、核心技术&#xff1a;注意力机制与多头注意力 1. 注意力机制 2. 多头注意力&…

使用YOLO系列txt目标检测标签的滑窗切割:批量处理图像和标签的实用工具

使用YOLO系列txt目标检测标签的滑窗切割&#xff1a;批量处理图像和标签的实用工具 使用YOLO的TXT目标检测标签的滑窗切割&#xff1a;批量处理图像和标签的实用工具背景1. 代码概述2. 滑窗切割算法原理滑窗切割步骤&#xff1a;示例&#xff1a; 3. **代码实现**1. **加载标签…

Java ArrayList 详解

Java ArrayList 详解 ArrayList 是 Java 集合框架&#xff08;Collection Framework&#xff09;中最常用的类之一&#xff0c;是一种基于动态数组的数据结构&#xff0c;属于 List 接口的实现类。它允许存储重复的元素&#xff0c;有序&#xff0c;支持随机访问&#xff0c;且…

springboot/ssm线上教育培训办公系统Java代码web项目在线课程作业源码

springboot/ssm线上教育培训办公系统Java代码web项目在线课程作业源码 基于springboot(可改ssm)htmlvue项目 开发语言&#xff1a;Java 框架&#xff1a;springboot/可改ssm vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&…

Rust学习笔记_13——枚举

Rust学习笔记_10——守卫 Rust学习笔记_11——函数 Rust学习笔记_12——闭包 枚举 文章目录 枚举1. 定义1.1 无值变体1.2 有值变体1.3 枚举与泛型的结合 2. 使用2.1 和匹配模式一起使用2.2 枚举作为类型别名 3. 常用枚举类型 在Rust编程语言中&#xff0c;枚举&#xff08;enum…

容器运行应用及Docker命令

文章目录 一、使用容器运行Nginx应用1_使用docker run命令运行Nginx应用1 观察下载容器镜像过程2 观察容器运行情况 2_访问容器中运行的Nginx服务1 确认容器IP地址2 容器网络说明3 使用curl命令访问 二、Docker命令1_Docker命令获取帮助方法2_Docker官网提供的命令说明3_docker…

深入浅出:php-学习入门全攻略

文章目录 1. 为什么选择 PHP&#xff1f;2. 安装 PHP 环境2.1 Windows 系统安装步骤 1&#xff1a;下载 PHP步骤 2&#xff1a;解压并配置步骤 3&#xff1a;配置环境变量步骤 4&#xff1a;验证安装 2.2 Mac 系统安装步骤 1&#xff1a;使用 Homebrew 安装步骤 2&#xff1a;验…

【热门主题】000075 探索嵌入式硬件设计的奥秘

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【热…

数据分析(一): 掌握STDF 掌握金钥匙-码农切入半导体的捷径

中国的半导体行业必然崛起&#xff01;看清这个大势&#xff0c;就会有很多机会。 今天&#xff0c;我们一起来了解一下半导体行业的一朵金花&#xff1a;STDF。 实际上这只是一种文件格式&#xff0c;但是当你熟练掌握解析这种文件的时候&#xff0c;你就已经打开在这个基础…

PostgreSQLt二进制安装-contos7

1、安装依赖 yum install -y gcc readline readline-devel zlib-devel net-tools perl wget numactl libicu-devel bison flex openssl-devel pam pam-devel libxml2 libxml2-devel libxslt libxslt-devel openldap openldap-devel 2、创建目录 mkdir -p /data/postgresql/{…

Latex转word(docx)或者说PDF转word 一个相对靠谱的方式

0. 前言 投文章过程中总会有各种各样的要求&#xff0c;其中提供word格式的手稿往往是令我头疼的一件事。尤其在多公式的文章中&#xff0c;其中公式转换是一个头疼的地方&#xff0c;还有很多图表&#xff0c;格式等等&#xff0c;想想就让人头疼欲裂。实践中摸索出一条相对靠…

AWS创建ec2实例并连接成功

aws创建ec2实例并连接 aws创建ec2并连接 1.ec2创建前准备 首先创建一个VPC隔离云资源并且有公有子网 2.创建EC2实例 1.启动新实例或者创建实例 2.创建实例名 3.选择AMI使用linux(HVM) 4.选择实例类型 5.创建密钥对下载到本地并填入密钥对名称 6.选择自己创建的VPC和公有子网…

“放弃Redis Desktop Manager使用Redis Insight”:日常使用教程(Redis可视化工具)

文章目录 更新Redis Insight连接页面基础解释自动更新key汉化暂时没有找到方法&#xff0c; Redis Desktop Manager在连接上右键在数据库上右键在key上右键1、添加连接2、key过期时间 参考文章 更新 (TωT)&#xff89;~~~ β&#xff59;ё β&#xff59;ё~ 现在在维护另一…

如何用注册机破解Reflexive游戏

相信有许多小朋友&#xff08;像我以前一样&#xff09;已经迫不及待地准备准备对浩瀚的、像三星堆一般的Reflexive游戏合集进行考古挖掘工作了。不巧的是&#xff0c;打开游戏之后发现常常提示要付费才能解锁完整版。 一、下载注册机与破解文件 首先&#xff0c;在我的永硕网…

Java 多线程探秘:从线程池到死锁的奇幻之旅

1.简述一下你对线程池的理解 线程池是一种多线程处理形式&#xff0c;处理过程中将任务分为若干个线程&#xff0c;使用线程池可以有效地管理并发线程的数量&#xff0c;提高程序的响应速度和资源利用率。以下是关于线程池的一些关键点&#xff1a; 预创建线程&#xff1a;线…

一万台服务器用saltstack还是ansible?

一万台服务器用saltstack还是ansible? 选择使用 SaltStack 还是 Ansible 来管理一万台服务器&#xff0c;取决于几个关键因素&#xff0c;如性能、扩展性、易用性、配置管理需求和团队的熟悉度。以下是两者的对比分析&#xff0c;帮助你做出决策&#xff1a; SaltStack&…

PDF文件页面转换成图片怎么弄-免费PDF编辑工具分享

>>更多PDF文件处理应用技巧请前往 96缔盟PDF处理器 主页 查阅&#xff01; —————————————————————————————————————— 序言 我之前的文章也有介绍过如何使用96缔盟PDF处理器对PDF文件转换成图片&#xff0c;但是当时是使用DMPDFU…