mirror of
https://gitee.com/ccnetcore/Yi
synced 2026-04-11 19:56:37 +08:00
feat: 项目加载优化
This commit is contained in:
@@ -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();
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
Reference in New Issue
Block a user