feat: add portal home launcher, favorites UI, and WorkTab overflow eviction
Replace top-bar module navigation with a card home page, persist favorites via API, reopen favorite tabs on cold login, and enable portal multi-tabs with overflow-based eviction. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
7898d65437
commit
2df2ef0808
17 files changed
+2242
-157
No files matched your search
@@ -0,0 +1,80 @@
|
||||
import { HOME_PAGE_PATH, router } from '@/router'
|
||||
import { useMenuFavoriteStore } from '@/store/modules/menuFavorite'
|
||||
import { useWorktabStore } from '@/store/modules/worktab'
|
||||
import { useMenuStore } from '@/store/modules/menu'
|
||||
import { collectPortalHomeSections, findHomeCardByPath } from '@/utils/router/portal-home-cards'
|
||||
|
||||
const PREOPEN_FLAG_KEY = 'portal-favorites-preopened-user'
|
||||
|
||||
/**
|
||||
* 冷登录:按收藏预开 Tab,当前仍停在首页。
|
||||
* 同用户刷新且已有业务 Tab 时跳过(依赖 worktab 本地持久化)。
|
||||
*/
|
||||
/** @returns 是否执行了冷登录预开(调用方应落点首页) */
|
||||
export async function preopenFavoriteTabsIfNeeded(userId: string): Promise<boolean> {
|
||||
if (!userId) return false
|
||||
|
||||
const home = HOME_PAGE_PATH || '/home'
|
||||
const worktab = useWorktabStore()
|
||||
const hasBusinessTabs = worktab.opened.some(
|
||||
(t) => t.path && t.path !== home && !t.fixedTab
|
||||
)
|
||||
// 刷新恢复:已有业务 Tab 则不再按收藏重开
|
||||
if (hasBusinessTabs) {
|
||||
sessionStorage.setItem(PREOPEN_FLAG_KEY, userId)
|
||||
return false
|
||||
}
|
||||
// 同会话已预开过(仅首页等)则跳过
|
||||
if (sessionStorage.getItem(PREOPEN_FLAG_KEY) === userId) {
|
||||
return false
|
||||
}
|
||||
|
||||
const favStore = useMenuFavoriteStore()
|
||||
await favStore.loadFavorites()
|
||||
const menuStore = useMenuStore()
|
||||
const sections = collectPortalHomeSections(menuStore.menuList || [])
|
||||
|
||||
worktab.openTab(
|
||||
{
|
||||
path: home,
|
||||
title: '首页',
|
||||
name: 'PortalHome',
|
||||
fixedTab: true,
|
||||
keepAlive: true
|
||||
},
|
||||
{ activate: true }
|
||||
)
|
||||
|
||||
for (const item of [...favStore.items]) {
|
||||
const card = findHomeCardByPath(sections, item.path)
|
||||
if (!card) {
|
||||
try {
|
||||
await favStore.removeFavorite(item.path)
|
||||
} catch {
|
||||
// 忽略清理失败,继续预开其余项
|
||||
}
|
||||
continue
|
||||
}
|
||||
const resolved = router.resolve(card.path)
|
||||
if (!resolved.matched.length) {
|
||||
continue
|
||||
}
|
||||
worktab.openTab(
|
||||
{
|
||||
path: card.path,
|
||||
title: card.title,
|
||||
name: String(resolved.name || ''),
|
||||
fixedTab: false,
|
||||
keepAlive: true
|
||||
},
|
||||
{ activate: false }
|
||||
)
|
||||
}
|
||||
|
||||
sessionStorage.setItem(PREOPEN_FLAG_KEY, userId)
|
||||
return true
|
||||
}
|
||||
|
||||
export function clearFavoritePreopenFlag(): void {
|
||||
sessionStorage.removeItem(PREOPEN_FLAG_KEY)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import type { AppRouteRecord } from '@/types/router'
|
||||
import {
|
||||
filterPortalTopModules,
|
||||
resolveMegaPanelL3Links
|
||||
} from '@/utils/router/portal-menu'
|
||||
import {
|
||||
filterSidebarRoutes,
|
||||
isSidebarNavigableRoute
|
||||
} from '@/utils/router/sidebar-menu-filter'
|
||||
|
||||
export interface PortalHomeCardItem {
|
||||
path: string
|
||||
title: string
|
||||
icon?: string
|
||||
}
|
||||
|
||||
export interface PortalHomeSection {
|
||||
/** 一级模块 path */
|
||||
modulePath: string
|
||||
moduleTitle: string
|
||||
cards: PortalHomeCardItem[]
|
||||
}
|
||||
|
||||
function routeTitle(item: AppRouteRecord): string {
|
||||
return String(item.meta?.title ?? item.name ?? item.path ?? '')
|
||||
}
|
||||
|
||||
function toCard(item: AppRouteRecord): PortalHomeCardItem | null {
|
||||
const path = String(item.meta?.activePath || item.path || '')
|
||||
if (!path) return null
|
||||
return {
|
||||
path,
|
||||
title: routeTitle(item),
|
||||
icon: item.meta?.icon ? String(item.meta.icon) : undefined
|
||||
}
|
||||
}
|
||||
|
||||
/** 从一级模块收集可点卡片(跳过不可导航二级标题) */
|
||||
export function collectCardsForModule(module: AppRouteRecord): PortalHomeCardItem[] {
|
||||
const l2List = filterSidebarRoutes(module.children || [])
|
||||
const cards: PortalHomeCardItem[] = []
|
||||
const seen = new Set<string>()
|
||||
|
||||
const push = (item: AppRouteRecord) => {
|
||||
const card = toCard(item)
|
||||
if (!card || seen.has(card.path)) return
|
||||
seen.add(card.path)
|
||||
cards.push(card)
|
||||
}
|
||||
|
||||
for (const l2 of l2List) {
|
||||
const links = resolveMegaPanelL3Links(l2)
|
||||
for (const link of links) {
|
||||
push(link)
|
||||
}
|
||||
const hasKids = filterSidebarRoutes(l2.children || []).length > 0
|
||||
if (hasKids && isSidebarNavigableRoute(l2)) {
|
||||
push(l2)
|
||||
}
|
||||
}
|
||||
return cards
|
||||
}
|
||||
|
||||
export function collectPortalHomeSections(menuList: AppRouteRecord[]): PortalHomeSection[] {
|
||||
const tops = filterPortalTopModules(menuList)
|
||||
return tops
|
||||
.map((mod) => ({
|
||||
modulePath: String(mod.path || ''),
|
||||
moduleTitle: routeTitle(mod),
|
||||
cards: collectCardsForModule(mod)
|
||||
}))
|
||||
.filter((s) => s.cards.length > 0)
|
||||
}
|
||||
|
||||
/** 在全部分区中按 path 查找卡片元数据 */
|
||||
export function findHomeCardByPath(
|
||||
sections: PortalHomeSection[],
|
||||
path: string
|
||||
): PortalHomeCardItem | undefined {
|
||||
for (const s of sections) {
|
||||
const hit = s.cards.find((c) => c.path === path)
|
||||
if (hit) return hit
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
Reference in new issue
Block a user