2025-12-20 23:44:46 +08:00
|
|
|
|
import { reactive } from 'vue'
|
|
|
|
|
|
import { defineStore } from 'pinia'
|
|
|
|
|
|
import { STORE_TAB_VIEW_CONFIG } from '@/stores/constant/cacheKey'
|
|
|
|
|
|
import type { NavTabs } from '@/stores/interface/index'
|
|
|
|
|
|
import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'
|
|
|
|
|
|
import { adminBaseRoutePath } from '@/router/static'
|
|
|
|
|
|
import { set } from 'lodash'
|
|
|
|
|
|
|
|
|
|
|
|
export const useNavTabs = defineStore(
|
|
|
|
|
|
'navTabs',
|
|
|
|
|
|
() => {
|
|
|
|
|
|
const state: NavTabs = reactive({
|
|
|
|
|
|
// 激活tab的index
|
|
|
|
|
|
activeIndex: 0,
|
|
|
|
|
|
// 激活的tab
|
|
|
|
|
|
activeRoute: null,
|
|
|
|
|
|
// tab列表
|
|
|
|
|
|
tabsView: [],
|
|
|
|
|
|
// 当前tab是否全屏
|
|
|
|
|
|
tabFullScreen: false,
|
|
|
|
|
|
// 从后台加载到的菜单路由列表
|
|
|
|
|
|
tabsViewRoutes: [],
|
|
|
|
|
|
// 按钮权限节点
|
|
|
|
|
|
authNode: new Map()
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
function addTab(route: RouteLocationNormalized) {
|
|
|
|
|
|
if (!route.meta.addtab) return
|
|
|
|
|
|
for (const key in state.tabsView) {
|
|
|
|
|
|
if (state.tabsView[key].path === route.path) {
|
|
|
|
|
|
state.tabsView[key].params = route.params ? route.params : state.tabsView[key].params
|
|
|
|
|
|
state.tabsView[key].query = route.query ? route.query : state.tabsView[key].query
|
|
|
|
|
|
state.tabsView[key].meta = route.query ? route.meta : state.tabsView[key].meta
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
state.tabsView.push(route)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeTab(route: RouteLocationNormalized) {
|
|
|
|
|
|
state.tabsView.map((v, k) => {
|
|
|
|
|
|
if (v.path == route.path) {
|
|
|
|
|
|
state.tabsView.splice(k, 1)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 关闭多个标签
|
|
|
|
|
|
* @param retainMenu 需要保留的标签,否则关闭全部标签
|
|
|
|
|
|
*/
|
|
|
|
|
|
const closeTabs = (retainMenu: RouteLocationNormalized | false = false) => {
|
|
|
|
|
|
if (retainMenu) {
|
|
|
|
|
|
state.tabsView = [retainMenu]
|
|
|
|
|
|
} else {
|
|
|
|
|
|
state.tabsView = []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const setActiveRoute = (route: RouteLocationNormalized): void => {
|
|
|
|
|
|
const currentRouteIndex: number = state.tabsView.findIndex((item: RouteLocationNormalized) => {
|
|
|
|
|
|
return item.path === route.path
|
|
|
|
|
|
})
|
|
|
|
|
|
if (currentRouteIndex === -1) return
|
|
|
|
|
|
state.activeRoute = route
|
|
|
|
|
|
state.activeIndex = currentRouteIndex
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const setTabsViewRoutes = (data: RouteRecordRaw[]): void => {
|
2026-01-05 16:34:42 +08:00
|
|
|
|
state.tabsViewRoutes = encodeRoutesURI(JSON.parse(JSON.stringify(data)))
|
2025-12-20 23:44:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const setAuthNode = (key: string, data: string[]) => {
|
|
|
|
|
|
state.authNode.set(key, data)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const fillAuthNode = (data: Map<string, string[]>) => {
|
|
|
|
|
|
state.authNode = data
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const setFullScreen = (fullScreen: boolean): void => {
|
|
|
|
|
|
state.tabFullScreen = fullScreen
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const refresh = () => {
|
|
|
|
|
|
// setTimeout(() => {
|
|
|
|
|
|
// console.log(123, state.tabsViewRoutes)
|
2026-01-05 16:34:42 +08:00
|
|
|
|
|
2025-12-20 23:44:46 +08:00
|
|
|
|
let list = matchAndReturnRouteData(state.tabsViewRoutes, state.tabsView)
|
2026-01-05 16:34:42 +08:00
|
|
|
|
state.tabsView = []
|
2025-12-20 23:44:46 +08:00
|
|
|
|
list.forEach(item => {
|
|
|
|
|
|
addTab(item)
|
|
|
|
|
|
})
|
|
|
|
|
|
// }, 1000)
|
|
|
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
|
|
|
state,
|
|
|
|
|
|
addTab,
|
|
|
|
|
|
closeTab,
|
|
|
|
|
|
closeTabs,
|
|
|
|
|
|
setActiveRoute,
|
|
|
|
|
|
setTabsViewRoutes,
|
|
|
|
|
|
setAuthNode,
|
|
|
|
|
|
fillAuthNode,
|
|
|
|
|
|
setFullScreen,
|
|
|
|
|
|
refresh
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
persist: {
|
|
|
|
|
|
key: STORE_TAB_VIEW_CONFIG,
|
|
|
|
|
|
paths: ['state.tabFullScreen']
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 核心逻辑:
|
|
|
|
|
|
* 1. 递归遍历树形菜单,筛选出与routeList中name匹配的节点
|
|
|
|
|
|
* 2. 将匹配到的节点格式转换为routeList的结构并返回
|
|
|
|
|
|
*/
|
|
|
|
|
|
function matchAndReturnRouteData(tree, routeList) {
|
|
|
|
|
|
// 1. 构建路由name映射(name -> 完整路由对象)
|
|
|
|
|
|
const routeMap = new Map()
|
|
|
|
|
|
routeList.forEach(route => {
|
|
|
|
|
|
if (route.name) {
|
|
|
|
|
|
routeMap.set(route.name, route)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 递归遍历树形菜单,收集匹配的节点
|
|
|
|
|
|
const matchedNodes = []
|
|
|
|
|
|
function recursion(node) {
|
|
|
|
|
|
// 匹配当前节点
|
|
|
|
|
|
if (routeMap.has(node.name)) {
|
|
|
|
|
|
// 深度克隆路由对象,避免修改原数据
|
|
|
|
|
|
const matchedRoute = JSON.parse(JSON.stringify(routeMap.get(node.name)))
|
|
|
|
|
|
matchedNodes.push(matchedRoute)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 递归处理子节点
|
|
|
|
|
|
if (node.children && node.children.length) {
|
|
|
|
|
|
node.children.forEach(child => recursion(child))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历所有顶级节点
|
|
|
|
|
|
tree.forEach(node => recursion(node))
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 返回匹配后的第二个数据格式(和routeList结构一致)
|
|
|
|
|
|
return matchedNodes
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 对iframe的url进行编码
|
|
|
|
|
|
*/
|
|
|
|
|
|
function encodeRoutesURI(data: RouteRecordRaw[]) {
|
|
|
|
|
|
data.forEach(item => {
|
|
|
|
|
|
if (item.meta?.menu_type == 'iframe') {
|
|
|
|
|
|
item.path = adminBaseRoutePath + '/iframe/' + encodeURIComponent(item.path)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (item.children && item.children.length) {
|
|
|
|
|
|
item.children = encodeRoutesURI(item.children)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
return data
|
|
|
|
|
|
}
|