jeecgboot vue 路由 拆分01

框架

入口
Go 复制代码
async function bootstrap(props?: MainAppProps) {
  // 创建应用实例
  const app = createApp(App);
  // 【QQYUN-6329】
  window['JAppRootInstance'] = app;

  // 创建路由
  createRouter();

  // 配置存储
  setupStore(app);

  // 配置参数
  setupProps(props);

  // 多语言配置,异步情况:语言文件可以从服务器端获得
  await setupI18n(app);

  // 初始化内部系统配置
  initAppConfigStore();

  // 注册外部模块路由(注册online模块lib)
  registerPackages(app);

  // 注册全局组件
  registerGlobComp(app);

  //CAS单点登录
  await useSso().ssoLogin();

  // 注册super应用路由
  await registerSuper(app);
  
  // 配置路由
  setupRouter(app);

  // 路由保护
  setupRouterGuard(router);

  // 注册全局指令
  setupGlobDirectives(app);

  // 配置全局错误处理
  setupErrorHandle(app);

  // 注册第三方组件
  await registerThirdComp(app);

  // 配置electron
  setupElectron(app)

  // 当路由准备好时再执行挂载( https://next.router.vuejs.org/api/#isready)
  await router.isReady();

  // 挂载应用
  app.mount(getMountContainer(props), true);

  console.log(" vue3 app 加载完成!")

  return app
}
路由的加载
java 复制代码
// 1. 引入依赖
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'

// 2. 路由规则
const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
]

// 3. 创建路由实例
// 两种模式:history / hash(二选一)
const router = createRouter({
  // history 模式(URL 干净,无 #)
  history: createWebHistory(),
  
  // hash 模式(URL 带 #,兼容性更好)
  // history: createWebHashHistory(),
  routes,
})

export default router
java 复制代码
export function createRouter() {
  let router = createVueRouter({
    //类型转换写法
      routes: basicRoutes as unknown as RouteRecordRaw[],
      strict: true,
      scrollBehavior: () => ({left: 0, top: 0}),
    },
    // 如果是 Electron 环境,则使用 hash 路由
    $electron.isElectron(),
  )

  // TODO 【QQYUN-4517】【表单设计器】记录分享路由守卫测试
  // @ts-ignore
  //路由
  router.beforeEach(async (to, from, next) => {
    //console.group('【QQYUN-4517】beforeEach');
    //console.warn('from', from);
    //console.warn('to', to);
    //console.groupEnd();
    next();
  });
}
复制代码
basicRoutes 看看路由是怎么写的

路由白名单写法

java 复制代码
export function resetRouter() {
  router.getRoutes().forEach((route) => {
    const { name } = route;
    if (name && !WHITE_NAME_LIST.includes(name as string)) {
      // 不在白名单 = 动态路由/权限路由
      // 退出登录时删除
      router.hasRoute(name) && router.removeRoute(name);
    }
  });
}

递归的一个写法

退出登录时清空动态路由(权限路由) 防止切换用户后,看到上一个用户的路由。

java 复制代码
export function setupRouter(app: App<Element>) {
  app.use(router);
}
java 复制代码
import type { AppRouteRecordRaw, AppRouteModule } from '/@/router/types';

import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/routes/basic';

import { mainOutRoutes } from './mainOut';
import { PageEnum } from '/@/enums/pageEnum';
import { t } from '/@/hooks/web/useI18n';
import { LAYOUT } from '/@/router/constant';
//自动读取 modules 里的所有业务路由(权限路由)
const modules = import.meta.glob('./modules/**/*.ts', { eager: true });

const routeModuleList: AppRouteModule[] = [];

// 加入到路由集合中
Object.keys(modules).forEach((key) => {
  const mod = (modules as Recordable)[key].default || {};
  const modList = Array.isArray(mod) ? [...mod] : [mod];
  routeModuleList.push(...modList);
});
/**
 * 自动读取 modules 文件夹下的所有路由
 * 这些路由 = 需要权限的动态路由
 * → 登录后才会加载
 * → 退出登录会被清空
 */
export const asyncRoutes = [PAGE_NOT_FOUND_ROUTE, ...routeModuleList];
//根路由
export const RootRoute: AppRouteRecordRaw = {
  path: '/',
  name: 'Root',
  redirect: PageEnum.BASE_HOME,
  meta: {
    title: 'Root',
  },
};
//登录路由(所有人可访问)
export const LoginRoute: AppRouteRecordRaw = {
  path: '/login',
  name: 'Login',
  //新版后台登录,如果想要使用旧版登录放开即可
  // component: () => import('/@/views/sys/login/Login.vue'),
  component: () => import('/@/views/system/loginmini/MiniLogin.vue'),
  meta: {
    title: t('routes.basic.login'),
  },
};
//登录路由(所有人可访问)
// 代码逻辑说明: auth2登录页面路由------------
export const Oauth2LoginRoute: AppRouteRecordRaw = {
  path: '/oauth2-app/login',
  name: 'oauth2-app-login',
  //新版钉钉免登录,如果想要使用旧版放开即可
  // component: () => import('/@/views/sys/login/OAuth2Login.vue'),
  component: () => import('/@/views/system/loginmini/OAuth2Login.vue'),
  meta: {
    title: t('routes.oauth2.login'),
  },
};

