chore(config): 初始化项目配置文件
- 添加 .editorconfig 文件统一代码风格 - 配置 .env.development 环境变量文件 - 创建 .env.example 环境变量示例文件 - 设置 .eslintignore 忽略检查规则 - 配置 .eslintrc.js 代码检查规则 - 添加 .gitignore 文件忽略版本控制 - 设置 .prettierignore 忽略格式化规则 - 新增隐私政策HTML页面文件 - 创建API密钥编辑组件基础结构
This commit is contained in:
86
src/router/index.ts
Normal file
86
src/router/index.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 路由配置
|
||||
*/
|
||||
import NProgress from 'nprogress';
|
||||
import type { _RouteLocationBase } from 'vue-router';
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import { WHITE_LIST, REDIRECT_PATH, LAYOUT_PATH } from '@/config/setting';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { getToken } from '@/utils/token-util';
|
||||
import { routes, getMenuRoutes } from './routes';
|
||||
// import { useTenantStore } from '@/store/modules/tenant';
|
||||
|
||||
NProgress.configure({
|
||||
speed: 200,
|
||||
minimum: 0.02,
|
||||
trickleSpeed: 200,
|
||||
showSpinner: false
|
||||
});
|
||||
|
||||
const router = createRouter({
|
||||
routes,
|
||||
history: createWebHistory(),
|
||||
scrollBehavior() {
|
||||
return { top: 0 };
|
||||
}
|
||||
});
|
||||
|
||||
// 标记动态路由是否已经注册
|
||||
let dynamicRoutesRegistered = false;
|
||||
|
||||
// 重置动态路由注册状态的函数
|
||||
export function resetDynamicRoutes() {
|
||||
dynamicRoutesRegistered = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由守卫
|
||||
*/
|
||||
router.beforeEach(async (to, from) => {
|
||||
if (!from.path.includes(REDIRECT_PATH)) {
|
||||
NProgress.start();
|
||||
}
|
||||
// 租户信息
|
||||
// const tenantStore = useTenantStore();
|
||||
// await tenantStore.fetchTenantInfo();
|
||||
if (!getToken()) {
|
||||
// 未登录跳转登录界面
|
||||
if (!WHITE_LIST.includes(to.path)) {
|
||||
return {
|
||||
path: '/login',
|
||||
query: to.path === LAYOUT_PATH ? {} : { from: to.path }
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 注册动态路由
|
||||
const userStore = useUserStore();
|
||||
if (!userStore.menus && !dynamicRoutesRegistered) {
|
||||
const { menus, homePath } = await userStore.fetchUserInfo();
|
||||
if (menus) {
|
||||
const menuRoute = getMenuRoutes(menus, homePath);
|
||||
router.addRoute(menuRoute);
|
||||
dynamicRoutesRegistered = true;
|
||||
|
||||
// 只有当访问根路径时才跳转到首页
|
||||
if (to.path === LAYOUT_PATH) {
|
||||
return { path: homePath || '/dashboard', replace: true };
|
||||
}
|
||||
|
||||
// 对于其他路径,只有在路由确实不存在时才跳转
|
||||
// 这避免了已存在页面的不必要跳转
|
||||
return { ...to, replace: true };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
router.afterEach((to) => {
|
||||
if (!to.path.includes(REDIRECT_PATH) && NProgress.isStarted()) {
|
||||
setTimeout(() => {
|
||||
NProgress.done(true);
|
||||
}, 200);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
313
src/router/performance.ts
Normal file
313
src/router/performance.ts
Normal file
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
* 路由性能优化
|
||||
*/
|
||||
import type { Router, RouteLocationNormalized } from 'vue-router';
|
||||
import { routePerformanceMonitor } from '@/utils/performance';
|
||||
import { componentPreloader } from '@/utils/lazy-load';
|
||||
|
||||
// 路由预加载配置
|
||||
const PRELOAD_ROUTES = [
|
||||
'/dashboard',
|
||||
'/user/profile',
|
||||
'/system/user',
|
||||
'/system/role'
|
||||
];
|
||||
|
||||
// 路由性能优化类
|
||||
export class RoutePerformanceOptimizer {
|
||||
private router: Router;
|
||||
private preloadTimer: number | null = null;
|
||||
|
||||
constructor(router: Router) {
|
||||
this.router = router;
|
||||
this.setupOptimizations();
|
||||
}
|
||||
|
||||
private setupOptimizations() {
|
||||
// 路由性能监控
|
||||
this.setupPerformanceMonitoring();
|
||||
|
||||
// 路由预加载
|
||||
this.setupRoutePreloading();
|
||||
|
||||
// 路由缓存优化
|
||||
this.setupRouteCaching();
|
||||
}
|
||||
|
||||
// 设置性能监控
|
||||
private setupPerformanceMonitoring() {
|
||||
this.router.beforeEach((to, from) => {
|
||||
routePerformanceMonitor.startRouteTimer();
|
||||
return true;
|
||||
});
|
||||
|
||||
this.router.afterEach((to, from) => {
|
||||
routePerformanceMonitor.endRouteTimer(to.path);
|
||||
});
|
||||
}
|
||||
|
||||
// 设置路由预加载
|
||||
private setupRoutePreloading() {
|
||||
this.router.afterEach((to) => {
|
||||
// 延迟预加载相关路由
|
||||
if (this.preloadTimer) {
|
||||
clearTimeout(this.preloadTimer);
|
||||
}
|
||||
|
||||
this.preloadTimer = window.setTimeout(() => {
|
||||
this.preloadRelatedRoutes(to);
|
||||
}, 2000); // 2秒后开始预加载
|
||||
});
|
||||
}
|
||||
|
||||
// 预加载相关路由
|
||||
private preloadRelatedRoutes(currentRoute: RouteLocationNormalized) {
|
||||
const routesToPreload = this.getRelatedRoutes(currentRoute.path);
|
||||
|
||||
routesToPreload.forEach(routePath => {
|
||||
const route = this.router.resolve(routePath);
|
||||
if (route.matched.length > 0) {
|
||||
const component = route.matched[route.matched.length - 1].components?.default;
|
||||
if (component && typeof component === 'function') {
|
||||
componentPreloader.preload(routePath, component as () => Promise<any>);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 获取相关路由
|
||||
private getRelatedRoutes(currentPath: string): string[] {
|
||||
const related: string[] = [];
|
||||
|
||||
// 根据当前路径推断可能访问的路由
|
||||
if (currentPath === '/') {
|
||||
related.push(...PRELOAD_ROUTES);
|
||||
} else if (currentPath.startsWith('/system')) {
|
||||
related.push('/system/user', '/system/role', '/system/menu');
|
||||
} else if (currentPath.startsWith('/cms')) {
|
||||
related.push('/cms/dashboard', '/cms/setting');
|
||||
} else if (currentPath.startsWith('/bszx')) {
|
||||
related.push('/bszx/dashboard', '/bszx/ranking');
|
||||
}
|
||||
|
||||
return related.filter(path => path !== currentPath);
|
||||
}
|
||||
|
||||
// 设置路由缓存
|
||||
private setupRouteCaching() {
|
||||
// 这里可以实现路由级别的缓存策略
|
||||
// 例如缓存路由组件、路由数据等
|
||||
}
|
||||
|
||||
// 清理资源
|
||||
destroy() {
|
||||
if (this.preloadTimer) {
|
||||
clearTimeout(this.preloadTimer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 路由懒加载优化
|
||||
export function optimizedLazyRoute(loader: () => Promise<any>) {
|
||||
return async () => {
|
||||
// 检查是否已经预加载
|
||||
const preloaded = componentPreloader.get(loader.toString());
|
||||
if (preloaded) {
|
||||
return preloaded;
|
||||
}
|
||||
|
||||
// 正常加载
|
||||
return loader();
|
||||
};
|
||||
}
|
||||
|
||||
// 智能路由预取
|
||||
export class SmartRoutePrefetcher {
|
||||
private router: Router;
|
||||
private prefetchQueue: Set<string> = new Set();
|
||||
private isIdle = true;
|
||||
|
||||
constructor(router: Router) {
|
||||
this.router = router;
|
||||
this.setupIdleDetection();
|
||||
}
|
||||
|
||||
// 设置空闲检测
|
||||
private setupIdleDetection() {
|
||||
let idleTimer: number;
|
||||
|
||||
const resetIdleTimer = () => {
|
||||
this.isIdle = false;
|
||||
clearTimeout(idleTimer);
|
||||
idleTimer = window.setTimeout(() => {
|
||||
this.isIdle = true;
|
||||
this.processPrefetchQueue();
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
// 监听用户活动
|
||||
['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart'].forEach(event => {
|
||||
document.addEventListener(event, resetIdleTimer, true);
|
||||
});
|
||||
|
||||
resetIdleTimer();
|
||||
}
|
||||
|
||||
// 添加到预取队列
|
||||
addToPrefetchQueue(routePath: string) {
|
||||
this.prefetchQueue.add(routePath);
|
||||
|
||||
if (this.isIdle) {
|
||||
this.processPrefetchQueue();
|
||||
}
|
||||
}
|
||||
|
||||
// 处理预取队列
|
||||
private async processPrefetchQueue() {
|
||||
if (!this.isIdle || this.prefetchQueue.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const routePath = this.prefetchQueue.values().next().value;
|
||||
this.prefetchQueue.delete(routePath);
|
||||
|
||||
try {
|
||||
const route = this.router.resolve(routePath);
|
||||
if (route.matched.length > 0) {
|
||||
const component = route.matched[route.matched.length - 1].components?.default;
|
||||
if (component && typeof component === 'function') {
|
||||
await componentPreloader.preload(routePath, component as () => Promise<any>);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Failed to prefetch route ${routePath}:`, error);
|
||||
}
|
||||
|
||||
// 继续处理队列
|
||||
if (this.isIdle && this.prefetchQueue.size > 0) {
|
||||
setTimeout(() => this.processPrefetchQueue(), 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 路由性能分析器
|
||||
export class RoutePerformanceAnalyzer {
|
||||
private router: Router;
|
||||
private performanceData: Map<string, number[]> = new Map();
|
||||
|
||||
constructor(router: Router) {
|
||||
this.router = router;
|
||||
this.setupAnalysis();
|
||||
}
|
||||
|
||||
private setupAnalysis() {
|
||||
let startTime: number;
|
||||
|
||||
this.router.beforeEach((to) => {
|
||||
startTime = performance.now();
|
||||
return true;
|
||||
});
|
||||
|
||||
this.router.afterEach((to) => {
|
||||
const duration = performance.now() - startTime;
|
||||
this.recordPerformance(to.path, duration);
|
||||
});
|
||||
}
|
||||
|
||||
private recordPerformance(path: string, duration: number) {
|
||||
if (!this.performanceData.has(path)) {
|
||||
this.performanceData.set(path, []);
|
||||
}
|
||||
|
||||
const data = this.performanceData.get(path)!;
|
||||
data.push(duration);
|
||||
|
||||
// 只保留最近10次记录
|
||||
if (data.length > 10) {
|
||||
data.shift();
|
||||
}
|
||||
}
|
||||
|
||||
// 获取性能报告
|
||||
getPerformanceReport() {
|
||||
const report: Record<string, any> = {};
|
||||
|
||||
this.performanceData.forEach((durations, path) => {
|
||||
const avg = durations.reduce((sum, d) => sum + d, 0) / durations.length;
|
||||
const min = Math.min(...durations);
|
||||
const max = Math.max(...durations);
|
||||
|
||||
report[path] = {
|
||||
average: Math.round(avg),
|
||||
min: Math.round(min),
|
||||
max: Math.round(max),
|
||||
count: durations.length
|
||||
};
|
||||
});
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
// 获取慢路由
|
||||
getSlowRoutes(threshold: number = 1000) {
|
||||
const slowRoutes: Array<{ path: string; avgTime: number }> = [];
|
||||
|
||||
this.performanceData.forEach((durations, path) => {
|
||||
const avg = durations.reduce((sum, d) => sum + d, 0) / durations.length;
|
||||
if (avg > threshold) {
|
||||
slowRoutes.push({ path, avgTime: Math.round(avg) });
|
||||
}
|
||||
});
|
||||
|
||||
return slowRoutes.sort((a, b) => b.avgTime - a.avgTime);
|
||||
}
|
||||
}
|
||||
|
||||
// 路由缓存管理器
|
||||
export class RouteCacheManager {
|
||||
private cache = new Map<string, any>();
|
||||
private maxSize: number;
|
||||
|
||||
constructor(maxSize: number = 20) {
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
// 缓存路由数据
|
||||
set(key: string, data: any) {
|
||||
if (this.cache.size >= this.maxSize) {
|
||||
const firstKey = this.cache.keys().next().value;
|
||||
this.cache.delete(firstKey);
|
||||
}
|
||||
|
||||
this.cache.set(key, {
|
||||
data,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
// 获取缓存数据
|
||||
get(key: string, maxAge: number = 5 * 60 * 1000) {
|
||||
const cached = this.cache.get(key);
|
||||
|
||||
if (!cached) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Date.now() - cached.timestamp > maxAge) {
|
||||
this.cache.delete(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
// 清除缓存
|
||||
clear() {
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
// 删除特定缓存
|
||||
delete(key: string) {
|
||||
return this.cache.delete(key);
|
||||
}
|
||||
}
|
||||
128
src/router/routes.ts
Normal file
128
src/router/routes.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import type { MenuItemType } from 'ele-admin-pro/es';
|
||||
import { menuToRoutes, eachTreeData } from 'ele-admin-pro/es';
|
||||
import { HOME_PATH, LAYOUT_PATH, REDIRECT_PATH } from '@/config/setting';
|
||||
import EleLayout from '@/layout/index.vue';
|
||||
import RedirectLayout from '@/components/RedirectLayout';
|
||||
const modules = import.meta.glob('/src/views/**/index.vue');
|
||||
|
||||
/**
|
||||
* 静态路由
|
||||
*/
|
||||
export const routes = [
|
||||
{
|
||||
path: '/login',
|
||||
component: () => import('@/views/passport/login/index.vue'),
|
||||
meta: { title: '登录' }
|
||||
},
|
||||
{
|
||||
path: '/token-login',
|
||||
component: () => import('@/views/passport/loginToken/index.vue'),
|
||||
meta: { title: 'token登录' }
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
component: () => import('@/views/passport/register/index.vue'),
|
||||
meta: { title: '注册' }
|
||||
},
|
||||
{
|
||||
path: '/bszx/pay-cert/:id',
|
||||
component: () => import('@/views/bszx/bszxPayCert/index.vue'),
|
||||
meta: { title: '查看证书' }
|
||||
},
|
||||
{
|
||||
path: '/dealer/register',
|
||||
component: () => import('@/views/passport/dealer/register.vue'),
|
||||
meta: { title: '邀请注册' }
|
||||
},
|
||||
{
|
||||
path: '/qr-confirm',
|
||||
component: () => import('@/views/passport/qrConfirm/index.vue'),
|
||||
meta: { title: '扫码登录确认' }
|
||||
},
|
||||
{
|
||||
path: '/qr-demo',
|
||||
component: () => import('@/components/QrLogin/demo.vue'),
|
||||
meta: { title: '二维码登录演示' }
|
||||
},
|
||||
{
|
||||
path: '/merchant/apply',
|
||||
component: () => import('@/views/passport/merchant/apply.vue'),
|
||||
meta: { title: '商家入驻申请' }
|
||||
},
|
||||
{
|
||||
path: '/merchant/success',
|
||||
component: () => import('@/views/passport/merchant/success.vue'),
|
||||
meta: { title: '申请提交成功' }
|
||||
},
|
||||
// {
|
||||
// path: '/forget',
|
||||
// component: () => import('@/views/passport/forget/index.vue'),
|
||||
// meta: { title: '忘记密码' }
|
||||
// },
|
||||
// {
|
||||
// path: '/wx-work-login',
|
||||
// component: () => import('@/views/passport/wx-work/index.vue'),
|
||||
// meta: { title: '企业微信登录' }
|
||||
// },
|
||||
// {
|
||||
// path: '/token-login',
|
||||
// component: () => import('@/views/passport/token-login/index.vue'),
|
||||
// meta: { title: '快捷登录' }
|
||||
// },
|
||||
// {
|
||||
// path: '/cms/category/:id',
|
||||
// component: () => import('@/views/cms/category/preview/index.vue'),
|
||||
// meta: { title: '文章列表' }
|
||||
// },
|
||||
// {
|
||||
// path: '/cms/article/:id',
|
||||
// component: () => import('@/views/cms/article/preview/index.vue'),
|
||||
// meta: { title: '文章详情' }
|
||||
// },
|
||||
// 404
|
||||
{
|
||||
path: '/:path(.*)*',
|
||||
component: () => import('@/views/result/fail/index.vue')
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* 动态路由
|
||||
* @param menus 菜单数据
|
||||
* @param homePath 主页地址
|
||||
*/
|
||||
export function getMenuRoutes(menus?: MenuItemType[], homePath?: string) {
|
||||
const routes: RouteRecordRaw[] = [
|
||||
// 用于刷新的路由
|
||||
{
|
||||
path: REDIRECT_PATH + '/:path(.*)',
|
||||
component: RedirectLayout,
|
||||
meta: { hideFooter: true }
|
||||
}
|
||||
];
|
||||
// 路由铺平处理
|
||||
eachTreeData(menuToRoutes(menus, getComponent), (route) => {
|
||||
routes.push(Object.assign({}, route, { children: void 0 }));
|
||||
});
|
||||
return {
|
||||
path: LAYOUT_PATH,
|
||||
component: EleLayout,
|
||||
redirect: HOME_PATH ?? homePath,
|
||||
children: routes
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析路由组件
|
||||
* @param component 组件名称
|
||||
*/
|
||||
function getComponent(component?: string) {
|
||||
if (component) {
|
||||
const module = modules[`/src/views/${component}.vue`];
|
||||
if (!module) {
|
||||
return modules[`/src/views/${component}/index.vue`];
|
||||
}
|
||||
return module;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user