Files
medical-mall/layouts/admin/store/adminNavStore.uts
2026-02-06 10:14:46 +08:00

244 lines
5.9 KiB
Plaintext
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.
/**
* Admin 导航状态管理
* 管理路由切换、菜单选中、标签页等状态
*/
import { ref, computed } from 'vue'
import type { RouteRecord } from '@/layouts/admin/router/adminRoutes.uts'
import {
findRouteById,
findRouteByPath,
buildDefaultTabs,
getTopMenus
} from '@/layouts/admin/router/adminRoutes.uts'
import { addView, activeFullPath, visitedViews } from './tagsViewStore.uts'
/**
* 标签页类型
*/
export type TabItem = {
id: string
title: string
path: string
isAffix: boolean // 是否固定(不可关闭)
}
// ============================================
// 状态定义
// ============================================
/** 当前选中的一级菜单ID */
export const activeTopMenuId = ref<string>('home')
/** 当前激活的路由ID */
export const activeRouteId = ref<string>('home_index')
/** 记录每个一级模块上一次访问的二级路由ID (CRMEB 体验增强) */
export const lastSubIdByMenu = ref<Record<string, string>>({})
/** 标记是否由用户手动关闭了 SubSider (移动端) */
export const isManualClosed = ref<boolean>(false)
/** 打开的标签页列表 */
export const tabs = ref<TabItem[]>([])
/** 是否折叠主侧边栏 (CRMEB: 70px) */
export const isMainAsideCollapsed = ref<boolean>(false)
/** 是否显示二级侧边栏状态控制 */
export const showSubSider = ref<boolean>(true)
/** 屏幕宽度 */
export const windowWidth = ref<number>(1024)
/** 布局模式desktop | tablet | mobile */
export const layoutMode = computed<string>(() => {
if (windowWidth.value < 768) return 'mobile'
if (windowWidth.value < 1200) return 'tablet'
return 'desktop'
})
/** 是否为移动端简易判断 */
export const isMobile = computed<boolean>(() => layoutMode.value === 'mobile')
/** 移动端开关 */
export const isMobileMenuOpen = ref<boolean>(false)
/** 遮罩层开关 (用于 tablet 的 subSider overlay 和 mobile 的 aside overlay) */
export const isOverlayVisible = ref<boolean>(false)
// ============================================
// Actions
// ============================================
/**
* 打开路由(核心方法)
* @param routeId 路由ID
* @param addTab 是否添加到标签页
*/
export function openRoute(routeId: string, addTab: boolean = true): void {
const route = findRouteById(routeId)
if (!route) {
console.warn(`[AdminNav] Route not found: ${routeId}`)
return
}
// 更新当前路由
activeRouteId.value = routeId
// 更新一级菜单选中态
if (route.parentId) {
activeTopMenuId.value = route.parentId
// 记录该模块最后访问的子路由
lastSubIdByMenu.value[route.parentId] = routeId
} else {
// 首页等顶级路由
activeTopMenuId.value = routeId.split('_')[0]
}
// 添加到标签页
if (addTab) {
addTabItem(route)
}
}
/**
* 通过路径打开路由
*/
export function openRouteByPath(path: string): void {
const route = findRouteByPath(path)
if (route) {
openRoute(route.id)
}
}
/**
* 添加标签页
*/
function addTabItem(route: RouteRecord): void {
const existingTab = tabs.value.find(t => t.id === route.id)
if (!existingTab) {
tabs.value.push({
id: route.id,
title: route.title,
path: route.path,
isAffix: route.isAffix || false
})
}
// 更新新版 TagsViewStore
addView(route, route.path)
activeFullPath.value = route.path
}
/**
* 关闭标签页
* @param tabId 标签ID
*/
export function closeTab(tabId: string): void {
const index = tabs.value.findIndex(t => t.id === tabId)
if (index === -1) return
const tab = tabs.value[index]
// 固定标签不可关闭
if (tab.isAffix) {
console.warn(`[AdminNav] Cannot close fixed tab: ${tabId}`)
return
}
// 如果关闭的是当前激活标签,需要切换到其他标签
if (activeRouteId.value === tabId) {
// 优先切换到右侧标签,否则切换到左侧
const nextTab = tabs.value[index + 1] || tabs.value[index - 1]
if (nextTab) {
openRoute(nextTab.id, false)
}
}
tabs.value.splice(index, 1)
}
/**
* 关闭其他标签页
* @param keepTabId 保留的标签ID
*/
export function closeOtherTabs(keepTabId: string): void {
tabs.value = tabs.value.filter(t => t.isAffix || t.id === keepTabId)
// 如果当前激活的标签被关闭了,切换到保留的标签
const stillExists = tabs.value.find(t => t.id === activeRouteId.value)
if (!stillExists) {
openRoute(keepTabId, false)
}
}
/**
* 关闭所有标签页(保留固定标签)
*/
export function closeAllTabs(): void {
tabs.value = tabs.value.filter(t => t.isAffix)
// 切换到首页
const homeTab = tabs.value.find(t => t.isAffix)
if (homeTab) {
openRoute(homeTab.id, false)
}
}
/**
* 切换主侧边栏折叠状态
*/
export function toggleMainAsideCollapse(): void {
isMainAsideCollapsed.value = !isMainAsideCollapsed.value
}
/**
* 切换二级侧边栏显示状态 (Desktop 模式)
*/
export function toggleSubSider(): void {
showSubSider.value = !showSubSider.value
}
/**
* 初始化导航状态
* 在 AdminLayout 组件 onMounted 时调用
*/
export function initNavState(): void {
// 初始化默认标签页
const defaultTabs = buildDefaultTabs()
tabs.value = defaultTabs.map(r => ({
id: r.id,
title: r.title,
path: r.path,
isAffix: r.isAffix || false
}))
// 初始化 TagsViewStore
defaultTabs.forEach(r => {
addView(r, r.path)
})
// 打开首页
openRoute('home_index', false)
}
/**
* 根据 currentPage 同步状态
* 用于页面组件传入 currentPage prop 时的状态同步
*/
export function syncFromCurrentPage(currentPage: string): void {
if (!currentPage) return
// 可能是路由ID或路径
const route = findRouteById(currentPage) || findRouteByPath(currentPage)
if (route) {
activeRouteId.value = route.id
// 更新一级菜单
if (route.parentId) {
activeTopMenuId.value = route.parentId
}
}
}