/**
 * 【通过token直接静默登录】流程办理登录页面 中转跳转
 */
export const TokenLoginRoute: AppRouteRecordRaw = {
  path: '/tokenLogin',
  name: 'TokenLoginRoute',
  component: () => import('/@/views/sys/login/TokenLoginPage.vue'),
  meta: {
    title: '带token登录页面',
    ignoreAuth: true,
  },
};
//basicRoutes 最终导出
// Basic routing without permission
export const basicRoutes = [LoginRoute, RootRoute, ...mainOutRoutes, REDIRECT_ROUTE, PAGE_NOT_FOUND_ROUTE, TokenLoginRoute, Oauth2LoginRoute];

外部模块路由

registerPackages(app) 是 JeecgBoot Vue3 的外部模块注册机制 ,用于实现 monorepo 架构下的按需加载 。

核心作用 :将独立的业务模块(如流程设计器 @jeecg/aiflow )注册到主应用,实现模块解耦和按需加载。

main.ts: bootstrap()

└──→ registerPackages(app) ← 第67行

└──→ appInstance = app ← 仅保存Vue应用实例,不立即加载模块

它是 Vue3 项目里的「微前端 / 模块化按需加载」核心工具,专门负责:项目太大时,只加载当前页面需要的子模块,不加载全部,让项目启动更快、体积更小。

路由访问时 → 识别路径关键字 → 懒加载对应子包 → 注册子路由 → 渲染页面

java 复制代码
async function ensurePackageLoaded(pkgConfig) {
  const { name, importer } = pkgConfig;

  // 防止重复加载
  const promise = importer().then((pkg) => {
    const mod = pkg.default || pkg;
    appInstance.use(mod, installOptions); // 安装子模块
    registerDynamicRouter(mod.getViews); // 注册子模块路由
    return mod;
  });

  return promise;
}
java 复制代码
export async function loadPackageComponent(component: string) {
  // 根据路由路径(如 /aiflow/xxx)自动匹配子包
  const matched = getMatchedPackage(component);
  return findComponentInPackage(matched, component);
}
java 复制代码
function getMatchedPackage(component: string) {
  const lc = component.toLowerCase();
  for (const pkg of lazyPackages) {
    const keyword = pkg.name.split('/').pop(); // aiflow / online
    if (lc.includes(keyword)) return pkg;
  }
}

路由注册解耦

java 复制代码
registerSuper(app) 是 JeecgBoot Vue3 的 自动化模块注册机制 :

1. 扫描 :通过 import.meta.glob 自动发现 super 目录下所有 register.ts 文件
2. 加载 :动态导入每个模块
3. 注册 :执行每个模块的 register(app) 方法,完成组件和路由注册
核心价值 :实现了 插件化的架构设计 ,新增功能模块无需修改主入口代码,只需按照约定创建 register.ts 文件即可自动集成到系统中。
java 复制代码
// airag/aiapp/chat/route/register.ts
export async function register(app: App) {
  await registerMyAppRouter(app);
}

async function registerMyAppRouter(_: App) {
  for(let appRoute of ChatRoutes){
    await router.addRoute(appRoute);
  }
}
java 复制代码
export async function registerSuper(app: App) {
  const modules = import.meta.glob('./**/register.ts');
  for (let [url, module] of Object.entries(modules)) {
    let { register } = await module();
    if (typeof register === 'function') {
      await register(app);
    } else {
      console.error(`${url} 没有导出 register 函数,无法完成注册!`);
    }
  }
}
路由拦截

入口:setupRouterGuard(router) ↓ 按顺序注册 9 个守卫 ↓ 页面跳转 → 所有守卫自动按逻辑执行

复制代码
export function setupRouterGuard(router: Router) {
  createPageGuard(router);
  createPageLoadingGuard(router);
  createHttpGuard(router);
  createScrollGuard(router);
  createMessageGuard(router);
  createProgressGuard(router);
  createPermissionGuard(router);
  createParamMenuGuard(router); // must after createPermissionGuard (menu has been built.)
  createStateGuard(router);
}
  • createPageGuard:记录页面是否加载过(缓存优化)
  • createPageLoadingGuard:页面加载 Loading 状态
  • createHttpGuard :切换路由时取消所有未完成的接口请求
  • createScrollGuard :页面跳转后回到顶部
  • createMessageGuard :切换路由时关闭所有弹窗、提示
  • createProgressGuard:顶部进度条(类似浏览器加载条)
  • createPermissionGuard权限拦截(核心!登录、角色判断)
  • createParamMenuGuard:动态菜单参数处理(必须在权限之后)
  • createStateGuard:页面状态同步 / 异常处理
