material_manage_system/frontend/src/router/index.js
文睿 张 6cc3cd5dda feat: 新增公司切换与请求上下文
- 新增 company 请求上下文工具、organization store 与公司切换器
- axios 拦截器为组织级请求写入 X-Organization-ID 并校验上下文版本
- 切换公司用 AbortController 取消旧读请求,丢弃旧响应且不弹提示
- 路由与页面加公司初始化屏障,组织级页面按公司 ID + 版本挂载
2026-10-09 10:19:19 +08:00

133 lines
3.8 KiB
JavaScript

import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '../stores/auth'
import { useOrganizationStore } from '../stores/organization'
// organizationScoped 标记“按公司隔离”的业务页面:进入前必须有当前公司,
// 且用公司 ID + 版本作为挂载 key。全局的用户/组织/角色管理页不重置公司数据。
export const routes = [
{
path: '/login',
name: 'Login',
component: () => import('../views/Login.vue'),
meta: { guestOnly: true }
},
{
path: '/',
redirect: '/stock'
},
{
path: '/materials',
name: 'Materials',
component: () => import('../views/Materials.vue'),
meta: { requiresAuth: true, adminOnly: true, organizationScoped: true }
},
{
path: '/master-data/settings',
name: 'MasterDataSettings',
component: () => import('../views/master-data/MasterDataSettings.vue'),
meta: { requiresAuth: true, adminOnly: true, organizationScoped: true }
},
{
path: '/organizations',
name: 'Organizations',
component: () => import('../views/organizations/Organizations.vue'),
meta: { requiresAuth: true, adminOnly: true }
},
{
path: '/roles',
name: 'Roles',
component: () => import('../views/organizations/Roles.vue'),
meta: { requiresAuth: true, adminOnly: true }
},
{
path: '/partners/suppliers',
name: 'Suppliers',
component: () => import('../views/partners/Suppliers.vue'),
meta: { requiresAuth: true, adminOnly: true, organizationScoped: true }
},
{
path: '/partners/customers',
name: 'Customers',
component: () => import('../views/partners/Customers.vue'),
meta: { requiresAuth: true, adminOnly: true, organizationScoped: true }
},
{
path: '/inbound',
name: 'Inbound',
component: () => import('../views/Inbound.vue'),
meta: { requiresAuth: true, adminOnly: true, organizationScoped: true }
},
{
path: '/outbound',
name: 'Outbound',
component: () => import('../views/Outbound.vue'),
meta: { requiresAuth: true, adminOnly: true, organizationScoped: true }
},
{
path: '/stock',
name: 'Stock',
component: () => import('../views/Stock.vue'),
meta: { requiresAuth: true, organizationScoped: true }
},
{
path: '/application',
name: 'Application',
component: () => import('../views/Application.vue'),
meta: { requiresAuth: true, organizationScoped: true }
},
{
path: '/approval',
name: 'Approval',
component: () => import('../views/Approval.vue'),
meta: { requiresAuth: true, adminOnly: true, organizationScoped: true }
},
{
path: '/logs',
name: 'Logs',
component: () => import('../views/Logs.vue'),
meta: { requiresAuth: true, adminOnly: true, organizationScoped: true }
},
{
path: '/users',
name: 'Users',
component: () => import('../views/Users.vue'),
meta: { requiresAuth: true, adminOnly: true }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore()
const organizationStore = useOrganizationStore()
if (to.meta.guestOnly && authStore.isLoggedIn) {
return next('/stock')
}
if (to.meta.requiresAuth && !authStore.isLoggedIn) {
return next('/login')
}
if (to.meta.adminOnly && !authStore.isAdmin) {
return next('/stock')
}
// 组织初始化屏障:登录后先加载公司并确定当前公司,避免业务组件 onMounted
// 在缺少组织头的情况下发出请求。路由级只负责加载,页面不挂载交给 App 的屏障。
if (to.meta.requiresAuth && authStore.isLoggedIn && !organizationStore.initialized) {
try {
await organizationStore.initialize()
} catch {
// 加载失败不阻断导航:公司为空时由 App 的屏障提示用户选择公司。
}
}
next()
})
export default router