daShangDao_newAdmin/src/components/TabsView.vue
2025-06-05 10:35:45 +08:00

116 lines
3.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>