feat: 初始化考培练系统项目

- 从服务器拉取完整代码
- 按框架规范整理项目结构
- 配置 Drone CI 测试环境部署
- 包含后端(FastAPI)、前端(Vue3)、管理端

技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
111
2026-01-24 19:33:28 +08:00
commit 998211c483
1197 changed files with 228429 additions and 0 deletions

View File

@@ -0,0 +1,270 @@
/**
* 路由守卫
* 处理路由权限验证、登录状态检查等
*/
import { Router, RouteLocationNormalized, NavigationGuardNext } from 'vue-router'
import { ElMessage } from 'element-plus'
import { authManager } from '@/utils/auth'
import { loadingManager } from '@/utils/loadingManager'
// 白名单路由(不需要登录)
const WHITE_LIST = ['/login', '/register', '/404']
// 需要特殊权限的路由映射
const ROUTE_PERMISSIONS: Record<string, string[]> = {
'/admin': ['admin'],
'/manager': ['manager', 'admin'],
'/analysis': ['manager', 'admin']
}
/**
* 设置路由守卫
*/
export function setupRouterGuard(router: Router) {
// 全局前置守卫
router.beforeEach(async (to, from, next) => {
// 显示页面加载状态
loadingManager.start('page-loading', {
message: '页面加载中...',
background: 'rgba(255, 255, 255, 0.8)'
})
try {
await handleRouteGuard(to, from, next)
} catch (error) {
console.error('Route guard error:', error)
ElMessage.error('页面加载失败,请重试')
next('/404')
}
})
// 全局后置守卫
router.afterEach((to, from) => {
// 隐藏页面加载状态
loadingManager.stop('page-loading')
// 设置页面标题
document.title = getPageTitle(to.meta?.title as string)
// 记录页面访问日志
if (process.env.NODE_ENV === 'development') {
console.log(`[Router] Navigate from ${from.path} to ${to.path}`)
}
})
// 路由错误处理
router.onError((error) => {
console.error('Router error:', error)
ElMessage.error('路由加载失败')
loadingManager.stop('page-loading')
})
}
/**
* 处理路由守卫逻辑
*/
async function handleRouteGuard(
to: RouteLocationNormalized,
_from: RouteLocationNormalized,
next: NavigationGuardNext
) {
const { path } = to
// 白名单路由直接通过
if (WHITE_LIST.includes(path)) {
// 如果已登录用户访问登录页,重定向到首页
if (path === '/login' && authManager.isAuthenticated()) {
next(authManager.getDefaultRoute())
return
}
next()
return
}
// 检查登录状态
if (!authManager.isAuthenticated()) {
ElMessage.warning('请先登录')
next(`/login?redirect=${encodeURIComponent(path)}`)
return
}
// 检查token是否过期
if (authManager.isTokenExpired()) {
try {
// 尝试刷新token
await authManager.refreshToken()
} catch (error) {
ElMessage.error('登录已过期,请重新登录')
next(`/login?redirect=${encodeURIComponent(path)}`)
return
}
}
// 检查路由权限
if (!checkRoutePermission(path)) {
ElMessage.error('您没有访问此页面的权限')
// 根据用户角色重定向到合适的页面
next(authManager.getDefaultRoute())
return
}
// 检查特殊路由规则
if (!checkSpecialRouteRules(to)) {
ElMessage.error('访问被拒绝')
next(authManager.getDefaultRoute())
return
}
next()
}
/**
* 检查路由权限
*/
function checkRoutePermission(path: string): boolean {
// 检查是否可以访问路由
if (!authManager.canAccessRoute(path)) {
return false
}
// 检查特定路由的权限要求
for (const [routePrefix, roles] of Object.entries(ROUTE_PERMISSIONS)) {
if (path.startsWith(routePrefix)) {
const userRole = authManager.getUserRole()
if (!userRole || !roles.includes(userRole)) {
return false
}
}
}
return true
}
/**
* 检查特殊路由规则
*/
function checkSpecialRouteRules(to: RouteLocationNormalized): boolean {
const { path, params } = to
// 检查用户ID参数权限只能访问自己的数据管理员除外
if (params.userId && !authManager.isAdmin()) {
const currentUser = authManager.getCurrentUser()
if (currentUser && String(params.userId) !== String(currentUser.id)) {
return false
}
}
// 检查团队ID参数权限
if (params.teamId && !authManager.isAdmin()) {
// 这里可以添加团队权限检查逻辑
// 暂时允许通过,实际项目中需要检查用户是否属于该团队
}
// 检查课程访问权限
if (path.includes('/course/') && params.courseId) {
// 这里可以添加课程访问权限检查
// 例如检查课程是否分配给用户的岗位
}
return true
}
/**
* 获取页面标题
*/
function getPageTitle(title?: string): string {
const appTitle = '考培练系统'
return title ? `${title} - ${appTitle}` : appTitle
}
/**
* 动态添加路由(用于角色权限路由)
*/
export function addDynamicRoutes(router: Router) {
const userRole = authManager.getUserRole()
if (!userRole) return
// 根据角色动态添加路由
const dynamicRoutes = getDynamicRoutesByRole(userRole)
dynamicRoutes.forEach(route => {
router.addRoute(route)
})
}
/**
* 根据角色获取动态路由
*/
function getDynamicRoutesByRole(role: string) {
const routes: any[] = []
// 根据角色添加不同的路由
switch (role) {
case 'admin':
// 管理员可以访问所有路由
break
case 'manager':
// 管理者路由
break
case 'trainee':
// 学员路由
break
}
return routes
}
/**
* 检查页面权限(组件内使用)
*/
export function checkPagePermission(permissions: string[]): boolean {
if (!permissions || permissions.length === 0) {
return true
}
const userRole = authManager.getUserRole()
if (!userRole) return false
return permissions.includes(userRole) || authManager.isAdmin()
}
/**
* 权限指令v-permission
*/
export const permissionDirective = {
mounted(el: HTMLElement, binding: any) {
const { value } = binding
if (value && !checkPagePermission(Array.isArray(value) ? value : [value])) {
el.style.display = 'none'
}
},
updated(el: HTMLElement, binding: any) {
const { value } = binding
if (value && !checkPagePermission(Array.isArray(value) ? value : [value])) {
el.style.display = 'none'
} else {
el.style.display = ''
}
}
}
/**
* 路由元信息接口扩展
*/
declare module 'vue-router' {
interface RouteMeta {
title?: string
icon?: string
hidden?: boolean
roles?: string[]
permissions?: string[]
keepAlive?: boolean
affix?: boolean
breadcrumb?: boolean
activeMenu?: string
}
}