优化构架
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { adminNavigation, getNavigationLeaves } from '@/core/navigation/adminNavigation'
|
||||
import router from './index'
|
||||
|
||||
describe('admin router access metadata', () => {
|
||||
it('uses the root route only as a permission-aware entry point while retaining the dashboard prototype', () => {
|
||||
const home = router.getRoutes().find((route) => route.name === 'home')
|
||||
|
||||
expect(home?.path).toBe('/')
|
||||
expect(home?.meta.permission).toBeUndefined()
|
||||
expect(router.getRoutes().some((route) => route.name === 'dashboard')).toBe(true)
|
||||
})
|
||||
|
||||
it('protects warehouse management with its stable list permission', () => {
|
||||
const warehouseRoute = router.getRoutes().find((route) => route.name === 'warehouses')
|
||||
|
||||
expect(warehouseRoute?.path).toBe('/warehouses')
|
||||
expect(warehouseRoute?.meta.permission).toBe('platform:warehouse:list')
|
||||
})
|
||||
|
||||
it('protects express company records with a stable master-data permission', () => {
|
||||
const route = router.getRoutes().find((item) => item.name === 'express-companies')
|
||||
|
||||
expect(route?.path).toBe('/master-data/express-companies')
|
||||
expect(route?.meta.permission).toBe('platform:express:list')
|
||||
})
|
||||
|
||||
it('protects counterparty records with a stable master-data permission', () => {
|
||||
const route = router.getRoutes().find((item) => item.name === 'counterparties')
|
||||
expect(route?.path).toBe('/master-data/counterparties')
|
||||
expect(route?.meta.permission).toBe('platform:counterparty:list')
|
||||
})
|
||||
|
||||
it('protects the download center with its stable list permission', () => {
|
||||
const route = router.getRoutes().find((item) => item.name === 'download-center')
|
||||
expect(route?.path).toBe('/downloads')
|
||||
expect(route?.meta.permission).toBe('platform:download:list')
|
||||
})
|
||||
|
||||
it('protects enterprise reviews with the backend review menu permission', () => {
|
||||
const route = router.getRoutes().find((item) => item.name === 'enterprise-reviews')
|
||||
|
||||
expect(route?.path).toBe('/customers/reviews')
|
||||
expect(route?.meta.permission).toBe('platform:customer:review')
|
||||
})
|
||||
|
||||
it('keeps forbidden inside the authenticated layout without a business permission', () => {
|
||||
const authenticatedLayout = router.options.routes.find((route) => route.path === '/')
|
||||
const forbidden = authenticatedLayout?.children?.find((route) => route.name === 'forbidden')
|
||||
const resolved = router.resolve('/403')
|
||||
|
||||
expect(authenticatedLayout?.meta?.requiresAuth).toBe(true)
|
||||
expect(forbidden?.path).toBe('403')
|
||||
expect(forbidden?.meta?.permission).toBeUndefined()
|
||||
expect(resolved.matched.some((record) => record.meta.requiresAuth)).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps visible navigation paths and permissions aligned with routes', () => {
|
||||
for (const item of getNavigationLeaves(adminNavigation)) {
|
||||
const resolved = router.resolve(item.path)
|
||||
const pageRoute = resolved.matched.at(-1)
|
||||
|
||||
expect(pageRoute, `missing route for ${item.path}`).toBeDefined()
|
||||
expect(pageRoute?.meta.permission, `permission mismatch for ${item.path}`).toBe(item.permission)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,85 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import AppLayout from '@/app/layouts/AppLayout.vue'
|
||||
import { decideRouteAccess } from '@/core/routing/accessDecision'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { downloadRoutes } from '@/modules/downloads/routes'
|
||||
import { firstLegOrderRoutes } from '@/modules/first-leg-orders/routes'
|
||||
import { iamRoutes } from '@/modules/iam/routes'
|
||||
import { masterDataRoutes } from '@/modules/master-data/routes'
|
||||
import { useAuthStore } from '@/core/auth/auth'
|
||||
import { customerRoutes } from '@/modules/customers/routes'
|
||||
import { warehouseRoutes } from '@/modules/warehouses/routes'
|
||||
import { prototypeModuleRoutes } from '@/prototype/routes'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import('@/modules/auth/pages/LoginView.vue'),
|
||||
meta: { title: '登录' },
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: AppLayout,
|
||||
meta: { requiresAuth: true },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'home',
|
||||
component: () => import('@/app/pages/ModuleView.vue'),
|
||||
meta: { title: '进入管理后台', description: '正在进入当前角色可访问的业务页面' },
|
||||
},
|
||||
...prototypeModuleRoutes,
|
||||
...customerRoutes,
|
||||
...warehouseRoutes,
|
||||
...firstLegOrderRoutes,
|
||||
...masterDataRoutes,
|
||||
...downloadRoutes,
|
||||
...iamRoutes,
|
||||
{
|
||||
path: '403',
|
||||
name: 'forbidden',
|
||||
component: () => import('@/app/pages/ForbiddenView.vue'),
|
||||
meta: { title: '权限不足', description: '当前登录账号没有访问此页面的权限' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{ path: '/:pathMatch(.*)*', redirect: '/' },
|
||||
],
|
||||
})
|
||||
|
||||
router.beforeEach((to) => {
|
||||
document.title = to.meta.title ? `${to.meta.title} - 柏悦物流管理后台` : '柏悦物流管理后台'
|
||||
const auth = useAuthStore()
|
||||
if (to.name === 'login' && auth.isAuthenticated) {
|
||||
return { name: 'home' }
|
||||
}
|
||||
if (!auth.isAuthenticated && !to.meta.requiresAuth) return true
|
||||
|
||||
const access = useAccessPreviewStore()
|
||||
if (auth.isAuthenticated) access.initializeAfterLogin()
|
||||
const decision = decideRouteAccess({
|
||||
requiresAuth: to.meta.requiresAuth,
|
||||
isAuthenticated: auth.isAuthenticated,
|
||||
fullPath: to.fullPath,
|
||||
permission: to.meta.permission,
|
||||
hasPermission: to.meta.permission ? access.can(to.meta.permission) : true,
|
||||
})
|
||||
|
||||
if (decision.type === 'login') {
|
||||
return { name: 'login', query: { redirect: decision.redirect } }
|
||||
}
|
||||
if (decision.type === 'forbidden') {
|
||||
return { name: 'forbidden', query: { from: decision.from }, replace: true }
|
||||
}
|
||||
if (to.name === 'home') {
|
||||
return access.firstAccessiblePath
|
||||
? { path: access.firstAccessiblePath, replace: true }
|
||||
: { name: 'forbidden', replace: true }
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
export default router
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
import 'vue-router'
|
||||
|
||||
declare module 'vue-router' {
|
||||
interface RouteMeta {
|
||||
title?: string
|
||||
description?: string
|
||||
showPageHeading?: boolean
|
||||
requiresAuth?: boolean
|
||||
permission?: string
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user