修复尝试生产中的大量问题

This commit is contained in:
TermiNexus committed 2026-08-02 03:09:53 +08:00
1 parent dbf30a9bc0
commit e8ceb5b46b
29 files changed
+1778 -460

No files matched your search

+146
View File
@@ -0,0 +1,146 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { RouteLocationNormalized } from 'vue-router'
/**
* 标签页项
*
* 每个 tab 对应一个已打开的路由页面,path 作为唯一标识。
* 详情页带 query 参数时,同一 path 视为同一 tab(刷新 query,不重复开)。
*/
export interface TabItem {
/** 路由路径(唯一标识,不含 query) */
path: string
/** 路由 name */
name: string
/** 显示标题(取自 route.meta.title) */
title: string
/** 图标(Element Plus Icon 名或 FontAwesome 类名) */
icon?: string
/** 是否可关闭(首页 Dashboard 不可关闭) */
closable: boolean
/** 路由 query(详情页带参数时保留,用于关闭后恢复) */
query: Record<string, string>
/** 完整 fullPath(含 query,用于路由跳转恢复) */
fullPath: string
/** 组件是否已挂载(首次打开后置 true,常驻 DOM 不销毁,切换仅 v-show) */
loaded: boolean
}
/**
* 多标签页状态管理
*
* 对齐原生 GERP 的多 tab 切换能力:
* - 点击菜单自动开 tab(重复点击激活已有,不重复开)
* - tab 可关闭、可切换、右键菜单(关闭其他/全部关闭/刷新)
* - 首页 Dashboard 常驻不可关闭
* - 不持久化(刷新后重置为仅首页,避免脏状态)
*/
export const useTabsStore = defineStore('tabs', () => {
/** 首页常驻 tab */
const HOME_TAB: TabItem = {
path: '/dashboard',
name: 'Dashboard',
title: '仪表盘',
icon: 'Odometer',
closable: false,
query: {},
fullPath: '/dashboard',
loaded: true,
}
/** 已打开的标签页列表 */
const tabs = ref<TabItem[]>([{ ...HOME_TAB }])
/** 当前激活的 tab path */
const activePath = ref('/dashboard')
/** 判断图标是否 FontAwesome 类名(与 DefaultLayout.isFaIcon 一致) */
function isFaIcon(icon: string | undefined | null): boolean {
if (!icon) return false
const t = icon.trim()
return t.includes(' ') || t.startsWith('fa-') || /^fa[brsl]?\b/.test(t)
}
/**
* 新增/激活 tab(路由 beforeEach 调用)
* - 已存在:更新 query/fullPath/title(详情页带不同参数时刷新),返回 false
* - 不存在:追加新 tab(loaded=true 立即挂载组件)并激活,返回 true
* @returns true=新 tab(首次打开,需 loading);false=已存在(已缓存,瞬时切换)
*/
function addTab(route: RouteLocationNormalized): boolean {
const title = (route.meta.title as string) || (route.name as string) || route.path
const icon = route.meta.icon as string | undefined
const path = route.path
const fullPath = route.fullPath
// 提取 string 类型的 query
const query: Record<string, string> = {}
for (const k of Object.keys(route.query)) {
const v = route.query[k]
if (typeof v === 'string') query[k] = v
}
const existing = tabs.value.find((t) => t.path === path)
if (existing) {
existing.query = query
existing.fullPath = fullPath
existing.title = title
activePath.value = path
return false
}
tabs.value.push({
path,
name: String(route.name || ''),
title,
icon,
closable: path !== '/dashboard',
query,
fullPath,
loaded: true,
})
activePath.value = path
return true
}
/**
* 关闭指定 tab
* @returns 若关闭的是当前激活 tab,返回应跳转的 fullPath;否则返回 null
*/
function removeTab(path: string): string | null {
const idx = tabs.value.findIndex((t) => t.path === path)
if (idx === -1) return null
if (!tabs.value[idx].closable) return null // 不可关闭
tabs.value.splice(idx, 1)
// 若关闭的是当前激活 tab,激活相邻 tab
if (activePath.value === path) {
const next = tabs.value[idx] || tabs.value[idx - 1] || tabs.value[tabs.value.length - 1]
if (next) {
activePath.value = next.path
return next.fullPath
}
}
return null
}
/** 关闭其他所有可关闭 tab(保留首页与当前) */
function removeOthers(path: string): void {
tabs.value = tabs.value.filter((t) => !t.closable || t.path === path)
activePath.value = path
}
/** 关闭所有可关闭 tab(仅保留首页) */
function removeAll(): void {
tabs.value = tabs.value.filter((t) => !t.closable)
activePath.value = '/dashboard'
}
/** 重置(登出时调用) */
function reset(): void {
tabs.value = [{ ...HOME_TAB }]
activePath.value = '/dashboard'
}
return { tabs, activePath, addTab, removeTab, removeOthers, removeAll, reset, isFaIcon }
})