before逻辑
java 复制代码
// router/guard/permissionGuard.ts
router.beforeEach(async (to, from, next) => {
  // 1️⃣ 获取 Token
  const token = userStore.getToken;
  
  // 2️⃣ 白名单校验
  if (whitePathList.includes(to.path)) { ... }
  
  // 3️⃣ Token 不存在 → 重定向登录
  if (!token) { ... }
  
  // 4️⃣ 构建动态路由
  const routes = await permissionStore.buildRoutesAction();
  routes.forEach(route => router.addRoute(route));
  
  // 5️⃣ 放行
  next();
});
java 复制代码
userStore.getToken
    ↓
store/modules/user.ts: getToken()
    ↓
this.token || getAuthCache(TOKEN_KEY)
    ↓
utils/auth/index.ts: getAuthCache(key)
    ↓
localStorage.getItem(key) 或 sessionStorage.getItem(key)
动态路由
复制代码
const permissionStore = usePermissionStoreWithOut();

┌──────────────────────────────────────────────────────────────────────────────────┐

│ 动态路由构建完整执行链 │

└──────────────────────────────────────────────────────────────────────────────────┘

用户访问需要权限的页面

router.beforeEach → permissionGuard.ts

└──→ permissionStore.buildRoutesAction()

└──→ 根据权限模式选择构建策略

├── ROLE: 前端路由 + 角色过滤

├── ROUTE_MAPPING: 前端路由 + 路由映射

└── BACK: 🔵 后台菜单构建(主要模式)

├──→ changePermissionCode()

│ └──→ getBackMenuAndPerms()

│ └──→ axios.get('/sys/permission/getUserPermissionByToken')

│ └──→ 后端返回用户权限菜单

├──→ addSlashToRouteComponent(routeList)

│ └──→ 组件路径前加斜杠

├──→ transformObjToRoute(routeList)

│ └──→ 将后端菜单转为路由对象

├──→ transformRouteToMenu(routeList)

│ └──→ 路由转菜单结构

└──→ flatMultiLevelRoutes(routeList)

└──→ 多级路由扁平化

└──→ 返回路由数组

┌──────────────────────────────────────────────────────────────────────────────────┐

│ 数据流转流程 │

└──────────────────────────────────────────────────────────────────────────────────┘

后端接口返回

{

codeList: 'sys:user:list', 'sys:user:add', // 权限码

menu: [ // 菜单路由

{ path: '/system/user', component: 'system/user/index', ... },

{ path: '/system/role', component: 'system/role/index', ... },

],

auth: ..., // 权限列表

allAuth: ..., // 全部权限配置

sysSafeMode: false // 安全模式

}

addSlashToRouteComponent()

{ component: '/system/user/index', ... }

transformObjToRoute()

{

path: '',

name: 'system-user-indexParent',

component: LAYOUT,

children: [{

path: '/system/user/index',

component: () => import('/@/views/system/user/index.vue'),

...

}]

}

flatMultiLevelRoutes()

二级路由结构(扁平化后)

router.addRoute() × N

  1. main.ts 启动 ↓

  2. setupRouter(app) 执行 app.use(router) 【先安装路由】 ↓

  3. setupRouterGuard(router) 注册守卫 ↓

  4. 页面跳转 → 进入权限拦截

↓ 5. buildRoutesAction() 获取后端菜单 ↓

  1. 执行 router.addRoute(route) 【后添加路由】 ✅
相关推荐
ZC跨境爬虫1 小时前
跟着 MDN 学CSS day_46:(响应式实战——用媒体查询打造双列布局)
前端·css·ui·html·tensorflow·媒体
狗凯之家源码网1 小时前
多语言企鹅养殖投资返利系统 自定义产品配置 一键部署源码
前端·架构·php
每天吃饭的羊1 小时前
LeetCode 链表
前端
神仙别闹1 小时前
VUE框架 + Element UI + Node 模拟打印机的 Web 即时打印
前端·vue.js·ui
vivo互联网技术1 小时前
把输入框变成 AI 的“超级入口”(ProseMirror 全流程实战)
前端·agent
lunzi_08261 小时前
《图解HTTP》--第5章-与HTTP协作的Web服务器
服务器·前端·http
李剑一1 小时前
面试第一关!面试官:讲一下事件循环机制,宏&微任务,还有渲染时机
前端·面试
shuoshuohaohao1 小时前
《CSS》
前端·css