框架
入口
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
main.ts 启动 ↓
setupRouter(app) 执行 app.use(router) 【先安装路由】 ↓
setupRouterGuard(router) 注册守卫 ↓
页面跳转 → 进入权限拦截
↓ 5. buildRoutesAction() 获取后端菜单 ↓
- 执行 router.addRoute(route) 【后添加路由】 ✅
