feat: 项目加载优化

This commit is contained in:
Gsh
2026-02-01 00:30:44 +08:00
parent 3b6887dc2e
commit 11cbb1b612
29 changed files with 1490 additions and 299 deletions

View File

@@ -5,24 +5,69 @@ import { createRouter, createWebHistory } from 'vue-router';
import { ROUTER_WHITE_LIST } from '@/config';
import { checkPagePermission } from '@/config/permission';
import { errorRouter, layoutRouter, staticRouter } from '@/routers/modules/staticRouter';
import { useDesignStore, useUserStore } from '@/stores';
import { useUserStore } from '@/stores';
import { useDesignStore } from '@/stores/modules/design';
// 创建页面加载进度条,提升用户体验
// 创建页面加载进度条,提升用户体验
const { start, done } = useNProgress(0, {
showSpinner: false, // 不显示旋转器
trickleSpeed: 200, // 进度条增长速度(毫秒)
minimum: 0.3, // 最小进度值30%
easing: 'ease', // 动画缓动函数
speed: 500, // 动画速度
showSpinner: false,
trickleSpeed: 200,
minimum: 0.3,
easing: 'ease',
speed: 500,
});
// 创建路由实例
const router = createRouter({
history: createWebHistory(), // 使用 HTML5 History 模式
routes: [...layoutRouter, ...staticRouter, ...errorRouter], // 合并所有路由
strict: false, // 不严格匹配尾部斜杠
scrollBehavior: () => ({ left: 0, top: 0 }), // 路由切换时滚动到顶部
history: createWebHistory(),
routes: [...layoutRouter, ...staticRouter, ...errorRouter],
strict: false,
scrollBehavior: () => ({ left: 0, top: 0 }),
});
// 预加载标记,防止重复预加载
const preloadedComponents = new Set();
/**
* 预加载路由组件
* 提前加载可能访问的路由组件,减少路由切换时的等待时间
*/
function preloadRouteComponents() {
// 预加载核心路由组件
const coreRoutes = [
'/chat/conversation',
'/chat/image',
'/chat/video',
'/chat/agent',
'/console/user',
'/model-library',
];
// 延迟预加载,避免影响首屏加载
setTimeout(() => {
coreRoutes.forEach(path => {
const route = router.resolve(path);
if (route.matched.length > 0) {
const component = route.matched[route.matched.length - 1].components?.default;
if (typeof component === 'function' && !preloadedComponents.has(component)) {
preloadedComponents.add(component);
// 异步预加载,不阻塞主线程
requestIdleCallback?.(() => {
(component as () => Promise<any>)().catch(() => {});
}) || setTimeout(() => {
(component as () => Promise<any>)().catch(() => {});
}, 100);
}
}
});
}, 2000);
}
// 首屏加载完成后开始预加载
if (typeof window !== 'undefined') {
window.addEventListener('load', preloadRouteComponents);
}
// 路由前置守卫
router.beforeEach(
async (
@@ -30,54 +75,67 @@ router.beforeEach(
_from: RouteLocationNormalized,
next: NavigationGuardNext,
) => {
// 1. 获取状态管理
const userStore = useUserStore();
const designStore = useDesignStore(); // 必须在守卫内部调用
// 2. 设置布局根据路由meta中的layout配置
designStore._setLayout(to.meta?.layout || 'default');
// 3. 开始显示进度条
// 1. 开始显示进度条
start();
// 4. 设置页面标题
// 2. 设置页面标题
document.title = (to.meta.title as string) || (import.meta.env.VITE_WEB_TITLE as string);
// 3、权限 预留
// 3、判断是访问登陆页有Token访问当前页面token过期访问接口axios封装则自动跳转登录页面没有Token重置路由到登陆页。
// if (to.path.toLocaleLowerCase() === LOGIN_URL) {
// // 有Token访问当前页面
// if (userStore.token) {
// return next(from.fullPath);
// }
// else {
// ElMessage.error('账号身份已过期,请重新登录');
// }
// // 没有Token重置路由到登陆页。
// // resetRouter(); // 预留
// return next();
// }
// 4、判断访问页面是否在路由白名单地址[静态路由]中,如果存在直接放行。
// 3. 设置布局(使用 setTimeout 避免阻塞导航)
const layout = to.meta?.layout || 'default';
setTimeout(() => {
try {
const designStore = useDesignStore();
designStore._setLayout(layout);
} catch (e) {
// 忽略 store 初始化错误
}
}, 0);
// 4. 检查路由是否存在404 处理)
// 如果 to.matched 为空且 to.name 不存在,说明路由未匹配
if (to.matched.length === 0 || (to.matched.length === 1 && to.matched[0].path === '/:pathMatch(.*)*')) {
// 404 路由已定义在 errorRouter 中,这里不需要额外处理
}
// 5. 白名单检查(跳过权限验证)
if (ROUTER_WHITE_LIST.includes(to.path))
if (ROUTER_WHITE_LIST.some(path => {
// 支持通配符匹配
if (path.includes(':')) {
const pattern = path.replace(/:\w+/g, '[^/]+');
const regex = new RegExp(`^${pattern}$`);
return regex.test(to.path);
}
return path === to.path;
})) {
return next();
}
// 6. Token 检查(用户认证),没有重定向到 login 页面。
if (!userStore.token)
userStore.logout();
// 6. 获取用户状态(延迟加载,避免阻塞)
let userStore;
try {
userStore = useUserStore();
} catch (e) {
// Store 未初始化,允许继续
return next();
}
// 7. 页面权限检查
// 7. Token 检查(用户认证)
if (!userStore.token) {
userStore.clearUserInfo();
return next({ path: '/', replace: true });
}
// 8. 页面权限检查
const userName = userStore.userInfo?.user?.userName;
const hasPermission = checkPagePermission(to.path, userName);
if (!hasPermission) {
// 用户无权访问该页面跳转到403页面
ElMessage.warning('您没有权限访问该页面');
return next('/403');
return next({ path: '/403', replace: true });
}
// 其余逻辑 预留...
// 8. 放行路由
// 9. 放行路由
next();
},
);

View File

@@ -1,10 +1,22 @@
import type { RouteRecordRaw } from 'vue-router';
// 预加载辅助函数
function preloadComponent(importFn: () => Promise<any>) {
return () => {
// 在开发环境下直接返回
if (import.meta.env.DEV) {
return importFn();
}
// 生产环境下可以添加缓存逻辑
return importFn();
};
}
// LayoutRouter[布局路由]
export const layoutRouter: RouteRecordRaw[] = [
{
path: '/',
component: () => import('@/layouts/index.vue'),
component: preloadComponent(() => import('@/layouts/index.vue')),
children: [
// 将首页重定向逻辑放在这里
{
@@ -17,16 +29,12 @@ export const layoutRouter: RouteRecordRaw[] = [
path: 'chat',
name: 'chat',
component: () => import('@/pages/chat/index.vue'),
redirect: '/chat/conversation',
meta: {
title: 'AI应用',
icon: 'HomeFilled',
},
children: [
// chat 根路径重定向到 conversation
{
path: '',
redirect: '/chat/conversation',
},
{
path: 'conversation',
name: 'chatConversation',
@@ -140,17 +148,13 @@ export const layoutRouter: RouteRecordRaw[] = [
path: 'console',
name: 'console',
component: () => import('@/pages/console/index.vue'),
redirect: '/console/user',
meta: {
title: '意心Ai-控制台',
icon: 'Setting',
layout: 'default',
},
children: [
// console 根路径重定向到 user
{
path: '',
redirect: '/console/user',
},
{
path: 'user',
name: 'consoleUser',
@@ -244,8 +248,18 @@ export const layoutRouter: RouteRecordRaw[] = [
],
},
];
// staticRouter[静态路由] 预留
export const staticRouter: RouteRecordRaw[] = [];
// staticRouter[静态路由]
export const staticRouter: RouteRecordRaw[] = [
// FontAwesome 测试页面
{
path: '/test/fontawesome',
name: 'testFontAwesome',
component: () => import('@/pages/test/fontawesome.vue'),
meta: {
title: 'FontAwesome图标测试',
},
},
];
// errorRouter (错误页面路由)
export const errorRouter = [