From 68598d4776d905eb71806004a7717c004b6c21d5 Mon Sep 17 00:00:00 2001 From: YSRSKB <3470363512@qq.com> Date: Sun, 22 Jun 2025 10:04:07 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E5=9B=9B=E6=AC=A1=E5=89=8D=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 8 +- package.json | 2 +- src/auth/Login.vue | 207 ++++++++++++++++ src/auth/Register.vue | 383 +++++++++++++++++++++++++++++ src/backend/reviews/statistics.vue | 155 +++++++++++- src/components/NotFound.vue | 336 +++++++++++++++++++++++++ src/frontend/FrontLayout.vue | 33 --- src/frontend/Header.vue | 186 -------------- src/router/backend.ts | 131 ++++++++-- src/router/frontend.ts | 99 ++++++-- src/router/index.ts | 158 +++++++++++- src/stores/counter.ts | 34 --- src/stores/user.ts | 38 --- src/utils/auth.ts | 140 +++++++++++ src/views/FrontLayout.vue | 39 +++ src/views/Header.vue | 296 ++++++++++++++++++++++ src/{frontend => views}/Home.vue | 0 17 files changed, 1895 insertions(+), 350 deletions(-) create mode 100644 src/auth/Login.vue create mode 100644 src/auth/Register.vue create mode 100644 src/components/NotFound.vue delete mode 100644 src/frontend/FrontLayout.vue delete mode 100644 src/frontend/Header.vue delete mode 100644 src/stores/counter.ts delete mode 100644 src/stores/user.ts create mode 100644 src/utils/auth.ts create mode 100644 src/views/FrontLayout.vue create mode 100644 src/views/Header.vue rename src/{frontend => views}/Home.vue (100%) diff --git a/package-lock.json b/package-lock.json index 304e0c9..aec2cb3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "axios": "^1.8.4", "echarts": "^5.6.0", "element-plus": "^2.9.10", - "pinia": "^3.0.1", + "pinia": "^3.0.3", "vue": "^3.5.13", "vue-router": "^4.5.1" }, @@ -3082,9 +3082,9 @@ } }, "node_modules/pinia": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.1.tgz", - "integrity": "sha512-WXglsDzztOTH6IfcJ99ltYZin2mY8XZCXujkYWVIJlBjqsP6ST7zw+Aarh63E1cDVYeyUcPCxPHzJpEOmzB6Wg==", + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.3.tgz", + "integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==", "license": "MIT", "dependencies": { "@vue/devtools-api": "^7.7.2" diff --git a/package.json b/package.json index d5b0b90..d810327 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "axios": "^1.8.4", "echarts": "^5.6.0", "element-plus": "^2.9.10", - "pinia": "^3.0.1", + "pinia": "^3.0.3", "vue": "^3.5.13", "vue-router": "^4.5.1" }, diff --git a/src/auth/Login.vue b/src/auth/Login.vue new file mode 100644 index 0000000..85dde74 --- /dev/null +++ b/src/auth/Login.vue @@ -0,0 +1,207 @@ + + + + + \ No newline at end of file diff --git a/src/auth/Register.vue b/src/auth/Register.vue new file mode 100644 index 0000000..e6a3fb0 --- /dev/null +++ b/src/auth/Register.vue @@ -0,0 +1,383 @@ + + + + + \ No newline at end of file diff --git a/src/backend/reviews/statistics.vue b/src/backend/reviews/statistics.vue index 96a77c9..80bdbec 100644 --- a/src/backend/reviews/statistics.vue +++ b/src/backend/reviews/statistics.vue @@ -1,5 +1,156 @@ + + + + \ No newline at end of file diff --git a/src/components/NotFound.vue b/src/components/NotFound.vue new file mode 100644 index 0000000..97f6ace --- /dev/null +++ b/src/components/NotFound.vue @@ -0,0 +1,336 @@ + + + + + \ No newline at end of file diff --git a/src/frontend/FrontLayout.vue b/src/frontend/FrontLayout.vue deleted file mode 100644 index b204890..0000000 --- a/src/frontend/FrontLayout.vue +++ /dev/null @@ -1,33 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/frontend/Header.vue b/src/frontend/Header.vue deleted file mode 100644 index c12295c..0000000 --- a/src/frontend/Header.vue +++ /dev/null @@ -1,186 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/router/backend.ts b/src/router/backend.ts index 7210c73..4b71034 100644 --- a/src/router/backend.ts +++ b/src/router/backend.ts @@ -31,35 +31,136 @@ const backendRouter = [ path: '/admin', component: AdminLayout, redirect: '/admin/dashboard', + meta: { + userType: 'admin' + }, children: [ - { path: 'dashboard', component: Dashboard, meta: { title: '控制台' } }, + { + path: 'dashboard', + component: Dashboard, + meta: { + title: '控制台', + userType: 'admin' + } + }, // 用户管理 - { path: 'studentList', component: StudentList, meta: { title: '学生管理' } }, - { path: 'teacherList', component: TeacherList, meta: { title: '教师管理' } }, - { path: 'users/list', component: UserList, meta: { title: '用户列表' } }, - { path: 'users/status', component: Status, meta: { title: '用户状态管理' } }, + { + path: 'studentList', + component: StudentList, + meta: { + title: '学生管理', + userType: 'admin' + } + }, + { + path: 'teacherList', + component: TeacherList, + meta: { + title: '教师管理', + userType: 'admin' + } + }, + { + path: 'users/list', + component: UserList, + meta: { + title: '用户列表', + userType: 'admin' + } + }, + { + path: 'users/status', + component: Status, + meta: { + title: '用户状态管理', + userType: 'admin' + } + }, // 系部管理 - { path: 'departments/list', component: DepartmentsList, meta: { title: '系部管理' } }, - { path: 'departments/add', component: AddDepartments, meta: { title: '添加系部' } }, - { path: 'departments/required-courses', component: RequiredCourses, meta: { title: '必修课程设置' } }, + { + path: 'departments/list', + component: DepartmentsList, + meta: { + title: '系部管理', + userType: 'admin' + } + }, + { + path: 'departments/add', + component: AddDepartments, + meta: { + title: '添加系部', + userType: 'admin' + } + }, + { + path: 'departments/required-courses', + component: RequiredCourses, + meta: { + title: '必修课程设置', + userType: 'admin' + } + }, // 课程管理 - { path: 'courses/list', component: CoursesList, meta: { title: '课程管理' } }, - { path: 'courses/add', component: AddCourse, meta: { title: '添加课程' } }, - { path: 'courses/enrollment', component: CoursesEnrollment, meta: { title: '状态管理' } }, + { + path: 'courses/list', + component: CoursesList, + meta: { + title: '课程管理', + userType: 'admin' + } + }, + { + path: 'courses/add', + component: AddCourse, + meta: { + title: '添加课程', + userType: 'admin' + } + }, + { + path: 'courses/enrollment', + component: CoursesEnrollment, + meta: { + title: '状态管理', + userType: 'admin' + } + }, // 选课管理 - { path: 'enrollment/statistics', component: Enrollmentstatistics, meta: { title: '统计显示' } }, + { + path: 'enrollment/statistics', + component: Enrollmentstatistics, + meta: { + title: '统计显示', + userType: 'admin' + } + }, // 评价显示模块 - { path: 'reviews/statistics', component: Reviewsstatistics, meta: { title: '评价显示' } }, + { + path: 'reviews/statistics', + component: Reviewsstatistics, + meta: { + title: '评价显示', + userType: 'admin' + } + }, // echarts - { path: 'statistics/overview', component: Statisticsoverview, meta: { title: '图像显示' } }, + { + path: 'statistics/overview', + component: Statisticsoverview, + meta: { + title: '图像显示', + userType: 'admin' + } + }, ] } ]; -export default backendRouter; +export default backendRouter; \ No newline at end of file diff --git a/src/router/frontend.ts b/src/router/frontend.ts index ed753e0..febcde4 100644 --- a/src/router/frontend.ts +++ b/src/router/frontend.ts @@ -1,61 +1,112 @@ -// 根据你的实际项目结构导入文件 -import Home from '@/frontend/Home.vue'; -import MainLayout from '@/frontend/FrontLayout.vue'; +import Home from '@/views/Home.vue'; +import MainLayout from '@/views/FrontLayout.vue'; + +// 教师后台组件 import TeacherLayout from '@/frontend/teacher/TeacherLayout.vue'; import TeacherHome from '@/frontend/teacher/TeacherHome.vue'; -import StudentHome from '@/frontend/student/StudentHome.vue'; import UList15 from '@/frontend/teacher/UList15.vue'; import UList16 from '@/frontend/teacher/UList16.vue'; import UList17 from '@/frontend/teacher/UList17.vue'; import UList13 from '@/frontend/teacher/UList13.vue'; import UList10 from '@/frontend/teacher/UList10.vue'; +// 学生后台组件 +import StudentHome from '@/frontend/student/StudentHome.vue'; + const frontendRouter = [ { - path: '/', + path: '/dashboard', component: MainLayout, children: [ - { path: '', name: 'Home', component: Home }, + { + path: '', + name: 'Dashboard', + component: Home, + meta: { title: '仪表板' } + }, ] }, { path: '/student', component: MainLayout, // 学生端使用主布局 + redirect: '/student/home', children: [ { path: 'home', name: 'StudentHome', component: StudentHome, - meta: { userType: 'student' } + meta: { + userType: 'student', + title: '学生首页' + } } + // 可以在这里添加更多学生页面路由 ] }, { path: '/teacher', - component: TeacherLayout, + component: TeacherLayout, // 教师使用专用布局 + redirect: '/teacher/home', + meta: { + userType: 'teacher' + }, children: [ { path: 'home', name: 'TeacherHome', component: TeacherHome, - meta: { userType: 'teacher' } + meta: { + userType: 'teacher', + title: '教师首页' + } }, - { path: 'ulist15', name: 'UList15', component: UList15 }, - { path: 'ulist16', name: 'UList16', component: UList16 }, - { path: 'ulist17', name: 'UList17', component: UList17 }, - { path: 'ulist10', name: 'UList10', component: UList10 }, // 改为小写 - { path: 'ulist13', name: 'UList13', component: UList13 } + { + path: 'ulist15', + name: 'UList15', + component: UList15, + meta: { + userType: 'teacher', + title: 'UList15' + } + }, + { + path: 'ulist16', + name: 'UList16', + component: UList16, + meta: { + userType: 'teacher', + title: 'UList16' + } + }, + { + path: 'ulist17', + name: 'UList17', + component: UList17, + meta: { + userType: 'teacher', + title: 'UList17' + } + }, + { + path: 'ulist10', + name: 'UList10', + component: UList10, + meta: { + userType: 'teacher', + title: 'UList10' + } + }, + { + path: 'ulist13', + name: 'UList13', + component: UList13, + meta: { + userType: 'teacher', + title: 'UList13' + } + } ] - }, - // 添加重定向,方便访问 - { - path: '/student', - redirect: '/student/home' - }, - { - path: '/teacher', - redirect: '/teacher/home' } -]; // 修复点:此处已正确结束数组定义 +]; export default frontendRouter; \ No newline at end of file diff --git a/src/router/index.ts b/src/router/index.ts index 9e3349d..0bdf772 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,19 +1,151 @@ -// 从 vue-router 导入 createRouter(创建路由实例)和 createWebHistory(使用 HTML5 History 模式) -import { createRouter, createWebHistory } from 'vue-router'; +import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' +import { auth } from '@/utils/auth' -import frontendRouter from './frontend'; -import backendRouter from './backend'; +// 导入路由模块 +import frontendRouter from './frontend' +import backendRouter from './backend' +// 导入组件 +import Login from '@/auth/Login.vue' +import Register from '@/auth/Register.vue' +import NotFound from '@/components/NotFound.vue' +import Home from '@/views/Home.vue' +import FrontLayout from '@/views/FrontLayout.vue' -const routes = [ - ...frontendRouter, - ...backendRouter -]; +// 定义基础路由 +const baseRoutes: RouteRecordRaw[] = [ + { + path: '/', + redirect: '/home' + }, + // 让首页使用布局 + { + path: '/home', + component: FrontLayout, + children: [ + { + path: '', + name: 'Home', + component: Home, + meta: { + public: true, + title: '首页' + } + } + ] + }, + { + path: '/login', + name: 'Login', + component: Login, + meta: { + public: true, + title: '登录' + } + }, + { + path: '/register', + name: 'Register', + component: Register, + meta: { public: true, title: '注册' } + } +] + +// 404 路由 +const fallbackRoutes: RouteRecordRaw[] = [ + { + path: '/:pathMatch(.*)*', + name: 'NotFound', + component: NotFound, + meta: { + title: '页面未找到' + } + } +] + +// 合并所有路由 +const routes: RouteRecordRaw[] = [ + ...baseRoutes, + ...frontendRouter, + ...backendRouter, + ...fallbackRoutes +] // 创建路由实例 -export const router = createRouter({ - history: createWebHistory(), - routes -}); +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes, + scrollBehavior(to, from, savedPosition) { + // 路由切换时的滚动行为 + if (savedPosition) { + return savedPosition + } else { + return { top: 0 } + } + } +}) -export default router; \ No newline at end of file +// 全局前置守卫 +router.beforeEach(async (to, from, next) => { + try { + // 检查用户认证状态 + const isAuthenticated = auth.isAuthenticated() + const isPublicPage = to.meta?.public === true + + console.log(`[Router] 导航到: ${to.path}`) + console.log(`[Router] 认证状态: ${isAuthenticated}`) + console.log(`[Router] 公开页面: ${isPublicPage}`) + + // 设置页面标题 + if (to.meta?.title) { + document.title = `${to.meta.title} - 管理系统` + } + + // 如果是公开页面,直接通过 + if (isPublicPage) { + // 如果已登录用户访问登录页,重定向到对应的仪表板 + if (isAuthenticated && to.path === '/login') { + console.log('[Router] 已登录用户访问登录页,重定向到仪表板') + return next(auth.getDefaultRedirectPath()) + } + return next() + } + + // 需要认证的页面 + if (!isAuthenticated) { + console.log('[Router] 未认证用户访问受保护页面,重定向到登录页') + return next({ + path: '/login', + query: { redirect: to.fullPath } + }) + } + + // 检查用户权限(可选) + const userType = auth.getUserType() + if (to.meta?.userType && userType !== to.meta.userType) { + console.log(`[Router] 用户类型不匹配: ${userType} vs ${to.meta.userType}`) + // 重定向到用户对应的首页 + return next(auth.getDefaultRedirectPath()) + } + + // 认证通过,继续导航 + next() + + } catch (error) { + console.error('[Router] 导航守卫错误:', error) + // 发生错误时重定向到登录页 + next('/login') + } +}) + +// 全局后置钩子 +router.afterEach((to, from) => { + console.log(`[Router] 导航完成: ${from.path} -> ${to.path}`) +}) + +// 路由错误处理 +router.onError((error) => { + console.error('[Router] 路由错误:', error) +}) + +export default router \ No newline at end of file diff --git a/src/stores/counter.ts b/src/stores/counter.ts deleted file mode 100644 index a79803f..0000000 --- a/src/stores/counter.ts +++ /dev/null @@ -1,34 +0,0 @@ -// src/stores/counter.ts -import { defineStore } from 'pinia' - -export const useCounterStore = defineStore('counter', { - state: () => ({ - count: 0 - }), - getters: { - doubleCount(): number { - return this.count * 2; - } - }, - actions: { - // 计数加 1(同步操作) - increment() { - this.count++ - }, - // 异步版本(不会卡住 UI) - async incrementAsync() { - const start = Date.now(); - while (Date.now() - start < 5000) { - await new Promise(resolve => setTimeout(resolve, 0)) // 让出主线程 - } - this.count++ - }, - // 同步阻塞版本(会卡住 5 秒,UI 无响应) - incrementSyncBlocking() { - const start = Date.now(); - while (Date.now() - start < 5000) { - } - this.count++; - } - } -}) \ No newline at end of file diff --git a/src/stores/user.ts b/src/stores/user.ts deleted file mode 100644 index 70e422d..0000000 --- a/src/stores/user.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { defineStore } from 'pinia'; - -// 定义用户信息的 store 类型 -interface UserState { - username: string; - isLoggedIn: boolean; -} - -// 创建一个名为 "user" 的 store -export const useUserStore = defineStore('user', { - state: (): UserState => ({ - username: '', - isLoggedIn: false, - }), - - // Getters 用来计算派生状态 - getters: { - // 获取用户登录的基本信息 - userProfile(state) { - return state.isLoggedIn ? `用户名: ${state.username}` : '用户未登录'; - } - }, - - // Actions 用来修改状态,包含同步和异步操作 - actions: { - // 用户登录 - login(username: string) { - this.username = username; - this.isLoggedIn = true; - }, - - // 用户登出 - logout() { - this.username = ''; - this.isLoggedIn = false; - } - } -}); \ No newline at end of file diff --git a/src/utils/auth.ts b/src/utils/auth.ts new file mode 100644 index 0000000..08e170c --- /dev/null +++ b/src/utils/auth.ts @@ -0,0 +1,140 @@ +// 认证工具类 +export class Auth { + private tokenKey = 'token' + private userInfoKey = 'userInfo' + + /** + * 检查用户是否已认证 + */ + isAuthenticated(): boolean { + const token = this.getToken() + return !!token && !this.isTokenExpired(token) + } + + /** + * 获取存储的token + */ + getToken(): string | null { + try { + return localStorage.getItem(this.tokenKey) + } catch (error) { + console.error('获取token失败:', error) + return null + } + } + + /** + * 保存token + */ + setToken(token: string): void { + try { + localStorage.setItem(this.tokenKey, token) + } catch (error) { + console.error('保存token失败:', error) + } + } + + /** + * 移除token + */ + removeToken(): void { + try { + localStorage.removeItem(this.tokenKey) + localStorage.removeItem(this.userInfoKey) + } catch (error) { + console.error('移除token失败:', error) + } + } + + /** + * 检查token是否过期 + */ + private isTokenExpired(token: string): boolean { + try { + // 如果token包含JWT格式 + if (token.includes('.')) { + const payload = JSON.parse(atob(token.split('.')[1])) + const currentTime = Math.floor(Date.now() / 1000) + return payload.exp ? payload.exp < currentTime : false + } + // 简单token,假设有效 + return false + } catch (error) { + // 如果解析失败,认为token可能仍然有效(非JWT格式) + return false + } + } + + /** + * 保存用户信息 + */ + setUserInfo(userInfo: any): void { + try { + localStorage.setItem(this.userInfoKey, JSON.stringify(userInfo)) + } catch (error) { + console.error('保存用户信息失败:', error) + } + } + + /** + * 获取用户信息 + */ + getUserInfo(): any { + try { + const userInfoStr = localStorage.getItem(this.userInfoKey) + if (userInfoStr) { + return JSON.parse(userInfoStr) + } + + // 如果没有单独存储的用户信息,尝试从token解析 + const token = this.getToken() + if (!token) return null + + if (token.includes('.')) { + const payload = JSON.parse(atob(token.split('.')[1])) + return payload + } + + return null + } catch (error) { + console.error('获取用户信息失败:', error) + return null + } + } + + /** + * 获取用户类型 + */ + getUserType(): string | null { + const userInfo = this.getUserInfo() + return userInfo ? userInfo.userType || userInfo.role || null : null + } + + /** + * 根据用户类型获取默认重定向路径 + */ + getDefaultRedirectPath(): string { + const userType = this.getUserType() + switch (userType) { + case 'admin': + return '/admin/dashboard' + case 'teacher': + return '/teacher/home' + case 'student': + return '/student/home' + default: + return '/dashboard' + } + } + + /** + * 登出 + */ + logout(): void { + this.removeToken() + // 可以在这里添加其他登出逻辑,比如清除其他缓存数据 + } +} + +// 导出单例实例 +export const auth = new Auth() \ No newline at end of file diff --git a/src/views/FrontLayout.vue b/src/views/FrontLayout.vue new file mode 100644 index 0000000..779c013 --- /dev/null +++ b/src/views/FrontLayout.vue @@ -0,0 +1,39 @@ + + + + + \ No newline at end of file diff --git a/src/views/Header.vue b/src/views/Header.vue new file mode 100644 index 0000000..043a1a4 --- /dev/null +++ b/src/views/Header.vue @@ -0,0 +1,296 @@ + + + + + \ No newline at end of file diff --git a/src/frontend/Home.vue b/src/views/Home.vue similarity index 100% rename from src/frontend/Home.vue rename to src/views/Home.vue