feat: 运营平台基建 搭建
This commit is contained in:
+37
-14
@@ -1,30 +1,53 @@
|
||||
import router from '@/router';
|
||||
import { auth, handleRouteGuard } from '@/hooks/useAuth';
|
||||
import { auth } from '@/hooks/useAuth';
|
||||
|
||||
/**
|
||||
* 全局路由守卫
|
||||
* 核心流程:
|
||||
* 1. 白名单(/login 等) → 直接放行
|
||||
* 2. 已登录 → 加载菜单权限(首次) → 放行
|
||||
* 3. 未登录 → 跳转 /login
|
||||
* 4. 页面标题自动设置
|
||||
* 1. /login → 已登录则跳首页,未登录则放行
|
||||
* 2. 未登录 → 跳转 /login
|
||||
* 3. 已登录但菜单未加载 → 加载菜单+权限 → 重新导航(确保动态路由生效)
|
||||
* 4. 已登录且菜单已加载 → 放行
|
||||
*
|
||||
* 关键:当 loadMenu 动态注册路由后,必须用 next({ path, replace: true })
|
||||
* 重新触发导航,否则当前导航仍使用旧路由表,会匹配到 404。
|
||||
* 注意:不能用 next({ ...to, replace: true }),因为展开 to 会把旧的
|
||||
* matched 信息带过去,导致仍然匹配到 /:pathMatch(.*)*。
|
||||
*/
|
||||
router.beforeEach(async (to, _from, next) => {
|
||||
// 设置页面标题
|
||||
const appTitle = import.meta.env.VITE_APP_TITLE || 'CPMS 运营平台';
|
||||
document.title = to.meta.title ? `${to.meta.title} - ${appTitle}` : appTitle;
|
||||
|
||||
const { allow, redirect } = await handleRouteGuard(to.path);
|
||||
|
||||
if (allow) {
|
||||
// 已登录访问 /login → 跳首页
|
||||
if (to.path === '/login' && auth.isLoggedIn()) {
|
||||
// ── /login 特殊处理 ──
|
||||
if (to.path === '/login') {
|
||||
if (auth.isLoggedIn()) {
|
||||
const { homePath } = await import('@/stores/menuStore').then((m) => m.useMenuStore());
|
||||
next(homePath.value);
|
||||
return;
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
next();
|
||||
} else {
|
||||
next(redirect || '/login');
|
||||
return;
|
||||
}
|
||||
|
||||
// ── 未登录 → 跳转登录页 ──
|
||||
if (!auth.isLoggedIn()) {
|
||||
next('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
// ── 已登录:确保菜单和动态路由已加载 ──
|
||||
const { loadMenu, loaded } = await import('@/stores/menuStore').then((m) => m.useMenuStore());
|
||||
if (!loaded.value) {
|
||||
const { loadPermissions } = await import('@/stores/permissionStore').then((m) =>
|
||||
m.usePermissionStore(),
|
||||
);
|
||||
await Promise.all([loadMenu(), loadPermissions()]);
|
||||
|
||||
// 动态路由刚注册,需要用新路由表重新匹配当前路径
|
||||
next({ path: to.fullPath, replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
+11
-6
@@ -13,17 +13,22 @@ const router = createRouter({
|
||||
const WHITE_LIST = ['/login', '/404'];
|
||||
|
||||
/**
|
||||
* 重置路由:清除动态添加的子路由,恢复到只包含静态路由的状态
|
||||
* 用于退出登录时清理
|
||||
* 静态路由名称集合(这些路由不会被 resetRouter 移除)
|
||||
*/
|
||||
const STATIC_ROUTE_NAMES = new Set(['Login', 'NotFound']);
|
||||
|
||||
/**
|
||||
* 重置路由:清除动态添加的路由(包括 BasicLayout 和其子路由),
|
||||
* 恢复到只包含静态路由的状态。用于退出登录时清理。
|
||||
*/
|
||||
export function resetRouter() {
|
||||
const currentRoutes = router.getRoutes();
|
||||
const protectedNames = new Set(['Login', 'BasicLayout', 'NotFound']);
|
||||
|
||||
currentRoutes.forEach((route) => {
|
||||
// 保留静态路由(Login, BasicLayout, NotFound),移除动态添加的子路由
|
||||
if (!protectedNames.has(route.name as string)) {
|
||||
router.removeRoute(route.name!);
|
||||
const routeName = route.name as string;
|
||||
// 保留静态路由(Login, NotFound),移除所有动态路由(含 BasicLayout 及其子路由)
|
||||
if (routeName && !STATIC_ROUTE_NAMES.has(routeName)) {
|
||||
router.removeRoute(routeName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+4
-10
@@ -6,6 +6,9 @@ import type { RouteRecordRaw } from 'vue-router';
|
||||
* 动态路由由 menuStore 在登录后通过 router.addRoute 注入,
|
||||
* 后端下发 MenuNode 包含 component 字段,
|
||||
* 前端通过 import.meta.glob 动态匹配 src/pages 下的组件。
|
||||
*
|
||||
* 注意: BasicLayout 路由不在此注册,而是由 menuStore 动态添加,
|
||||
* 这样可以避免空 children 导致的路由匹配问题。
|
||||
*/
|
||||
export const constantRoutes: RouteRecordRaw[] = [
|
||||
// ── 登录页(不经过布局)──
|
||||
@@ -16,17 +19,8 @@ export const constantRoutes: RouteRecordRaw[] = [
|
||||
meta: { title: '登录' },
|
||||
},
|
||||
|
||||
// ── 主布局(动态路由的父容器) ──
|
||||
// name 为 'BasicLayout',动态路由通过 router.addRoute('BasicLayout', child) 挂载到此布局下
|
||||
{
|
||||
path: '/',
|
||||
name: 'BasicLayout',
|
||||
component: () => import('@/layouts/BasicLayout'),
|
||||
children: [],
|
||||
meta: { title: '首页' },
|
||||
},
|
||||
|
||||
// ── 404 ──
|
||||
// 注意: 通配路由必须放在最后,且不能被动态路由覆盖
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'NotFound',
|
||||
|
||||
Reference in New Issue
Block a user