├── viteenv ├── .env.development └── .env.production ├── public ├── favicon.ico └── vite.svg ├── src ├── assets │ ├── logo.png │ ├── logo2.png │ ├── banner01.jpg │ ├── system-bg.jpg │ ├── 404_images │ │ ├── 404.png │ │ ├── 404_bg.png │ │ └── 404_cloud.png │ ├── default_avatar.png │ └── login │ │ ├── side-logo.png │ │ └── login_bg.svg ├── App.vue ├── api │ ├── home │ │ └── home.ts │ ├── request.ts │ ├── login │ │ └── login.ts │ ├── census │ │ └── census.ts │ ├── course │ │ └── course.ts │ ├── role │ │ └── role.ts │ ├── scores │ │ └── scores.ts │ ├── gradeclass │ │ └── gradeclass.ts │ ├── student │ │ └── student.ts │ ├── teacher │ │ └── teacher.ts │ └── user │ │ └── user.ts ├── store │ ├── index.ts │ └── modules │ │ ├── setting.ts │ │ ├── user.ts │ │ ├── menu.ts │ │ └── tagsView.ts ├── vite-env.d.ts ├── config │ └── nprogress.ts ├── style.css ├── main.ts ├── views │ ├── layout │ │ ├── header │ │ │ ├── CollapseIcon.vue │ │ │ ├── Hamburger.vue │ │ │ └── TopBar.vue │ │ ├── tags │ │ │ ├── components │ │ │ │ └── MoreButton.vue │ │ │ └── Index.vue │ │ ├── Index.vue │ │ └── aside │ │ │ └── Index.vue │ ├── login │ │ ├── Login.vue │ │ └── components │ │ │ └── LoginForm.vue │ ├── home │ │ ├── components │ │ │ └── AllSubjectScoreContrast.vue │ │ └── Index.vue │ ├── census │ │ ├── components │ │ │ ├── ScoreContrastCensusBar.vue │ │ │ └── ScoreCensusPie.vue │ │ ├── ScoresContrastCensus.vue │ │ └── ScoresCensus.vue │ ├── user │ │ ├── components │ │ │ ├── UserInfo.vue │ │ │ ├── UpdatePwd.vue │ │ │ ├── AddUser.vue │ │ │ ├── EditUser.vue │ │ │ ├── PersonalSettings.vue │ │ │ └── BindEmail.vue │ │ └── UserList.vue │ ├── role │ │ ├── components │ │ │ ├── AddRole.vue │ │ │ └── EditRole.vue │ │ └── RoleList.vue │ ├── course │ │ ├── components │ │ │ ├── AddCourse.vue │ │ │ └── EditCourse.vue │ │ └── CourseList.vue │ ├── gradeclass │ │ ├── components │ │ │ ├── AddGradeClass.vue │ │ │ └── EditGradeClass.vue │ │ └── GradeClassList.vue │ ├── student │ │ ├── components │ │ │ ├── AddStudent.vue │ │ │ └── EditStudent.vue │ │ └── StudentList.vue │ └── teacher │ │ ├── components │ │ ├── AddTeacher.vue │ │ └── EditTeacher.vue │ │ └── TeacherList.vue ├── icons │ └── svg │ │ ├── eye.svg │ │ └── eye-open.svg ├── components │ └── SvgIcon │ │ └── index.vue ├── utils │ ├── date.ts │ └── exprotExcel.ts └── router │ └── index.ts ├── tsconfig.node.json ├── index.html ├── tsconfig.json ├── vite.config.ts ├── package.json └── README.md /viteenv/.env.development: -------------------------------------------------------------------------------- 1 | # .env.develop 开发环境参数值 2 | VITE_APP_BASE_API = 'http://localhost:8080/' 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonl2014/student-mangement-system-web/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonl2014/student-mangement-system-web/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonl2014/student-mangement-system-web/HEAD/src/assets/logo2.png -------------------------------------------------------------------------------- /viteenv/.env.production: -------------------------------------------------------------------------------- 1 | # .env.production 生成环境 2 | # 线上环境接口地址 3 | VITE_APP_BASE_API = 'http://192.168.0.40:8080/' 4 | -------------------------------------------------------------------------------- /src/assets/banner01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonl2014/student-mangement-system-web/HEAD/src/assets/banner01.jpg -------------------------------------------------------------------------------- /src/assets/system-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonl2014/student-mangement-system-web/HEAD/src/assets/system-bg.jpg -------------------------------------------------------------------------------- /src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonl2014/student-mangement-system-web/HEAD/src/assets/404_images/404.png -------------------------------------------------------------------------------- /src/assets/default_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonl2014/student-mangement-system-web/HEAD/src/assets/default_avatar.png -------------------------------------------------------------------------------- /src/assets/login/side-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonl2014/student-mangement-system-web/HEAD/src/assets/login/side-logo.png -------------------------------------------------------------------------------- /src/assets/404_images/404_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonl2014/student-mangement-system-web/HEAD/src/assets/404_images/404_bg.png -------------------------------------------------------------------------------- /src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonl2014/student-mangement-system-web/HEAD/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /src/api/home/home.ts: -------------------------------------------------------------------------------- 1 | import request from '../request' 2 | export function getIndexTotalApi() { 3 | return request({ 4 | url: 'home', 5 | method: 'get' 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia' 2 | import piniaPluginPersistedstate from "pinia-plugin-persistedstate"; 3 | const pinia = createPinia() 4 | pinia.use(piniaPluginPersistedstate) 5 | export default pinia 6 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | const component: DefineComponent<{}, {}, any> 6 | export default component 7 | } 8 | -------------------------------------------------------------------------------- /src/api/request.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | const service = axios.create({ 3 | baseURL: import.meta.env.VITE_APP_BASE_API, 4 | timeout: 3000000, 5 | // 跨域时候允许携带凭证 6 | withCredentials: true 7 | }) 8 | export default service 9 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /src/api/login/login.ts: -------------------------------------------------------------------------------- 1 | import request from '../request' 2 | export function loginApi(data:object) { 3 | return request({ 4 | url: 'login', 5 | method: 'post', 6 | data 7 | }) 8 | } 9 | 10 | // 退出系统 11 | export function loginOutApi() { 12 | return request({ 13 | url: 'loginOut' 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /src/config/nprogress.ts: -------------------------------------------------------------------------------- 1 | import NProgress from 'nprogress' 2 | import "nprogress/nprogress.css" 3 | 4 | NProgress.configure({ 5 | easing: "ease", // 动画方式 6 | speed: 500, // 递增进度条的速度 7 | showSpinner: false, // 是否显示加载ico 8 | trickleSpeed: 200, // 自动递增间隔 9 | minimum: 0.3 // 初始化时的最小百分比 10 | }) 11 | export default NProgress 12 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 学生信息管理系统 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/store/modules/setting.ts: -------------------------------------------------------------------------------- 1 | import {defineStore} from 'pinia' 2 | export const useSettingStore = defineStore({ 3 | // id: 必须的,在所有 Store 中唯一 4 | id:'settingState', 5 | // state: 返回对象的函数 6 | state: ()=>({ 7 | // menu 是否收缩 8 | isCollapse:true, 9 | // tagsView 是否展示 默认展示 10 | showTag:true, 11 | }), 12 | getters: {}, 13 | actions:{ 14 | // 切换 Collapse 15 | setCollapse(value: boolean){ 16 | this.isCollapse = value 17 | } 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "resolveJsonModule": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "lib": ["ESNext", "DOM"], 13 | "skipLibCheck": true, 14 | "noEmit": true 15 | }, 16 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], 17 | "references": [{ "path": "./tsconfig.node.json" }] 18 | } 19 | -------------------------------------------------------------------------------- /src/api/census/census.ts: -------------------------------------------------------------------------------- 1 | import request from '../request' 2 | export function getScoreCensusApi(courseId:number,gradeClassId:number) { 3 | return request({ 4 | url: 'scores/getScoreCensus', 5 | method: 'get', 6 | params: { 7 | courseId, 8 | gradeClassId 9 | } 10 | }) 11 | } 12 | // 班级学科成绩对比 13 | export function getScoresContrastCensusApi(courseId:number) { 14 | return request({ 15 | url: 'scores/getScoresContrastCensus', 16 | method: 'get', 17 | params: { 18 | courseId 19 | } 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' 4 | import path from 'path' 5 | function resolve (dir) { 6 | return path.join(__dirname, '.', dir) 7 | } 8 | // https://vitejs.dev/config/ 9 | export default defineConfig({ 10 | envDir: "./viteenv",//这里使用相对路径,绝对路径其实也可以 11 | plugins: [vue(), 12 | // * 使用 svg 图标 13 | createSvgIconsPlugin({ 14 | // 指定需要缓存的图标文件夹 15 | iconDirs: [path.resolve(process.cwd(), 'src/icons/svg')], 16 | // 指定symbolId格式 17 | symbolId: 'icon-[dir]-[name]', 18 | }) 19 | ] 20 | }) 21 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | /* 清楚默认内间距、外间距 */ 2 | * { 3 | /* 内间距 */ 4 | padding: 0; 5 | /* 外间距 */ 6 | margin: 0; 7 | } 8 | 9 | body, 10 | html { 11 | /* 默认页面所有字体为微软雅黑 */ 12 | font-family: "微软雅黑"; 13 | } 14 | 15 | /* 清楚a标签的下划线 */ 16 | a { 17 | color: #666; 18 | text-decoration: none; 19 | } 20 | 21 | a:hover { 22 | color: #2fa7b9; 23 | } 24 | 25 | /* 滚动条样式 */ 26 | ::-webkit-scrollbar-thumb { 27 | border-radius: 50px; 28 | background: linear-gradient(to bottom, #35ac5d, #65ce6d); 29 | } 30 | 31 | ::-webkit-scrollbar { 32 | width: 8px; 33 | height: 8px; 34 | } 35 | 36 | #nprogress .bar { 37 | background: #178557 !important; 38 | } 39 | -------------------------------------------------------------------------------- /src/store/modules/user.ts: -------------------------------------------------------------------------------- 1 | import {defineStore} from 'pinia' 2 | export const useUserStore = defineStore({ 3 | // id: 必须的,在所有 Store 中唯一 4 | id:'userStore', 5 | // state: 返回对象的函数 6 | state: ()=>{ 7 | return { 8 | // 登录token 9 | token: '', 10 | // 登录用户信息 11 | userInfo:{}, 12 | // 角色 13 | roles:[] 14 | } 15 | }, 16 | getters: {}, 17 | // 可以同步 也可以异步 18 | actions:{ 19 | // 设置登录token 20 | setToken(token:string){ 21 | this.token = token; 22 | }, 23 | // 设置登录用户信息 24 | setUserInfo(userInfo:any){ 25 | this.userInfo = userInfo 26 | } 27 | }, 28 | persist: true 29 | }) 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "student-mangement-system-web", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build --mode production", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "axios": "^1.2.0", 13 | "element-plus": "^2.2.25", 14 | "exceljs": "^4.3.0", 15 | "nprogress": "^0.2.0", 16 | "pinia": "^2.0.27", 17 | "pinia-plugin-persistedstate": "^3.0.1", 18 | "vue": "^3.2.41", 19 | "vue-router": "^4.1.6" 20 | }, 21 | "devDependencies": { 22 | "@vitejs/plugin-vue": "^3.2.0", 23 | "echarts": "^5.4.1", 24 | "fast-glob": "^3.2.12", 25 | "typescript": "^4.6.4", 26 | "vite": "^3.2.3", 27 | "vite-plugin-svg-icons": "^2.0.1", 28 | "vue-tsc": "^1.0.9" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import './style.css' 3 | import App from './App.vue' 4 | import router from './router/index' 5 | import pinia from "./store" 6 | import ElementPlus from 'element-plus' 7 | import 'element-plus/dist/index.css' 8 | import zhCn from 'element-plus/dist/locale/zh-cn.mjs' 9 | // svg-icons注册导入 10 | import 'virtual:svg-icons-register' 11 | import SvgIcon from './components/SvgIcon/index.vue' 12 | // 导入所有ElementPlus图标 13 | import * as ElementPlusIconsVue from '@element-plus/icons-vue' 14 | const app = createApp(App) 15 | app.use(router) 16 | app.use(pinia) 17 | 18 | // 将所有图标进行全局注册 19 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 20 | app.component(key, component) 21 | } 22 | 23 | 24 | 25 | app.component('svg-icon',SvgIcon) 26 | app.use(ElementPlus, { 27 | locale: zhCn, 28 | }) 29 | app.mount('#app') 30 | -------------------------------------------------------------------------------- /src/views/layout/header/CollapseIcon.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | 30 | -------------------------------------------------------------------------------- /src/api/course/course.ts: -------------------------------------------------------------------------------- 1 | import request from '../request' 2 | export function getCourseListApi(data:object) { 3 | return request({ 4 | url: 'course', 5 | method: 'get', 6 | params: data 7 | }) 8 | } 9 | // 添加课程信息 10 | export function addCourseApi(data:object) { 11 | return request({ 12 | url: 'course', 13 | method: 'post', 14 | data 15 | }) 16 | } 17 | 18 | // 根据ID获取课程信息 19 | export function getCourseApi(id:number) { 20 | return request({ 21 | url: `course/${id}`, 22 | method: 'get' 23 | }) 24 | } 25 | // 更新课程信息 26 | export function editCourseApi(data:object) { 27 | return request({ 28 | url: 'course', 29 | method: 'put', 30 | data 31 | }) 32 | } 33 | // 根据ID删除课程信息 34 | export function deleteCourseApi(id:number) { 35 | return request({ 36 | url: `course/${id}`, 37 | method: 'delete' 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /src/api/role/role.ts: -------------------------------------------------------------------------------- 1 | import request from '../request' 2 | // 获取角色列表数据 3 | export function getRoleListApi(data:object) { 4 | return request({ 5 | url: 'role', 6 | method: 'get', 7 | params: data 8 | }) 9 | } 10 | 11 | // 添加角色信息 12 | export function addRoleApi(data:object) { 13 | return request({ 14 | url: 'role', 15 | method: 'post', 16 | data 17 | }) 18 | } 19 | 20 | // 根据ID获取角色信息 21 | export function getRoleApi(id:number) { 22 | return request({ 23 | url: `role/${id}`, 24 | method: 'get' 25 | }) 26 | } 27 | // 更新角色信息 28 | export function editRoleApi(data:object) { 29 | return request({ 30 | url: 'role', 31 | method: 'put', 32 | data 33 | }) 34 | } 35 | // 根据ID删除角色信息 36 | export function deleteRoleApi(id:number) { 37 | return request({ 38 | url: `role/${id}`, 39 | method: 'delete' 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /src/icons/svg/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 26 | 27 | 36 | -------------------------------------------------------------------------------- /src/api/scores/scores.ts: -------------------------------------------------------------------------------- 1 | import request from '../request' 2 | export function getScoresListApi(data:object) { 3 | return request({ 4 | url: 'scores', 5 | method: 'get', 6 | params: data 7 | }) 8 | } 9 | // 登记学科成绩 10 | export function registerScoresApi(gradeClassId:number,courseId:number) { 11 | return request({ 12 | url: 'scores', 13 | method: 'post', 14 | data: { 15 | gradeClassId: gradeClassId, 16 | courseId: courseId 17 | } 18 | }) 19 | } 20 | 21 | // 更新成绩 22 | export function editScoresApi(id:number,score: number) { 23 | return request({ 24 | url: 'scores', 25 | method: 'put', 26 | data: { 27 | id:id, 28 | score:score 29 | } 30 | }) 31 | } 32 | // 根据ID删除成绩信息 33 | export function deleteScoresApi(id:number) { 34 | return request({ 35 | url: `scores/${id}`, 36 | method: 'delete' 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /src/api/gradeclass/gradeclass.ts: -------------------------------------------------------------------------------- 1 | import request from '../request' 2 | export function getGradeClassListApi(data:object) { 3 | return request({ 4 | url: 'gradeclass', 5 | method: 'get', 6 | params: data 7 | }) 8 | } 9 | // 添加班级信息 10 | export function addGradeClassApi(data:object) { 11 | return request({ 12 | url: 'gradeclass', 13 | method: 'post', 14 | data 15 | }) 16 | } 17 | // 根据ID获取班级信息 18 | export function getGradeClassApi(id:number) { 19 | return request({ 20 | url: `gradeclass/${id}`, 21 | method: 'get' 22 | }) 23 | } 24 | // 更新班级信息 25 | export function editGradeClassApi(data:object) { 26 | return request({ 27 | url: 'gradeclass', 28 | method: 'put', 29 | data 30 | }) 31 | } 32 | // 根据ID删除班级信息 33 | export function deleteGradeClassApi(id:number) { 34 | return request({ 35 | url: `gradeclass/${id}`, 36 | method: 'delete' 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /src/api/student/student.ts: -------------------------------------------------------------------------------- 1 | import request from '../request' 2 | export function getStudentListApi(data:object) { 3 | return request({ 4 | url: 'student', 5 | method: 'get', 6 | params: data 7 | }) 8 | } 9 | // 添加学生信息 10 | export function addStudentApi(data:object) { 11 | return request({ 12 | url: 'student', 13 | method: 'post', 14 | data 15 | }) 16 | } 17 | // 获取所有班级列表 18 | export function gradeClassListApi() { 19 | return request({ 20 | url: 'gradeclass/all', 21 | method: 'get' 22 | }) 23 | } 24 | // 根据ID获取学生信息 25 | export function getStudentApi(id:number) { 26 | return request({ 27 | url: `student/${id}`, 28 | method: 'get' 29 | }) 30 | } 31 | // 更新学生信息 32 | export function editStudentApi(data:object) { 33 | return request({ 34 | url: 'student', 35 | method: 'put', 36 | data 37 | }) 38 | } 39 | // 根据ID删除学生信息 40 | export function deleteStudentApi(id:number) { 41 | return request({ 42 | url: `student/${id}`, 43 | method: 'delete' 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /src/api/teacher/teacher.ts: -------------------------------------------------------------------------------- 1 | import request from '../request' 2 | export function getTeacherListApi(data:object) { 3 | return request({ 4 | url: 'teacher', 5 | method: 'get', 6 | params: data 7 | }) 8 | } 9 | // 添加教师信息 10 | export function addTeacherApi(data:object) { 11 | return request({ 12 | url: 'teacher', 13 | method: 'post', 14 | data 15 | }) 16 | } 17 | // 获取所有课程列表 18 | export function getAllCourseListApi() { 19 | return request({ 20 | url: 'course/all', 21 | method: 'get' 22 | }) 23 | } 24 | // 根据ID获取教师信息 25 | export function getTeacherApi(id:number) { 26 | return request({ 27 | url: `teacher/${id}`, 28 | method: 'get' 29 | }) 30 | } 31 | // 更新教师信息 32 | export function editTeacherApi(data:object) { 33 | return request({ 34 | url: 'teacher', 35 | method: 'put', 36 | data 37 | }) 38 | } 39 | // 根据ID删除教师信息 40 | export function deleteTeacherApi(id:number) { 41 | return request({ 42 | url: `teacher/${id}`, 43 | method: 'delete' 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /src/views/layout/header/Hamburger.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 33 | 34 | 37 | -------------------------------------------------------------------------------- /src/icons/svg/eye-open.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/store/modules/menu.ts: -------------------------------------------------------------------------------- 1 | import {defineStore} from 'pinia' 2 | import {asyncRoutes, staticRouter} from "../../router"; 3 | export const useMenuStore = defineStore({ 4 | // id: 必须的,在所有 Store 中唯一 5 | id:'menuState', 6 | // state: 返回对象的函数 7 | state: ()=>({ 8 | // menu 静态路由 9 | routers:[], 10 | // 动态路由 11 | addRouters: [], 12 | // 用户角色 13 | roles: [] 14 | }), 15 | getters: {}, 16 | actions: { 17 | // 设置角色 18 | generateRoutes: function ({roles}: { roles: any }) { 19 | let accessedRoutes = filterAsyncRoutes({routes: asyncRoutes, roles: roles}) 20 | this.addRouters = accessedRoutes 21 | this.routers = staticRouter.concat(accessedRoutes) 22 | return accessedRoutes 23 | }, 24 | // 动态生成访问路由 25 | setRoles({roles}: { roles: any }) { 26 | this.roles = roles 27 | } 28 | } 29 | }) 30 | // 通过递归过滤asyncRoutes 31 | export function filterAsyncRoutes ({routes, roles}: { routes: any, roles: any }) { 32 | const res = [] 33 | routes.forEach(route => { 34 | const tmp = { ...route } 35 | if (hasPermission(roles, tmp)) { 36 | if (tmp.children) { 37 | tmp.children = filterAsyncRoutes({routes: tmp.children, roles: roles}) 38 | } 39 | res.push(tmp) 40 | } 41 | }) 42 | return res 43 | } 44 | function hasPermission (roles, route) { 45 | if (route.meta && route.meta.role) { 46 | // some() 方法用于检测数组中的元素是否满足指定条件(函数提供) 47 | return roles.some(role => route.meta.role.indexOf(role) >= 0) 48 | } else { 49 | return true 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/utils/date.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {date} time 需要转换的时间 3 | * @param {String} fmt 需要转换的格式 如 yyyy-MM-dd、yyyy-MM-dd HH:mm:ss 4 | */ 5 | export function formatTime(time: any, fmt: string) { 6 | if (!time) return '' 7 | else { 8 | const date = new Date(time) 9 | const o = { 10 | 'M+': date.getMonth() + 1,//getMonth() 返回值是0(一月)到11(十二月)之间的一个整数 11 | 'd+': date.getDate(), // getDate() 返回值是1~31之间的一个整数 12 | 'H+': date.getHours(),// 小时,返回 Date 对象的小时 (0 ~ 23)。 13 | 'm+': date.getMinutes(),// 分钟,返回 Date 对象的分钟 (0 ~ 59)。 14 | 's+': date.getSeconds(), // 秒,返回 Date 对象的秒数 (0 ~ 59)。 15 | 'q+': Math.floor((date.getMonth() + 3) / 3),// 季度 16 | 'S': date.getMilliseconds() // 毫秒,返回 Date 对象的毫秒(0 ~ 999)。 17 | } 18 | // 处理年份 19 | // RegExp.$1指的是与正则表达式匹配的第一个 子匹配(以括号为标志)字符串 20 | // getFullYear() 返回一个表示年份的4位数字 21 | // 输出的结果如:2022-MM-dd 22 | if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) 23 | 24 | // 遍历o对象 25 | for (const k in o) { 26 | if (new RegExp('(' + k + ')').test(fmt)) { 27 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (( 28 | '00' + o[k]).substr(('' + o[k]).length))) 29 | } 30 | } 31 | return fmt 32 | } 33 | } 34 | 35 | /** 36 | * 计算天数 37 | * @param time 38 | */ 39 | export function calculateDays(time:any) { 40 | if (!time) return '' 41 | else { 42 | let day = Math.floor(new Date().getTime() / 1000) - (new Date(time) 43 | .getTime() / 44 | 1000), 45 | day2 = Math.floor(day / (24 * 3600)); 46 | return day2 47 | } 48 | 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/views/layout/tags/components/MoreButton.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 43 | 44 | 55 | -------------------------------------------------------------------------------- /src/views/layout/Index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 36 | 37 | 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 基于SpringBoot3+Vue3整合开发学生信息管理系统 2 | 3 | > 后端项目gitHub源码地址:https://github.com/seasonl2014/student-mangement-system 4 | > 5 | > 前端项目gitHub源码地址:https://github.com/seasonl2014/student-mangement-system-web 6 | > 7 | > 欢迎给个star鼓励一下~ 8 | 9 | ## 系统功能结构 10 | 11 | 本系统主要有两种角色,分别管理员角色和普通用户角色,其中: 12 | 13 | 管理员角色主要功能有:后台首页展示、个人信息展示和修改、用户管理、角色管理、班级管理、学生管理、课程管理、教师管理、班级科目成绩管理、班级科目成绩统计、班级科目对比统计等功能,如图所示: 14 | 15 | ![]( https://i.imgtg.com/2022/12/30/EHbe1.png ) 16 | 17 | 普通用户角色主要功能有:后台首页展示、个人信息展示和修改、班级科目成绩管理、班级科目成绩统计、班级科目对比统计等功能,如图所示: 18 | 19 | ![]( https://i.imgtg.com/2022/12/30/EHkEI.png ) 20 | 21 | 22 | 23 | 24 | 25 | ## 技术栈 26 | 27 | 1. web框架:SpringBoot3.X 28 | 2. 数据库持久框架:Sping Data JPA 29 | 3. 数据库:MySql 30 | 4. 项目构建工具:Maven、vite 31 | 5. 前端框架:Vue3.X、element plus、pina、axios、Vue Router 32 | 6. 数据图表:ECharts 33 | 34 | ## 数据库表 35 | 36 | 本项目由七张表,分别如下: 37 | 38 | 1、用户表(sys_user) 39 | 40 | 2、角色表(sys_role) 41 | 42 | 3、教师表(s_teacher) 43 | 44 | 4、班级表(s_grade_class) 45 | 46 | 5、学生表(s_student) 47 | 48 | 6、课程表(s_course) 49 | 50 | 7、学生成绩表(s_student_score) 51 | 52 | ## 知识星球 53 | 54 | 本项目配套详细的项目开发手册,有兴趣的话,可以加入我的星球查看, 55 | 56 | [知识星球地址](https://t.zsxq.com/09BZEGLJB) 57 | 58 | 星球提供如下服务: 59 | 60 | 1.带你做实战项目,手把手教学,从0到1开发学生信息管理系统。 61 | 62 | 2.提供详细的项目开发手册。 63 | 64 | 3.提供各章节对应的源码。 65 | 66 | 4.关于项目或者学习过程中不懂的问题可以随时提问,星主将1对1解答你的问题。 67 | 68 | ![知识星球]( https://i.imgtg.com/2022/12/30/EHWaG.png ) 69 | 70 | 71 | 72 | ## 配套视频教程 73 | 74 | 75 | 76 | 77 | 78 | ## 项目预览 79 | 80 | > 登录页面 81 | 82 | ![登录页面](https://i.imgtg.com/2022/12/30/EDj9L.jpg) 83 | 84 | > 课程管理 85 | 86 | ![课程管理]( https://i.imgtg.com/2022/12/30/EHOFY.jpg ) 87 | 88 | 89 | 90 | > 班级管理 91 | 92 | ![班级管理]( https://i.imgtg.com/2022/12/30/EHojq.jpg ) 93 | 94 | > 教师管理 95 | 96 | ![教师管理]( https://i.imgtg.com/2022/12/30/EH5Uc.jpg ) 97 | 98 | > 学生管理 99 | 100 | ![学生管理]( https://i.imgtg.com/2022/12/30/EHByr.jpg ) 101 | 102 | > 后台首页 103 | 104 | ![后台首页]( https://i.imgtg.com/2022/12/30/EHTsM.jpg ) 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/views/login/Login.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 29 | 30 | 92 | -------------------------------------------------------------------------------- /src/views/home/components/AllSubjectScoreContrast.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 96 | 97 | -------------------------------------------------------------------------------- /src/views/census/components/ScoreContrastCensusBar.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/api/user/user.ts: -------------------------------------------------------------------------------- 1 | import request from '../request' 2 | 3 | // 获取用户列表数据 4 | export function getUserListApi(data:object) { 5 | return request({ 6 | url: 'user', 7 | method: 'get', 8 | params: data 9 | 10 | }) 11 | } 12 | // 添加用户信息 13 | export function addUserApi(data:object) { 14 | return request({ 15 | url: 'user', 16 | method: 'post', 17 | data 18 | }) 19 | } 20 | // 根据ID获取用户信息 21 | export function getUserApi(id:number) { 22 | return request({ 23 | url: `user/${id}`, 24 | method: 'get' 25 | }) 26 | } 27 | // 更新用户信息 28 | export function editUserApi(data:object) { 29 | return request({ 30 | url: 'user', 31 | method: 'put', 32 | data 33 | }) 34 | } 35 | // 根据ID删除用户信息 36 | export function deleteUserApi(id:number) { 37 | return request({ 38 | url: `user/${id}`, 39 | method: 'delete' 40 | }) 41 | } 42 | // 更新个人信息 43 | export function updateInfoApi(data:object) { 44 | return request({ 45 | url: 'user/updateInfo', 46 | method: 'put', 47 | data 48 | }) 49 | } 50 | // 发送验证码 51 | export function sendEmailApi(email:string) { 52 | return request({ 53 | url: 'user/sendEmail', 54 | method: 'get', 55 | params: { 56 | email 57 | } 58 | 59 | }) 60 | } 61 | 62 | // 校验用户输入验证码是否正确 63 | export function verifyCodeApi(code:string) { 64 | return request({ 65 | url: 'user/verifyCode', 66 | method: 'get', 67 | params: { 68 | code 69 | } 70 | 71 | }) 72 | } 73 | 74 | // 更改绑定邮箱 75 | export function updateEmailApi(email:string,code:number) { 76 | return request({ 77 | url: 'user/updateEmail', 78 | method: 'put', 79 | params: { 80 | email, 81 | code 82 | } 83 | 84 | }) 85 | } 86 | // 更改个人密码 87 | export function updatePwdApi(data:object) { 88 | return request({ 89 | url: 'user/updatePwd', 90 | method: 'put', 91 | data 92 | }) 93 | } 94 | 95 | // 获取所有角色列表 96 | export function getAllRoleListApi() { 97 | return request({ 98 | url: 'role/all', 99 | method: 'get' 100 | }) 101 | } 102 | -------------------------------------------------------------------------------- /src/assets/login/login_bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/views/layout/aside/Index.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 58 | 59 | 80 | -------------------------------------------------------------------------------- /src/utils/exprotExcel.ts: -------------------------------------------------------------------------------- 1 | import ExcelJS from 'exceljs' 2 | 3 | export const autoWidthAction = (val,width=10)=> { 4 | if(val==null){ 5 | width = 10 6 | }else if(val.toString().charCodeAt(0)>255){ 7 | /*if chinese*/ 8 | width = val.toString().length * 2; 9 | }else { 10 | width = val.toString().length; 11 | } 12 | 13 | } 14 | 15 | // 导出普通Excel 16 | export const exportExcel = async ({column,data,filename,autoWidth,format})=>{ 17 | console.log('data----------:',data) 18 | // 创建excel工作簿 19 | const workbook = new ExcelJS.Workbook() 20 | // 设置工作簿属性 21 | workbook.creator = 'Me' 22 | workbook.title = filename 23 | workbook.created = new Date() 24 | workbook.modified = new Date() 25 | // 添加工作表 26 | const worksheet = workbook.addWorksheet(filename) 27 | // 设置列名 28 | const columnsName = [] 29 | console.log('column----------:',column) 30 | for (let item in column) { 31 | console.log('item----------:',item) 32 | } 33 | column.forEach((item,index)=>{ 34 | const obj = { 35 | header: item.label, 36 | key: item.name, 37 | width:null 38 | } 39 | if(autoWidth){ 40 | const maxArr = [autoWidthAction(item.label)] 41 | data.forEach(ite=> { 42 | const str = ite[item.name] || '' 43 | if(str){ 44 | maxArr.push(autoWidthAction(str)) 45 | } 46 | }) 47 | obj.width = Math.max(...maxArr)+5 48 | } 49 | // 设置列名、键和宽度 50 | columnsName.push(obj) 51 | }) 52 | worksheet.columns = columnsName 53 | // 添加行 54 | worksheet.addRows(data) 55 | // 写入文件 56 | const uint8Array = 57 | format === "xlsx" 58 | ? await workbook.xlsx.writeBuffer() 59 | : await workbook.csv.writeBuffer() 60 | const blob = new Blob([uint8Array],{type: 'application/octet-binary'}) 61 | // 判断是否允许用户在客户端上保存文件 62 | if(window.navigator.msSaveOrOpenBlob){ 63 | // msSaveOrOpenBlob方法返回boolean值 64 | navigator.msSaveBlob(blob, filename + `.${format}`); 65 | // 本地保存 66 | }else { 67 | const link = document.createElement("a"); // a标签下载 68 | link.href = window.URL.createObjectURL(blob); // href属性指定下载链接 69 | link.download = filename + `.${format}`; // dowload属性指定文件名 70 | link.click(); // click()事件触发下载 71 | window.URL.revokeObjectURL(link.href); // 释放内存 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/views/user/components/UserInfo.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 60 | 61 | 98 | -------------------------------------------------------------------------------- /src/views/role/components/AddRole.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 76 | 77 | 83 | -------------------------------------------------------------------------------- /src/views/course/components/AddCourse.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 78 | 79 | 85 | -------------------------------------------------------------------------------- /src/views/role/components/EditRole.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 83 | 84 | 90 | -------------------------------------------------------------------------------- /src/views/course/components/EditCourse.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 84 | 85 | 91 | -------------------------------------------------------------------------------- /src/views/user/components/UpdatePwd.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 80 | 81 | 84 | -------------------------------------------------------------------------------- /src/views/census/components/ScoreCensusPie.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 129 | 130 | -------------------------------------------------------------------------------- /src/views/census/ScoresContrastCensus.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 73 | 74 | 105 | -------------------------------------------------------------------------------- /src/views/gradeclass/components/AddGradeClass.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 90 | 91 | 97 | -------------------------------------------------------------------------------- /src/views/layout/tags/Index.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 93 | 94 | 124 | -------------------------------------------------------------------------------- /src/views/gradeclass/components/EditGradeClass.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 97 | 98 | 104 | -------------------------------------------------------------------------------- /src/views/census/ScoresCensus.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 100 | 101 | 132 | -------------------------------------------------------------------------------- /src/views/login/components/LoginForm.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 117 | 118 | 135 | -------------------------------------------------------------------------------- /src/store/modules/tagsView.ts: -------------------------------------------------------------------------------- 1 | import {defineStore} from 'pinia' 2 | import router from "../../router" 3 | export const useTagsViewStore = defineStore({ 4 | // id: 必须的,在所有 Store 中唯一 5 | id:'tagsViewState', 6 | // state: 返回对象的函数 7 | state: ()=>({ 8 | activeTabsValue:'', 9 | visitedViews:[{path: '/home',name: 'home',meta:{title: '首页',affix: true},title: '首页'}], 10 | cachedViews:[], 11 | 12 | }), 13 | getters: {}, 14 | // 可以同步 也可以异步 15 | actions:{ 16 | setTabsMenuValue(val: string){ 17 | this.activeTabsValue = val 18 | }, 19 | addView(view: any){ 20 | this.addVisitedView(view) 21 | }, 22 | removeView(routes: string | any[]){ 23 | return new Promise((resolve, reject) => { 24 | this.visitedViews = this.visitedViews.filter(item=>!routes.includes(item.path)) 25 | resolve(null) 26 | }) 27 | }, 28 | addVisitedView(view: never){ 29 | this.setTabsMenuValue(view.path); 30 | if (this.visitedViews.some(v => v.path === view.path)) return 31 | 32 | this.visitedViews.push( 33 | Object.assign({}, view, { 34 | title: view.meta.title || 'no-name' 35 | }) 36 | ) 37 | if (view.meta.keepAlive) { 38 | this.cachedViews.push(view.name) 39 | } 40 | 41 | }, 42 | delView(activeTabPath){ 43 | return new Promise(resolve => { 44 | this.delVisitedView(activeTabPath) 45 | this.delCachedView(activeTabPath) 46 | resolve({ 47 | visitedViews: [...this.visitedViews], 48 | cachedViews: [...this.cachedViews] 49 | }) 50 | }) 51 | 52 | }, 53 | toLastView(activeTabPath){ 54 | let index = this.visitedViews.findIndex(item=>item.path===activeTabPath) 55 | const nextTab = this.visitedViews[index + 1] || this.visitedViews[index - 1]; 56 | if (!nextTab) return; 57 | router.push(nextTab.path); 58 | this.addVisitedView(nextTab) 59 | }, 60 | delVisitedView(path){ 61 | return new Promise(resolve => { 62 | this.visitedViews = this.visitedViews.filter(v=>{ 63 | return (v.path !== path||v.meta.affix) 64 | }) 65 | this.cachedViews = this.cachedViews.filter(v=>{ 66 | return (v.path !== path||v.meta.affix) 67 | }) 68 | resolve([...this.visitedViews]) 69 | }) 70 | 71 | }, 72 | delCachedView(view){ 73 | return new Promise(resolve => { 74 | const index = this.cachedViews.indexOf(view.name) 75 | index > -1 && this.cachedViews.splice(index, 1) 76 | resolve([...this.cachedViews]) 77 | }) 78 | 79 | }, 80 | clearVisitedView(){ 81 | this.delAllViews() 82 | }, 83 | delAllViews(){ 84 | return new Promise((resolve) => { 85 | this.visitedViews = this.visitedViews.filter(v=>v.meta.affix) 86 | this.cachedViews = this.visitedViews.filter(v=>v.meta.affix) 87 | resolve([...this.visitedViews]) 88 | }) 89 | }, 90 | delOtherViews(path){ 91 | this.visitedViews = this.visitedViews.filter(item => { 92 | return item.path === path || item.meta.affix; 93 | }); 94 | this.cachedViews = this.visitedViews.filter(item => { 95 | return item.path === path || item.meta.affix; 96 | }); 97 | }, 98 | goHome() { 99 | this.activeTabsValue = '/home'; 100 | router.push({path: '/home'}); 101 | }, 102 | updateVisitedView(view){ 103 | for (let v of this.visitedViews) { 104 | if (v.path === view.path) { 105 | v = Object.assign(v, view) 106 | break 107 | } 108 | } 109 | } 110 | }, 111 | 112 | }) 113 | -------------------------------------------------------------------------------- /src/views/student/components/AddStudent.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 123 | 124 | 130 | -------------------------------------------------------------------------------- /src/views/teacher/components/AddTeacher.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 123 | 124 | 130 | -------------------------------------------------------------------------------- /src/views/teacher/components/EditTeacher.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 56 | 130 | 131 | 137 | -------------------------------------------------------------------------------- /src/views/student/components/EditStudent.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 130 | 131 | 137 | -------------------------------------------------------------------------------- /src/views/user/components/AddUser.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 140 | 141 | 147 | -------------------------------------------------------------------------------- /src/views/user/components/EditUser.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 149 | 150 | 156 | -------------------------------------------------------------------------------- /src/views/home/Index.vue: -------------------------------------------------------------------------------- 1 | 82 | 83 | 115 | 116 | 189 | -------------------------------------------------------------------------------- /src/views/layout/header/TopBar.vue: -------------------------------------------------------------------------------- 1 | 89 | 90 | 114 | 115 | 162 | -------------------------------------------------------------------------------- /src/views/user/components/PersonalSettings.vue: -------------------------------------------------------------------------------- 1 | 72 | 73 | 155 | 156 | 185 | -------------------------------------------------------------------------------- /src/views/user/components/BindEmail.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 188 | 189 | 192 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | // 1. 导入Vue Router模块 2 | import { createRouter, createWebHashHistory } from 'vue-router' 3 | import NProgress from "../config/nprogress"; 4 | import { useUserStore } from '../store/modules/user' 5 | import { useMenuStore } from '../store/modules/menu' 6 | // 2. 定义一些路由,每个路由都需要映射到一个组件。 7 | // 定义静态路由 8 | export const staticRouter = [ 9 | { 10 | path: '/', 11 | redirect: "/login", 12 | isMenu: false 13 | }, 14 | { 15 | path: '/login', 16 | name: 'Login', 17 | meta: { title: '学生信息管理系统 - 登录'}, 18 | component: ()=> import('../views/login/Login.vue'), 19 | isMenu: false 20 | 21 | }, 22 | { 23 | path: '/index', 24 | name: 'index', 25 | component: ()=> import('../views/layout/Index.vue'), 26 | redirect: "/home", 27 | isMenu: true, 28 | funcNode:1, 29 | children: [{ 30 | path: '/home', 31 | name: 'home', 32 | meta: { title: '首页', icon: 'House',affix: true }, 33 | component: ()=> import('../views/home/Index.vue') 34 | }] 35 | 36 | }, 37 | { 38 | path: '/user', 39 | name: 'UserSetting', 40 | redirect: '/user/setting', 41 | component: ()=> import('../views/layout/Index.vue'), 42 | isMenu: true, 43 | funcNode:1, 44 | children: [ 45 | { 46 | path: 'setting', 47 | name: 'PersonalSettings', 48 | meta: { title: '个人设置', icon: 'Basketball'}, 49 | component: ()=> import('../views/user/components/PersonalSettings.vue') 50 | } 51 | ] 52 | } 53 | ] 54 | // 定义动态路由 55 | export const asyncRoutes = [ 56 | { 57 | path: '/system', 58 | name: 'system', 59 | meta: { 60 | title: '系统管理', 61 | icon: 'GoldMedal', 62 | role: ['ROLE_ADMIN'] 63 | }, 64 | redirect: '/system/user', 65 | component: ()=> import('../views/layout/Index.vue'), 66 | isMenu: true, 67 | funcNode:2, 68 | children: [ 69 | { 70 | path: 'user', 71 | name: 'User', 72 | meta: { 73 | title: '用户管理', 74 | icon: 'UserFilled', 75 | role: ['ROLE_ADMIN'] 76 | }, 77 | component: ()=> import('../views/user/UserList.vue') 78 | }, 79 | { 80 | path: 'role', 81 | name: 'Role', 82 | meta: { 83 | title: '角色管理', 84 | icon: 'Stamp', 85 | role: ['ROLE_ADMIN'] 86 | }, 87 | component: ()=> import('../views/role/RoleList.vue') 88 | } 89 | ] 90 | }, 91 | { 92 | path: '/base', 93 | name: 'base', 94 | meta: { 95 | title: '数据管理', 96 | icon: 'DataAnalysis', 97 | role: ['ROLE_ADMIN'] 98 | }, 99 | redirect: '/base/gradeclass', 100 | component: ()=> import('../views/layout/Index.vue'), 101 | isMenu: true, 102 | funcNode:2, 103 | children: [ 104 | { 105 | path: 'gradeclass', 106 | name: 'gradeclass', 107 | meta: { 108 | title: '班级管理', 109 | icon: 'Box', 110 | role: ['ROLE_ADMIN'] 111 | }, 112 | component: ()=> import('../views/gradeclass/GradeClassList.vue') 113 | }, 114 | { 115 | path: 'student', 116 | name: 'student', 117 | meta: { 118 | title: '学生管理', 119 | icon: 'User', 120 | role: ['ROLE_ADMIN'] 121 | }, 122 | component: ()=> import('../views/student/StudentList.vue') 123 | }, 124 | { 125 | path: 'course', 126 | name: 'course', 127 | meta: { 128 | title: '课程管理', 129 | icon: 'Tickets', 130 | role: ['ROLE_ADMIN'] 131 | }, 132 | component: ()=> import('../views/course/CourseList.vue') 133 | }, 134 | { 135 | path: 'teacher', 136 | name: 'teacher', 137 | meta: { 138 | title: '教师管理', 139 | icon: 'Avatar', 140 | role: ['ROLE_ADMIN'] 141 | }, 142 | component: ()=> import('../views/teacher/TeacherList.vue') 143 | } 144 | ] 145 | }, 146 | { 147 | path: '/scores', 148 | name: 'scores', 149 | meta: { 150 | title: '成绩管理', 151 | icon: 'GoldMedal', 152 | role: ['ROLE_USER','ROLE_ADMIN'] 153 | }, 154 | redirect: '/scores/index', 155 | component: ()=> import('../views/layout/Index.vue'), 156 | isMenu: true, 157 | funcNode:2, 158 | children: [ 159 | { 160 | path: 'index', 161 | name: 'scoresIndex', 162 | meta: { 163 | title: '班级科目成绩', 164 | icon: 'Money', 165 | role: ['ROLE_USER','ROLE_ADMIN'] 166 | }, 167 | component: ()=> import('../views/scores/ScoresList.vue') 168 | } 169 | ] 170 | }, 171 | { 172 | path: '/census', 173 | name: 'census', 174 | meta: { 175 | title: '数据统计', 176 | icon: 'Medal', 177 | role: ['ROLE_USER','ROLE_ADMIN'] 178 | }, 179 | redirect: '/census/index', 180 | component: ()=> import('../views/layout/Index.vue'), 181 | isMenu: true, 182 | funcNode:2, 183 | children: [ 184 | { 185 | path: 'index', 186 | name: 'scoresCensusIndex', 187 | meta: { 188 | title: '班级科目成绩统计', 189 | icon: 'Histogram', 190 | role: ['ROLE_USER','ROLE_ADMIN'] 191 | }, 192 | component: ()=> import('../views/census/ScoresCensus.vue') 193 | }, 194 | { 195 | path: 'contrast', 196 | name: 'scoresContrastCensusIndex', 197 | meta: { 198 | title: '班级科目对比统计', 199 | icon: 'Notification', 200 | role: ['ROLE_USER','ROLE_ADMIN'] 201 | }, 202 | component: ()=> import('../views/census/ScoresContrastCensus.vue') 203 | } 204 | ] 205 | } 206 | ] 207 | 208 | // 3. 创建路由实例并传递 `routes` 配置 209 | const router = createRouter({ 210 | // 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。 211 | history: createWebHashHistory(), 212 | routes: staticRouter 213 | }) 214 | // 路由拦截 beforeEach 215 | router.beforeEach(async (to, from, next) => { 216 | // 1.NProgress 开始 217 | NProgress.start(); 218 | 219 | //2.如果是访问登录页,直接放行 220 | if(to.path==='/login')return next() 221 | 222 | //3.判断是否有Token,没有重定向到login 223 | const userStore = useUserStore() 224 | if(!userStore.token)return next({path:`/login?redirect=${to.path}`,replace:true}) 225 | 226 | // 获取登录用户的角色 227 | const { userInfo } = userStore 228 | const roles = [] 229 | roles.push(userInfo.role.code) 230 | 231 | // 根据角色动态生成路由访问映射 232 | const menuStore = useMenuStore() 233 | if (!menuStore.routers.length) { 234 | const accessRoutes = menuStore.generateRoutes({roles: roles}) 235 | accessRoutes.forEach(item => router.addRoute(item)) // 动态添加访问路由表 236 | next({ ...to, replace: true }) // 这里相当于push到一个页面 不在进入路由拦截 237 | }else { 238 | // 正常访问页面 239 | next(); 240 | } 241 | }); 242 | /** 243 | * @description 路由跳转结束 244 | * */ 245 | router.afterEach(() => { 246 | NProgress.done(); 247 | }); 248 | /** 249 | * @description 路由跳转错误 250 | * */ 251 | router.onError(error => { 252 | NProgress.done(); 253 | console.warn("路由错误", error.message); 254 | }); 255 | export default router 256 | -------------------------------------------------------------------------------- /src/views/course/CourseList.vue: -------------------------------------------------------------------------------- 1 | 121 | 122 | 246 | 247 | 304 | -------------------------------------------------------------------------------- /src/views/role/RoleList.vue: -------------------------------------------------------------------------------- 1 | 125 | 126 | 250 | 251 | 307 | -------------------------------------------------------------------------------- /src/views/gradeclass/GradeClassList.vue: -------------------------------------------------------------------------------- 1 | 136 | 137 | 262 | 263 | 320 | -------------------------------------------------------------------------------- /src/views/student/StudentList.vue: -------------------------------------------------------------------------------- 1 | 143 | 144 | 271 | 272 | 328 | -------------------------------------------------------------------------------- /src/views/teacher/TeacherList.vue: -------------------------------------------------------------------------------- 1 | 146 | 147 | 276 | 277 | 333 | -------------------------------------------------------------------------------- /src/views/user/UserList.vue: -------------------------------------------------------------------------------- 1 | 166 | 167 | 325 | 326 | 385 | --------------------------------------------------------------------------------