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,一经查实,立即删除!

相关文章

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

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

容器运行应用及Docker命令

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

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

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

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

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

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;在我的永硕网…

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

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

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

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

从 scratch开始构建一个最小化的 Hello World Docker 镜像-docker的镜像源头

在这篇文章中&#xff0c;我们将学习如何从零开始构建一个最小化的 Docker 镜像&#xff0c;基于 scratch 镜像&#xff0c;并在其中运行一个简单的 “Hello World” 程序。 Scratch 是一个空白的基础镜像&#xff0c;适用于构建轻量化、独立的容器。由于 scratch 不包含任何系…

OpenHarmony-4.GPIO驱动

GPIO 1.功能简介 GPIO&#xff08;General-purpose input/output&#xff09;即通用型输入输出。GPIO又俗称为I/O口&#xff0c;I指的是输入(in&#xff09;&#xff0c;O指的是输出&#xff08;out&#xff09;。可以通过软件来控制其输入和输出&#xff0c;即I/O控制。通常&…

leetcode 1843 可疑银行账户(postgresql)

需求 表: Accounts -------------------- | Column Name | Type | -------------------- | account_id | int | | max_income | int | -------------------- account_id 是表主键。 每行包含一个银行账户每月最大收入的信息。 表: Transactions ------------------------ |…

【开源代码】图像水印移除-依赖python-tensorflow

下载源码 git clone https://github.com/zuruoke/watermark-removal创建conda环境 conda create -n tensorflow_gpu python=3.7 conda activate tensorflow_gpu conda install tensorflow-gpu==1.15

PyQt信号槽实现页面的登录与跳转 #页面进一步优化

将登录框中的取消按钮使用信号和槽的机制&#xff0c;关闭界面。 将登录按钮使用信号和槽连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;密码是否为"123456",如果账号密码匹配成功&#xff0c;当前界面关…

自动化立体仓库项目任务调度系统中任务流程可视化实现

在运维自动化平台中,任务系统无疑是最核心的组成部分之一。它承担着所有打包编译、项目上线、日常维护等运维任务的执行。通过任务系统,我们能够灵活地构建满足不同需求的自定义任务流。早期的任务流后端采用了类似列表的存储结构,根据任务流内子任务的排序依次执行,尽管通…

WEB安全 PHP学习

PHP基础 PHP编码显示问题 header ("Content-type: text/html; charsetgb2312"); header("Content-Type: text/html;charsetutf-8"); windows需要使用gbk编码显示 源码是 <?php header ("Content-type: text/html; charsetgb2312"); sys…

11.爬虫

前言&#xff1a; 正则表达式的作用&#xff1a; 作用一&#xff1a;校验字符串是否满足规则 作用二&#xff1a;在一段文本中查找满足要求的内容 一.Pattern类和Matcher类&#xff1a; 1.Pattern类&#xff1a;表示正则表达式 a.因此获取Pattern对象就相当于获取正则表达式…

Visual Studio 2022 项目配置常用选项

作为一名C++开发者,经常需要配置第三方库,今天来跟大家截图一下,方便大家快速配置: 头文件包含目录: 或者: 库文件包含目录:

如何搭建JMeter分布式集群环境来进行性能测试

在性能测试中&#xff0c;当面对海量用户请求的压力测试时&#xff0c;单机模式的JMeter往往力不从心。如何通过分布式集群环境&#xff0c;充分发挥JMeter的性能测试能力&#xff1f;这正是许多测试工程师在面临高并发、海量数据时最关注的问题。那么&#xff0c;如何轻松搭建…