Files
workspace/code/g3soft-erp/web/src/router/index.ts
T
2026-10-11 22:14:13 +08:00

168 lines
5.3 KiB
TypeScript
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.
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
import airRoutes from './modules/air'
import authRoutes from './modules/auth'
import containerRoutes from './modules/container'
import crmRoutes from './modules/crm'
import financeRoutes from './modules/finance'
import oceanRoutes from './modules/ocean'
import systemRoutes from './modules/system'
import { APP_TITLE } from '@/constants/common'
import { menuItems } from '@/constants/menu'
import type { MenuItem } from '@/types/menu'
NProgress.configure({ showSpinner: false })
/** 由菜单常量派生首页重定向目标,保证「菜单唯一数据源」不被打破。 */
const firstMenuPath = menuItems[0]?.path ?? '/ocean'
const routes: RouteRecordRaw[] = [
// 认证域(登录等):顶层路由,与主框架平级、不套 DefaultLayout
...authRoutes,
{
path: '/',
component: () => import('@/layouts/DefaultLayout.vue'),
redirect: firstMenuPath,
children: [
...oceanRoutes,
...airRoutes,
...containerRoutes,
...financeRoutes,
...crmRoutes,
...systemRoutes,
// 403 / 500:作为主框架的子路由(保留侧栏/顶栏/页签,用户可直接切走),
// 与 fms-vue 一致(routes.js 里 /403 /500 挂在布局下)。
// 渲染崩溃的兜底不经过这里 —— 那由 App 层的 ErrorBoundary 全屏渲染。
//
// 页签 / 面包屑 / 文档标题统一用「403 / 500」状态码(与 URL 一一对应),
// 页面内的标题仍由 G3Result 按 locale 显示「无权访问 / 页面出错了」。
{
path: '/403',
name: 'forbidden',
component: () => import('@/views/fallback/Forbidden.vue'),
meta: { title: '403' },
},
{
path: '/500',
name: 'server-error',
component: () => import('@/views/fallback/ServerError.vue'),
meta: { title: '500' },
},
],
},
// 通配 404:独立布局(无外壳),放最后
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/views/fallback/NotFound.vue'),
meta: { title: '页面不存在' },
},
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
scrollBehavior: () => ({ top: 0 }),
})
router.beforeEach((to) => {
NProgress.start()
const title = to.meta.title as string | undefined
document.title = title ? `${title} - ${APP_TITLE}` : APP_TITLE
// 登录校验待用户模块落地后接入;当前直接放行。
// vue-router 5 已弃用 next 回调,改用返回值:放行 return true,拦截 return false。
return true
})
router.afterEach(() => {
NProgress.done()
})
/**
* 在菜单树中按 path 查找菜单项(含子级)。
*/
export function findMenuItemByPath(path: string): MenuItem | undefined {
const walk = (items: MenuItem[]): MenuItem | undefined => {
for (const item of items) {
if (item.path === path) return item
if (item.children?.length) {
const found = walk(item.children)
if (found) return found
}
}
return undefined
}
return walk(menuItems)
}
/**
* 在菜单树中按 key 查找菜单项(含子级)。
* 面包屑用它把祖先 key 链还原成「分组名称」。
*/
export function findMenuItemByKey(key: string): MenuItem | undefined {
const walk = (items: MenuItem[]): MenuItem | undefined => {
for (const item of items) {
if (item.key === key) return item
if (item.children?.length) {
const found = walk(item.children)
if (found) return found
}
}
return undefined
}
return walk(menuItems)
}
/**
* 由当前路径反查它的所有祖先分组 key(从顶级到最近一级)。
* 支持多级嵌套与详情页:/ocean/booking/123 会命中「海运」「订舱业务」。
* 返回数组保证侧栏能逐层展开所有祖先分组。
*
* ⚠️ 这里的「命中」必须是【叶子】命中,不能是「分组 path 前缀命中」——
* 否则 /ocean/booking 会因 startsWith('/ocean/') 在「海运」这一层就提前返回,
* 丢掉真正的祖先链 [ocean, ocean-booking-group],导致刷新后不展开。
*/
export function findParentKeysByPath(path: string): string[] {
const walk = (items: MenuItem[], parents: string[]): string[] | null => {
for (const item of items) {
// 叶子(无 children)且精确命中 / 前缀命中(详情页):返回累计的祖先
if (!item.children?.length) {
if (item.path === path || path.startsWith(`${item.path}/`)) {
return parents
}
continue
}
// 分组:自身 path 精确命中(父级 path 与叶子相同的情况,如 /ocean/booking)
if (item.path === path) {
return parents
}
// 继续下钻,把当前分组 key 计入祖先链
const found = walk(item.children, [...parents, item.key])
if (found) return found
}
return null
}
return walk(menuItems, []) ?? []
}
/**
* 由当前路径反查所属的顶级分组 key(向后兼容,供需要单一顶级 key 的场景使用)。
*/
export function findParentKeyByPath(path: string): string | undefined {
return findParentKeysByPath(path)[0]
}
export default router