116 lines
3.9 KiB
Vue
116 lines
3.9 KiB
Vue
<template>
|
||
<div class="tabs-container">
|
||
<!-- 标签页导航 -->
|
||
<el-tabs v-model="activeTab" type="card" closable @tab-remove="removeTab">
|
||
<el-tab-pane v-for="tab in tabs" :key="tab.path" :label="tab.title" :name="tab.path"
|
||
:closable="tab.path !== '/welcome'" />
|
||
</el-tabs>
|
||
|
||
<!-- 嵌套路由出口 -->
|
||
<div class="tab-content">
|
||
<router-view v-slot="{ Component }">
|
||
<keep-alive :include="cachedViews">
|
||
<component :is="Component" :key="$route.fullPath" />
|
||
</keep-alive>
|
||
</router-view>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, watch, onMounted } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
|
||
// 定义TabItem数据结构
|
||
interface TabItem {
|
||
title : string
|
||
path : string
|
||
}
|
||
|
||
// 获取当前路由实例,包含当前路由信息(路径、参数等)
|
||
const route = useRoute()
|
||
|
||
// 获取路由控制器,用于执行导航操作(push、replace等)
|
||
const router = useRouter()
|
||
|
||
// 当前激活的标签页路径,使用路由路径作为标识 初始值为当前路由路径,保持与路由同步
|
||
const activeTab = ref(route.path)
|
||
|
||
// 标签页集合,存储所有打开的标签页信息 每个元素结构:{ title: 标签标题, path: 路由路径 }
|
||
const tabs = ref<TabItem[]>([])
|
||
|
||
// 需要缓存的视图路径列表,配合keep-alive实现页面状态保留 元素为路由的完整路径(包含查询参数)
|
||
const cachedViews = ref<string[]>([])
|
||
|
||
// 初始化默认标签
|
||
onMounted(() => {
|
||
//
|
||
if (!tabs.value.some(tab => tab.path === '/welcome')) tabs.value.push({ title: '欢迎页',path: '/welcome' })
|
||
|
||
})
|
||
|
||
// 监听路由变化(自动处理浏览器前进/后退/编程式导航)
|
||
watch(route, (newVal) => {
|
||
// 获取当前完整路径(包含查询参数)
|
||
const fullPath = newVal.fullPath
|
||
// 从路由元信息获取标题,无则显示"异常页"
|
||
const title = newVal.meta.title?.toString() || '异常页'
|
||
// 排除默认欢迎页的重复添加
|
||
if (fullPath === '/welcome') return
|
||
// 检查是否已存在相同路径的标签页
|
||
const existingTab = tabs.value.find(tab => tab.path === fullPath)
|
||
// 判断 新增标签页的条件
|
||
if (!existingTab) {
|
||
// 当前路径不存在于标签页列表
|
||
tabs.value.push({ title,path: fullPath })
|
||
// // 添加路径到缓存列表(需注意内存管理)
|
||
if (!cachedViews.value.includes(fullPath)) cachedViews.value.push(fullPath)
|
||
|
||
}
|
||
// 始终更新激活标签状态(处理同一路由不同参数的情况)
|
||
activeTab.value = fullPath
|
||
}, { immediate: true })
|
||
|
||
// 监听激活标签变化(用户点击标签切换时触发)
|
||
watch(activeTab, (newPath) => {
|
||
// 执行路由跳转的条件 新路径有效(非空)当前路由路径与新路径不一致
|
||
if (newPath && newPath !== route.fullPath) router.push(newPath)
|
||
})
|
||
|
||
// 标签关闭处理方法
|
||
const removeTab = (targetPath : string) => {
|
||
// 禁止关闭欢迎页
|
||
if (targetPath === '/welcome') return
|
||
// 查找目标标签的索引位置
|
||
const index = tabs.value.findIndex(tab => tab.path === targetPath)
|
||
// 防御性判断:确保找到有效标签
|
||
if (index === -1) return
|
||
// 处理缓存(避免内存泄漏)
|
||
cachedViews.value = cachedViews.value.filter(path => path !== targetPath)
|
||
// 从标签列表中移除
|
||
tabs.value.splice(index, 1)
|
||
// 处理关闭的是当前激活标签的情况
|
||
if (targetPath === route.fullPath) {
|
||
// 计算下一个应激活的标签 优先取当前标签的下一个,否则取前一个
|
||
const nextTab = tabs.value[index] || tabs.value[index - 1]
|
||
// 更新激活标签(若没有则回退到欢迎页)
|
||
activeTab.value = nextTab?.path || '/welcome'
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.tabs-container {
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.tab-content {
|
||
flex: 1;
|
||
overflow: auto;
|
||
padding: 15px;
|
||
padding-top: 0;
|
||
background: #fff;
|
||
}
|
||
</style> |