├── .gitignore ├── README.md ├── admin ├── .env.development ├── .env.production ├── index.html ├── package.json ├── postcss.config.cjs ├── public │ ├── logo.svg │ ├── qq.webp │ ├── robots.txt │ └── wechat.webp ├── src │ ├── App.vue │ ├── assets │ │ ├── blue-v.png │ │ ├── custom-empty-image.png │ │ ├── empty.png │ │ ├── logo.jpg │ │ ├── user.png │ │ └── 成功.svg │ ├── components │ │ ├── About.vue │ │ ├── Notice.vue │ │ ├── Upload.vue │ │ ├── admin │ │ │ ├── CreateNotice │ │ │ │ ├── Edit.vue │ │ │ │ └── index.vue │ │ │ ├── Navigate.vue │ │ │ ├── college │ │ │ │ └── CreateFrom.vue │ │ │ ├── course │ │ │ │ └── FromDialog.vue │ │ │ ├── student │ │ │ │ └── FromDialog.vue │ │ │ └── teacher │ │ │ │ └── FromDialog.vue │ │ ├── student │ │ │ └── Navigate.vue │ │ └── teacher │ │ │ └── Navigate.vue │ ├── hooks │ │ └── useFetch.js │ ├── layouts │ │ ├── admin.vue │ │ ├── student.vue │ │ └── teacher.vue │ ├── main.js │ ├── pages │ │ ├── admin │ │ │ ├── about.vue │ │ │ ├── class │ │ │ │ └── index.vue │ │ │ ├── college │ │ │ │ └── index.vue │ │ │ ├── course │ │ │ │ └── index.vue │ │ │ ├── index.vue │ │ │ ├── major │ │ │ │ └── index.vue │ │ │ ├── notice │ │ │ │ ├── create.vue │ │ │ │ └── index.vue │ │ │ ├── password │ │ │ │ └── index.vue │ │ │ ├── student │ │ │ │ └── index.vue │ │ │ └── teacher │ │ │ │ └── index.vue │ │ ├── index.vue │ │ ├── sign.vue │ │ ├── student │ │ │ ├── about.vue │ │ │ ├── index.vue │ │ │ ├── notice │ │ │ │ └── index.vue │ │ │ └── password │ │ │ │ └── index.vue │ │ └── teacher │ │ │ ├── about.vue │ │ │ ├── index.vue │ │ │ ├── notice │ │ │ └── index.vue │ │ │ ├── password │ │ │ └── index.vue │ │ │ └── score │ │ │ └── index.vue │ ├── plugin │ │ └── axios.js │ ├── store │ │ └── useUserData.js │ └── style │ │ └── index.scss ├── tailwind.config.cjs ├── vite.config.js └── yarn.lock ├── dev.bat ├── intall.bat ├── server ├── .env.dev ├── package.json ├── public │ ├── robots.txt │ └── 说明.txt └── src │ ├── db │ └── index.js │ ├── index.js │ ├── modules │ ├── auth.js │ ├── signToken.js │ └── test.js │ └── routes │ ├── admin │ ├── class │ │ ├── course-by-class.js │ │ ├── create.js │ │ ├── delete.js │ │ ├── major-list.js │ │ └── update.js │ ├── college │ │ ├── create.js │ │ ├── delete.js │ │ ├── list.js │ │ └── update.js │ ├── course │ │ ├── college-course-list.js │ │ ├── create.js │ │ ├── delete.js │ │ ├── list.js │ │ └── update.js │ ├── major │ │ ├── create.js │ │ ├── delete.js │ │ ├── list.js │ │ └── update.js │ ├── notice │ │ ├── create.js │ │ ├── delete.js │ │ └── list.js │ ├── password.js │ ├── semester │ │ ├── update.js │ │ └── value.js │ ├── student │ │ ├── class-tree.js │ │ ├── create.js │ │ ├── delete.js │ │ ├── list.js │ │ └── update.js │ ├── system-details.js │ ├── teacher │ │ ├── create.js │ │ ├── delete.js │ │ ├── list.js │ │ └── update.js │ └── update.js │ ├── common │ ├── sign.js │ └── user-info.js │ ├── index.js │ ├── student │ ├── password.js │ └── score.js │ ├── teacher │ ├── info.js │ ├── notice_history.js │ ├── password.js │ ├── score │ │ ├── create.js │ │ └── tree-student.js │ └── xlsx │ │ └── course-class.js │ └── upload.js └── stu_score.sql /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .idea 4 | .vscode 5 | .DS_Store 6 | *.local 7 | *.docx 8 | 9 | **/yarn.lock 10 | 11 | server/.env.test 12 | server/public/image 13 | server/public/xlsx 14 | server/test.js 15 | admin/front_end 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 学生成绩管理系统 student-achievement 2 | 3 | ## 介绍 4 | 5 | 项目是之前接的一个毕业设计的程序成果物,毕业设计基本水平 6 | 技术为 Vue3.0+Node.js(Koa)+MySQL,组件库使用 Element-plus 组件库 使用 Tailwind CSS+Vite 7 | 8 | **[项目在线体验地址](https://score.blogweb.cn/)** 9 | 10 | **[项目介绍文章地址(基础版)](https://blogweb.cn/article/7911372471912)** 项目包括论文(论文收费提供) 11 | **如果你有对成果物有什么问题,或者在毕业设计上有什么问题(需要代做)可以联系 微信:webzhizuo QQ:1974109227** 12 | 13 | **不是无偿解决(包括不会启动程序)** 14 | 15 |
16 | QQ 微信 17 |
18 | 19 | ## 全新版本介绍 20 | 21 | 本系统/论文有全新版本,修复了现版本的全部小 BUG,同时添加了很多功能,可以添加联系方式购买。 22 | 23 | 对比目前的版本添加了以下功能: 24 | 25 | 1. **管理员端功能** 26 | 27 | - **信息管理** 28 | - 支持通过 Excel 模板批量导入/导出:课程信息、专业班级、学院、教师、学生信息 29 | 30 | 2. **教师端功能** 31 | 32 | - **成绩管理** 33 | - 支持成绩批量导入和单独录入 34 | - 可自定义成绩计算公式(默认公式:总成绩=平时成绩 ×30%+考试成绩 ×70%) 35 | - 提供 Excel 模板下载/上传功能,方便批量操作 36 | - **班级管理** 37 | - 查看所带班级学生基本信息 38 | - 管理补考/重修成绩(标注原始成绩和补考成绩) 39 | - **教学互动** 40 | - 上传教学资料(带标题和简介的文件列表) 41 | - 处理学生成绩异议并记录修改原因 42 | - 查看课程评价统计结果 43 | 44 | 3. **学生端功能** 45 | 46 | - **学业查询** 47 | - 查看各学期成绩及变化趋势图表 48 | - 查询个人基本信息和总学分 49 | - 下载教师分享的教学资料 50 | - **互动功能** 51 | - 提交成绩异议申请 52 | - 对课程和教师进行署名评价 53 | - 接收系统邮件通知(成绩发布、重要通知等) 54 | 55 | 4. **通用** 56 | 57 | - 对比数据库存储密码进行加密 58 | 59 | ## 项目启动 60 | 61 | **MySQL 版本要求使用 8** 62 | **前端使用 Vite 开发,Node.js 版本要求大于等于 20.9.0** 63 | Node.js 20.18.0 下载:[Windows 地址](https://nodejs.org/download/release/v20.18.1/node-v20.18.1-x86.msi) , [苹果 MacBook 地址](https://nodejs.org/download/release/v20.18.1/node-v20.18.1.pkg) 64 | 65 | 推荐使用 yarn 作为包管理 66 | 使用 **npm i yarn -g** 来安装 yarn 67 | 68 | 先创建数据库 stu_score,后导入 SQL 文件(导入后 admin 表手动添加一个记录),在填写 server 中的.env.dev 文件,填写数据库信息。快捷文件启动和命令行启动二选一。 69 | 70 | **快捷文件启动** 71 | 72 | 1. 先双击打开 install.bat 文件,依赖安装结束后 cmd 会自动关闭 73 | 2. 双击 dev 文件启动前端和后端 74 | 75 | **命令行启动:** 76 | 77 | 如果出现部分模块未找到,请参考`intall.bat` 全局安装对应插件。 78 | 79 | 0.引入 SQL 文件 80 | 1.cd server 81 | 2.yarn 82 | 3.yarn dev 83 | 4.cd admin 84 | 5.yarn 85 | 6.yarn dev 86 | 87 | ### 初始密码 88 | 89 | 管理员:账号:1 密码:111111 90 | 91 | 学生:账号:2 密码:111111 92 | 93 | 教师:账号:1 密码:111111 94 | 95 | 为了方便大家测试,线上测试环境关掉了部分功能接口。 96 | 97 | ## 基础版功能 98 | 99 | 共数据表 14 张 见介绍文章 100 | 101 | ### 管理员 102 | 103 | 1. 验证信息,登录系统 104 | 2. 学院增删改查 105 | 3. 专业增删改查 106 | 4. 课程增删改查 107 | 5. 发布通知 108 | 6. 教师学生增删改查 109 | 7. 学院-专业-教师-课程的联表绑定 110 | 111 | ### 学生 112 | 113 | 1. 验证个人信息,登录系统 114 | 2. 查询/修改个人基本信息,查看成绩 115 | 3. 通知接收 116 | 117 | ### 教师 118 | 119 | 1. 验证个人信息,登录系统 120 | 2. 查询/修改个人基本信息,能修改登录密码 121 | 3. 查看成绩 122 | 4. 录入成绩 123 | 5. 修改或更新某一个成绩 124 | 6. 查询某一科的平均成绩,以及改科目的最高分最低分。并使用 Echarts 图表 对成绩进行统计分析 125 | 7. 下载某一科目的 Excel 成绩 126 | 8. 通知接收 127 | -------------------------------------------------------------------------------- /admin/.env.development: -------------------------------------------------------------------------------- 1 | VITE_API_HOST=http://localhost:3456 -------------------------------------------------------------------------------- /admin/.env.production: -------------------------------------------------------------------------------- 1 | VITE_API_HOST=https://score-api.blogweb.cn 2 | -------------------------------------------------------------------------------- /admin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 学生成绩管理系统 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /admin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "site", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@wangeditor/editor": "^5.1.23", 13 | "@wangeditor/editor-for-vue": "^5.1.12", 14 | "axios": "^1.2.1", 15 | "copy-to-clipboard": "^3.3.3", 16 | "echarts": "^5.5.1", 17 | "element-plus": "^2.9.1", 18 | "js-cookie": "^3.0.5", 19 | "moment": "^2.30.1", 20 | "pinia": "^2.3.0", 21 | "socket.io-client": "^4.7.4", 22 | "unplugin-auto-import": "^0.19.0", 23 | "vue": "^3.5.13", 24 | "vue-countup-v3": "^1.4.2", 25 | "vue-router": "^4.5.0" 26 | }, 27 | "devDependencies": { 28 | "@types/node": "^22.10.2", 29 | "@vitejs/plugin-vue": "^5.2.1", 30 | "cssnano": "^7.0.6", 31 | "postcss": "^8.4.20", 32 | "sass": "^1.57.1", 33 | "tailwindcss": "3.4.17", 34 | "vite": "^6.0.5", 35 | "vite-plugin-pages": "^0.32.4", 36 | "vite-plugin-vue-layouts": "^0.11.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /admin/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /admin/public/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /admin/public/qq.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lrunlin/student-achievement/e14b8b4854ff98c424b75b112954ee0d9897893b/admin/public/qq.webp -------------------------------------------------------------------------------- /admin/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow:/ -------------------------------------------------------------------------------- /admin/public/wechat.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lrunlin/student-achievement/e14b8b4854ff98c424b75b112954ee0d9897893b/admin/public/wechat.webp -------------------------------------------------------------------------------- /admin/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | -------------------------------------------------------------------------------- /admin/src/assets/blue-v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lrunlin/student-achievement/e14b8b4854ff98c424b75b112954ee0d9897893b/admin/src/assets/blue-v.png -------------------------------------------------------------------------------- /admin/src/assets/custom-empty-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lrunlin/student-achievement/e14b8b4854ff98c424b75b112954ee0d9897893b/admin/src/assets/custom-empty-image.png -------------------------------------------------------------------------------- /admin/src/assets/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lrunlin/student-achievement/e14b8b4854ff98c424b75b112954ee0d9897893b/admin/src/assets/empty.png -------------------------------------------------------------------------------- /admin/src/assets/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lrunlin/student-achievement/e14b8b4854ff98c424b75b112954ee0d9897893b/admin/src/assets/logo.jpg -------------------------------------------------------------------------------- /admin/src/assets/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lrunlin/student-achievement/e14b8b4854ff98c424b75b112954ee0d9897893b/admin/src/assets/user.png -------------------------------------------------------------------------------- /admin/src/components/About.vue: -------------------------------------------------------------------------------- 1 | 85 | 88 | 89 | -------------------------------------------------------------------------------- /admin/src/components/Notice.vue: -------------------------------------------------------------------------------- 1 | 69 | 98 | 99 | -------------------------------------------------------------------------------- /admin/src/components/Upload.vue: -------------------------------------------------------------------------------- 1 | 14 | 35 | 36 | 43 | 44 | 66 | -------------------------------------------------------------------------------- /admin/src/components/admin/CreateNotice/Edit.vue: -------------------------------------------------------------------------------- 1 | 19 | 95 | 103 | -------------------------------------------------------------------------------- /admin/src/components/admin/CreateNotice/index.vue: -------------------------------------------------------------------------------- 1 | 50 | 91 | 92 | -------------------------------------------------------------------------------- /admin/src/components/admin/Navigate.vue: -------------------------------------------------------------------------------- 1 | 50 | 65 | 66 | -------------------------------------------------------------------------------- /admin/src/components/admin/college/CreateFrom.vue: -------------------------------------------------------------------------------- 1 | 11 | 39 | 40 | -------------------------------------------------------------------------------- /admin/src/components/admin/course/FromDialog.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /admin/src/components/admin/student/FromDialog.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /admin/src/components/admin/teacher/FromDialog.vue: -------------------------------------------------------------------------------- 1 | 100 | 101 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /admin/src/components/student/Navigate.vue: -------------------------------------------------------------------------------- 1 | 27 | 32 | 33 | -------------------------------------------------------------------------------- /admin/src/components/teacher/Navigate.vue: -------------------------------------------------------------------------------- 1 | 28 | 32 | 33 | -------------------------------------------------------------------------------- /admin/src/hooks/useFetch.js: -------------------------------------------------------------------------------- 1 | import { ref, watch } from "vue"; 2 | import axios from "axios"; 3 | 4 | function useFetch(axiosFun, options = { manual: false, deps: [] }) { 5 | const data = ref(null); 6 | const error = ref(null); 7 | const isLoading = ref(false); 8 | 9 | // 定义 fetchData 方法,用于发起请求 10 | const fetchData = async () => { 11 | isLoading.value = true; 12 | 13 | try { 14 | const response = await axiosFun(); 15 | if (options.callback) { 16 | data.value = options.callback(response.data) || response.data; 17 | } else { 18 | data.value = response.data; 19 | } 20 | } catch (err) { 21 | error.value = err; 22 | } finally { 23 | isLoading.value = false; 24 | } 25 | }; 26 | 27 | // 如果 manual 为 false,自动触发请求 28 | if (!options.manual) { 29 | // 如果 deps 数组为空,立即触发请求 30 | if (options?.deps == undefined || options?.deps?.length === 0) { 31 | fetchData(); // 立即发起请求 32 | } else { 33 | // 监控 deps 中的 ref 变量变化,触发请求 34 | watch( 35 | options.deps, 36 | () => { 37 | fetchData(); 38 | }, 39 | { immediate: true } 40 | ); // immediate: true 让第一次就触发请求 41 | } 42 | } 43 | 44 | // 如果 manual 为 true,返回一个手动触发请求的方法 45 | const refetch = () => { 46 | fetchData(); 47 | }; 48 | 49 | return { 50 | data, 51 | error, 52 | isLoading, 53 | refetch, // 仅在 manual 为 true 时需要手动触发请求 54 | }; 55 | } 56 | 57 | export default useFetch; 58 | -------------------------------------------------------------------------------- /admin/src/layouts/admin.vue: -------------------------------------------------------------------------------- 1 | 15 | 64 | -------------------------------------------------------------------------------- /admin/src/layouts/student.vue: -------------------------------------------------------------------------------- 1 | 13 | 55 | -------------------------------------------------------------------------------- /admin/src/layouts/teacher.vue: -------------------------------------------------------------------------------- 1 | 13 | 55 | -------------------------------------------------------------------------------- /admin/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | import { createRouter, createWebHistory, createWebHashHistory } from "vue-router"; 4 | 5 | import "@/style/index.scss"; 6 | import "@/plugin/axios"; 7 | import { createPinia } from "pinia"; 8 | import "moment/dist/locale/zh-cn"; 9 | const app = createApp(App); 10 | 11 | import ElementPlus from "element-plus"; 12 | import "element-plus/dist/index.css"; 13 | 14 | 15 | import { setupLayouts } from "virtual:generated-layouts"; 16 | import pages from "~pages"; 17 | 18 | const routes = setupLayouts(pages); 19 | const router = createRouter({ 20 | history: createWebHashHistory(), 21 | routes, 22 | }); 23 | 24 | app.use(router).use(createPinia()).use(ElementPlus).mount("#app"); 25 | -------------------------------------------------------------------------------- /admin/src/pages/admin/about.vue: -------------------------------------------------------------------------------- 1 | 4 | 7 | -------------------------------------------------------------------------------- /admin/src/pages/admin/class/index.vue: -------------------------------------------------------------------------------- 1 | 105 | 182 | 183 | -------------------------------------------------------------------------------- /admin/src/pages/admin/college/index.vue: -------------------------------------------------------------------------------- 1 | 91 | 165 | 166 | -------------------------------------------------------------------------------- /admin/src/pages/admin/course/index.vue: -------------------------------------------------------------------------------- 1 | 128 | 214 | 215 | -------------------------------------------------------------------------------- /admin/src/pages/admin/index.vue: -------------------------------------------------------------------------------- 1 | 44 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /admin/src/pages/admin/major/index.vue: -------------------------------------------------------------------------------- 1 | 121 | 208 | 209 | -------------------------------------------------------------------------------- /admin/src/pages/admin/notice/create.vue: -------------------------------------------------------------------------------- 1 | 6 | 25 | 26 | -------------------------------------------------------------------------------- /admin/src/pages/admin/notice/index.vue: -------------------------------------------------------------------------------- 1 | 58 | 90 | 91 | -------------------------------------------------------------------------------- /admin/src/pages/admin/password/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 78 | -------------------------------------------------------------------------------- /admin/src/pages/admin/student/index.vue: -------------------------------------------------------------------------------- 1 | 90 | 181 | 182 | -------------------------------------------------------------------------------- /admin/src/pages/admin/teacher/index.vue: -------------------------------------------------------------------------------- 1 | 112 | 233 | 234 | -------------------------------------------------------------------------------- /admin/src/pages/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /admin/src/pages/sign.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 95 | 96 | 101 | 102 | 147 | -------------------------------------------------------------------------------- /admin/src/pages/student/about.vue: -------------------------------------------------------------------------------- 1 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /admin/src/pages/student/index.vue: -------------------------------------------------------------------------------- 1 | 31 | 53 | 54 | -------------------------------------------------------------------------------- /admin/src/pages/student/notice/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 16 | 17 | -------------------------------------------------------------------------------- /admin/src/pages/student/password/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 78 | -------------------------------------------------------------------------------- /admin/src/pages/teacher/about.vue: -------------------------------------------------------------------------------- 1 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /admin/src/pages/teacher/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 36 | 37 | -------------------------------------------------------------------------------- /admin/src/pages/teacher/notice/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 16 | 17 | -------------------------------------------------------------------------------- /admin/src/pages/teacher/password/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 78 | -------------------------------------------------------------------------------- /admin/src/pages/teacher/score/index.vue: -------------------------------------------------------------------------------- 1 | 91 | 209 | 210 | -------------------------------------------------------------------------------- /admin/src/plugin/axios.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import cookie from "js-cookie"; 3 | import { ElMessage } from "element-plus"; 4 | 5 | axios.defaults.baseURL = import.meta.env.VITE_API_HOST; 6 | 7 | function tokenKey() { 8 | let hash = window.location.hash.split("/")[1]; 9 | let keys = ["admin", "student", "teacher"]; 10 | let token = keys.find(item => item == hash); 11 | 12 | return token + "_token"; 13 | } 14 | 15 | axios.interceptors.request.use( 16 | function (config) { 17 | // 在发送请求之前做些什么 18 | config.headers.authorization = cookie.get(tokenKey()); 19 | return config; 20 | }, 21 | function (error) { 22 | // 对请求错误做些什么 23 | return Promise.reject(error); 24 | } 25 | ); 26 | axios.interceptors.response.use( 27 | function (config) { 28 | return config; 29 | }, 30 | function (error) { 31 | let message = error?.response?.data?.message; 32 | // ElMessage.error(message); 33 | return Promise.reject(error); 34 | } 35 | ); 36 | -------------------------------------------------------------------------------- /admin/src/store/useUserData.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { ref } from "vue"; 3 | 4 | export const useUserData = defineStore("user-data", () => { 5 | const userData = ref(null); 6 | function set(val) { 7 | userData.value = val; 8 | } 9 | return { userData, set }; 10 | }); 11 | -------------------------------------------------------------------------------- /admin/src/style/index.scss: -------------------------------------------------------------------------------- 1 | // @tailwind base; 2 | @tailwind utilities; 3 | -------------------------------------------------------------------------------- /admin/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | corePlugins: { 4 | preflight: false 5 | }, 6 | content: ["./index.html", "./src/**/*.vue"], 7 | theme: { 8 | extend: { 9 | colors: { 10 | } 11 | } 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /admin/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import vue from "@vitejs/plugin-vue"; 3 | import path from "path"; 4 | import Pages from "vite-plugin-pages"; 5 | import Layouts from "vite-plugin-vue-layouts"; 6 | import AutoImport from "unplugin-auto-import/vite"; 7 | 8 | export default defineConfig({ 9 | resolve: { 10 | alias: { 11 | "@": path.resolve(__dirname, "./src"), 12 | }, 13 | extensions: [".js", ".vue"], 14 | }, 15 | base: "./", 16 | server: { 17 | port: 8001, 18 | }, 19 | build: { 20 | outDir: "front_end", 21 | }, 22 | plugins: [ 23 | vue(), 24 | Pages({ 25 | dirs: "src/pages", 26 | extensions: ["vue"], 27 | extendRoute(route) { 28 | if (route.path.startsWith("/admin") && !route.path.includes("sign")) { 29 | route.meta = Object.assign(route.meta || {}, { layout: "admin" }); 30 | } else if (route.path.startsWith("/teacher") && !route.path.includes("sign")) { 31 | route.meta = Object.assign(route.meta || {}, { layout: "teacher" }); 32 | } else if (route.path.startsWith("/student") && !route.path.includes("sign")) { 33 | route.meta = Object.assign(route.meta || {}, { layout: "student" }); 34 | } 35 | 36 | return route; 37 | }, 38 | }), 39 | Layouts(), 40 | AutoImport({ 41 | imports: ["vue", "vue-router"], 42 | // dts: "src/auto-import.d.ts", 43 | include: [/\.vue$/, /\.vue\?vue/], 44 | }), 45 | ], 46 | }); 47 | -------------------------------------------------------------------------------- /dev.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | start cmd /k "cd server&&yarn dev" 3 | start cmd /k "cd admin&&yarn dev" 4 | -------------------------------------------------------------------------------- /intall.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | start cmd /k "npm config set registry https://registry.npmmirror.com/&&npm i yarn vite nodemon cross-env -g&&yarn config set registry https://registry.npmmirror.com/&&cd server&&yarn&&cd ../admin&&yarn&& exit" 3 | -------------------------------------------------------------------------------- /server/.env.dev: -------------------------------------------------------------------------------- 1 | #数据库用户名 2 | DB_USERNAME=root 3 | #数据库密码 4 | DB_PASSWORD=123456 5 | #数据库地址 6 | DB_HOST=127.0.0.1 7 | # 数据库端口 8 | DB_PORT=3306 9 | #JWT的key 用于签名混淆 用这个就行(不用改) 10 | KEY=key -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "student-performance-management-system", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "dev": "cross-env NODE_ENV=dev nodemon ./src/index.js", 8 | "test": "cross-env NODE_ENV=test nodemon ./src/index.js" 9 | }, 10 | "dependencies": { 11 | "@koa/cors": "^5.0.0", 12 | "@koa/multer": "^3.0.2", 13 | "axios": "^1.6.5", 14 | "cheerio": "^1.0.0-rc.10", 15 | "cookie": "^1.0.2", 16 | "cross-env": "^7.0.3", 17 | "dotenv": "^16.4.7", 18 | "jsonwebtoken": "^9.0.2", 19 | "koa": "^2.13.4", 20 | "koa-bodyparser": "^4.4.1", 21 | "koa-router": "^13.0.1", 22 | "koa-static": "^5.0.0", 23 | "lodash-toolkit": "latest", 24 | "module-alias": "^2.2.2", 25 | "moment": "^2.29.1", 26 | "multer": "^1.4.5-lts.1", 27 | "mysql2": "^3.14.1", 28 | "node-xlsx": "^0.24.0", 29 | "nodemailer": "^6.9.8", 30 | "nodemon": "^3.1.0" 31 | }, 32 | "devDependencies": {} 33 | } 34 | -------------------------------------------------------------------------------- /server/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow:/ -------------------------------------------------------------------------------- /server/public/说明.txt: -------------------------------------------------------------------------------- 1 | 代写毕设、项目、课设、论文。 2 | QQ:1974109227 3 | 微信:webzhizuo 4 | 5 | 详情见README.md -------------------------------------------------------------------------------- /server/src/db/index.js: -------------------------------------------------------------------------------- 1 | let mysql = require("mysql2/promise"); 2 | 3 | const connection = mysql.createPool({ 4 | host: process.env.DB_HOST, 5 | port: process.env.DB_PORT || 3306, 6 | database: "stu_score", 7 | user: process.env.DB_USERNAME, 8 | password: process.env.DB_PASSWORD, 9 | }); 10 | module.exports = connection; 11 | -------------------------------------------------------------------------------- /server/src/index.js: -------------------------------------------------------------------------------- 1 | // 变量别名 2 | let fs = require("fs"); 3 | if (fs.existsSync("./test.js")) { 4 | require("../test.js"); 5 | } 6 | 7 | const moduleAlias = require("module-alias"); 8 | moduleAlias.addAlias("@", __dirname); 9 | let dotenv = require("dotenv"); 10 | dotenv.config({ 11 | path: `.env.${process.env.NODE_ENV}`, // 配置文件路径 12 | }); 13 | let Koa = require("koa"); 14 | const app = new Koa(); 15 | let { getAllRouter } = require("lodash-toolkit"); 16 | let staticFiles = require("koa-static"); 17 | let path = require("path"); 18 | 19 | //测试环境限制 20 | const testEnvMiddleware = require("@/modules/test"); 21 | 22 | let Body = require("koa-bodyparser"); 23 | let cors = require("@koa/cors"); 24 | app.use(staticFiles("public")); 25 | app.use(cors()); 26 | app.use(Body()); 27 | app.use(testEnvMiddleware); 28 | 29 | (async () => { 30 | (await getAllRouter(path.join(__dirname, "./routes"))).forEach(item => { 31 | app.use(require(item).routes()); 32 | }); 33 | })(); 34 | 35 | const port = 3456; 36 | const http = require("http"); 37 | const server = http.createServer(app.callback()); //包装app保证http和socket监听同一端口 38 | module.exports = { 39 | server, 40 | }; 41 | 42 | server.listen(port, function () { 43 | console.log(` 44 | 项目运行于: ${port} 端口,代写毕设、项目、课设、论文。QQ:1974109227微信:webzhizuo 45 | `); 46 | }); 47 | -------------------------------------------------------------------------------- /server/src/modules/auth.js: -------------------------------------------------------------------------------- 1 | let jwt = require("jsonwebtoken"); 2 | 3 | async function verify(token) { 4 | return new Promise((resolve, reject) => { 5 | jwt.verify(token, process.env.KEY, async function (err, decoded) { 6 | if (err) { 7 | reject(); 8 | } else { 9 | resolve(decoded); 10 | } 11 | }); 12 | }); 13 | } 14 | 15 | function auth(arr) { 16 | return async (ctx, next) => { 17 | let token = ctx.headers.authorization; 18 | 19 | if (!token) { 20 | ctx.status = 401; 21 | return; 22 | } 23 | 24 | await verify(token) 25 | .then(async decoded => { 26 | if (arr) { 27 | if (!["a", ...arr].some(item => decoded.type.startsWith(item))) { 28 | ctx.status = 401; 29 | return; 30 | } 31 | } 32 | ctx.id = decoded.id; 33 | ctx.auth = decoded.type; 34 | ctx.username = decoded.username; 35 | await next(); 36 | }) 37 | .catch(err => { 38 | ctx.status = 401; 39 | }); 40 | }; 41 | } 42 | module.exports = auth; 43 | -------------------------------------------------------------------------------- /server/src/modules/signToken.js: -------------------------------------------------------------------------------- 1 | let jwt = require("jsonwebtoken"); 2 | function sign(obj) { 3 | const token = jwt.sign(obj, process.env.KEY); 4 | return token; 5 | } 6 | module.exports = sign; 7 | -------------------------------------------------------------------------------- /server/src/modules/test.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | 3 | //测试环境限制部分插件 4 | 5 | async function verify(token) { 6 | return new Promise((resolve, reject) => { 7 | jwt.verify(token, process.env.KEY, async function (err, decoded) { 8 | if (err) { 9 | reject(); 10 | } else { 11 | resolve(decoded); 12 | } 13 | }); 14 | }); 15 | } 16 | 17 | // 创建中间件 18 | async function testEnvMiddleware(ctx, next) { 19 | const isTestEnv = process.env.NODE_ENV == "test"; 20 | 21 | if (!isTestEnv) { 22 | await next(); 23 | return; 24 | } 25 | 26 | let decoded = await verify(ctx.headers.authorization) 27 | .then(res => res) 28 | .catch(err => ({})); 29 | 30 | // 1. 禁止修改 admin 账号密码 31 | if (ctx.method == "PUT" && ctx.path == "/admin-password") { 32 | ctx.status = 403; 33 | ctx.body = { 34 | success: false, 35 | message: "禁止在测试环境修改 admin 账号的密码。", 36 | }; 37 | return; 38 | } 39 | 40 | // 2. 禁止删除 ID 为 1 的教师和学生 41 | if ( 42 | ctx.method == "DELETE" && 43 | (ctx.path.startsWith("/teacher/") || ctx.path.startsWith("/student/")) 44 | ) { 45 | const id = ctx.params.id; 46 | if (id == "1" || id == "2") { 47 | ctx.status = 403; 48 | ctx.body = { 49 | success: false, 50 | message: "禁止在测试环境删除 ID 为 1、2 的教师或学生。", 51 | }; 52 | return; 53 | } 54 | } 55 | 56 | // 3. 禁止访问 POST 请求的 /static 路径 57 | if (ctx.method == "POST" && ctx.path == "/static") { 58 | ctx.status = 403; 59 | ctx.body = { 60 | success: false, 61 | message: "禁止在测试环境访问 POST 请求的 /static 路径。", 62 | }; 63 | return; 64 | } 65 | 66 | // 4. 禁止修改 ID 为 1 的教师和学生的密码 67 | if ( 68 | ctx.method == "PUT" && 69 | (decoded.id == 1 || decoded.id == 2) && 70 | (ctx.path == "/teacher-password" || ctx.path == "/student-password") 71 | ) { 72 | ctx.status = 403; 73 | ctx.body = { success: false, message: "禁止在测试环境修改 ID 为 1、2 的教师或学生的密码。" }; 74 | return; 75 | } 76 | 77 | // 5. 禁止修改学期 78 | if (ctx.method == "PUT" && ctx.path == "/semester") { 79 | ctx.status = 403; 80 | ctx.body = { success: false, message: "禁止在测试环境修改学期。" }; 81 | return; 82 | } 83 | 84 | // 6. 禁止发布和删除公告 85 | if (ctx.method == "POST" && ctx.path == "/notice") { 86 | ctx.status = 403; 87 | ctx.body = { success: false, message: "禁止在测试环境发布公告。" }; 88 | return; 89 | } 90 | if (ctx.method == "DELETE" && ctx.path.startsWith("/notice")) { 91 | ctx.status = 403; 92 | ctx.body = { success: false, message: "禁止在测试环境删除公告。" }; 93 | return; 94 | } 95 | 96 | await next(); 97 | } 98 | module.exports = testEnvMiddleware; 99 | -------------------------------------------------------------------------------- /server/src/routes/admin/class/course-by-class.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 假设你的数据库查询模块 3 | const router = new Router(); 4 | 5 | // 根据课程ID查询该课程所属专业下的班级及专业名称 6 | router.get("/course-by-class/:courseId", async ctx => { 7 | const courseId = ctx.params.courseId; 8 | 9 | try { 10 | // 查询课程对应的专业ID 11 | const courseQuery = `SELECT major FROM course WHERE id = ?`; 12 | const [courseResult] = await db.query(courseQuery, [courseId]); 13 | 14 | if (courseResult.length === 0) { 15 | ctx.body = { success: false, message: "课程不存在" }; 16 | return; 17 | } 18 | 19 | const majorId = courseResult[0].major; 20 | 21 | // 查询该专业的名称 22 | const majorQuery = `SELECT name FROM major WHERE id = ?`; 23 | const [majorResult] = await db.query(majorQuery, [majorId]); 24 | 25 | if (majorResult.length === 0) { 26 | ctx.body = { success: false, message: "该专业不存在" }; 27 | return; 28 | } 29 | 30 | const majorName = majorResult[0].name; 31 | 32 | // 查询该专业下的所有班级,并将专业名称添加到班级信息中 33 | const classQuery = `SELECT id, \`index\` AS class_index FROM class_list WHERE major_id = ?`; 34 | const [classResult] = await db.query(classQuery, [majorId]); 35 | 36 | // 将专业名称添加到每个班级信息中 37 | const classWithMajorName = classResult.map(classItem => ({ 38 | ...classItem, 39 | major_name: majorName, 40 | })); 41 | 42 | // 返回班级数据,并附带专业名称 43 | ctx.body = { 44 | success: true, 45 | message: "查询成功", 46 | data: classWithMajorName, 47 | }; 48 | } catch (error) { 49 | console.error(error); 50 | ctx.body = { success: false, message: "查询失败,请稍后再试" }; 51 | } 52 | }); 53 | 54 | module.exports = router; 55 | -------------------------------------------------------------------------------- /server/src/routes/admin/class/create.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | let db = require("@/db"); 3 | let auth = require("@/modules/auth"); 4 | let { id } = require("lodash-toolkit"); 5 | let router = new Router(); 6 | 7 | router.post("/class", auth(["a"]), async ctx => { 8 | try { 9 | // 获取请求参数 10 | const { major_id } = ctx.request.body; 11 | 12 | // 校验专业ID是否提供 13 | if (!major_id || isNaN(major_id)) { 14 | ctx.body = { success: false, message: "无效的专业ID" }; 15 | return; 16 | } 17 | 18 | // 查询该专业下当前最大的班级编号 (index) 19 | const maxIndexQuery = ` 20 | SELECT MAX(\`index\`) AS max_index 21 | FROM class_list 22 | WHERE major_id = ? 23 | `; 24 | const [maxIndexResult] = await db.query(maxIndexQuery, [major_id]); 25 | 26 | // 获取当前最大的班级编号 +1 27 | const nextIndex = maxIndexResult[0]?.max_index + 1 || 1; // 如果没有班级,则从1开始 28 | 29 | // 插入新的班级,使用id()生成唯一的id 30 | const insertClassQuery = ` 31 | INSERT INTO class_list (id, major_id, \`index\`) 32 | VALUES (?, ?, ?) 33 | `; 34 | const [insertResult] = await db.query(insertClassQuery, [id(), major_id, nextIndex]); 35 | 36 | // 返回成功结果 37 | ctx.body = { 38 | success: true, 39 | message: `班级创建成功,班级编号为 ${nextIndex}`, 40 | data: { 41 | class_id: insertResult.insertId, // 返回新创建的班级ID 42 | major_id, 43 | index: nextIndex, 44 | }, 45 | }; 46 | } catch (error) { 47 | console.error(error); 48 | ctx.body = { success: false, message: "创建班级失败,请稍后再试" }; 49 | } 50 | }); 51 | 52 | module.exports = router; 53 | -------------------------------------------------------------------------------- /server/src/routes/admin/class/delete.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | let db = require("@/db"); 3 | let auth = require("@/modules/auth"); 4 | let router = new Router(); 5 | 6 | router.delete("/class/:class_id", auth(["a"]), async ctx => { 7 | const classId = ctx.params.class_id; // 获取班级ID 8 | 9 | // 校验班级ID参数 10 | if (isNaN(classId)) { 11 | ctx.body = { success: false, message: "无效的班级ID" }; 12 | return; 13 | } 14 | 15 | try { 16 | // 1. 查询是否有学生关联到该班级 17 | const checkStudentsQuery = ` 18 | SELECT COUNT(*) AS student_count 19 | FROM student 20 | WHERE class_id = ? 21 | `; 22 | const [studentResult] = await db.query(checkStudentsQuery, [classId]); 23 | 24 | // 如果有学生关联,返回错误提示 25 | if (studentResult[0].student_count > 0) { 26 | ctx.body = { success: false, message: "无法删除该班级,因为已有学生在该班级中。" }; 27 | return; 28 | } 29 | 30 | // 2. 执行删除班级操作 31 | const deleteClassQuery = ` 32 | DELETE FROM class_list 33 | WHERE id = ? 34 | `; 35 | const [deleteResult] = await db.query(deleteClassQuery, [classId]); 36 | 37 | // 3. 检查删除结果 38 | if (deleteResult.affectedRows === 0) { 39 | ctx.body = { success: false, message: "删除失败,未找到该班级。" }; 40 | return; 41 | } 42 | 43 | // 4. 返回成功响应 44 | ctx.body = { success: true, message: "班级删除成功。" }; 45 | } catch (error) { 46 | console.error(error); 47 | ctx.body = { success: false, message: "系统错误,请稍后再试。" }; 48 | } 49 | }); 50 | 51 | module.exports = router; 52 | -------------------------------------------------------------------------------- /server/src/routes/admin/class/major-list.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | let db = require("@/db"); 3 | let auth = require("@/modules/auth"); 4 | let router = new Router(); 5 | 6 | router.get("/class/:major_id", auth(["a"]), async ctx => { 7 | try { 8 | // 获取专业ID 9 | const majorId = parseInt(ctx.params.major_id); 10 | 11 | // 校验专业ID是否有效 12 | if (isNaN(majorId)) { 13 | ctx.body = { success: false, message: "无效的专业ID" }; 14 | return; 15 | } 16 | 17 | // 查询该专业的所有班级及每个班级的学生数量 18 | const query = ` 19 | SELECT 20 | cl.id AS class_id, 21 | cl.index AS class_index, -- 班级编号 (例如:第一班、第二班等) 22 | cl.major_id, 23 | COUNT(s.id) AS student_count -- 学生数量 24 | FROM class_list cl 25 | LEFT JOIN student s ON s.class_id = cl.id 26 | WHERE cl.major_id = ? 27 | GROUP BY cl.id 28 | ORDER BY cl.index; 29 | `; 30 | 31 | const [classes] = await db.query(query, [majorId]); 32 | 33 | // 返回查询结果 34 | ctx.body = { 35 | success: true, 36 | data: { 37 | major_id: majorId, 38 | list: classes, 39 | }, 40 | }; 41 | } catch (error) { 42 | console.error(error); 43 | ctx.body = { success: false, message: "查询失败,请稍后再试" }; 44 | } 45 | }); 46 | 47 | module.exports = router; 48 | -------------------------------------------------------------------------------- /server/src/routes/admin/class/update.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | let db = require("@/db"); 3 | let auth = require("@/modules/auth"); 4 | let router = new Router(); 5 | 6 | router.put("/class/:class_id", auth(["a"]), async ctx => { 7 | const classId = ctx.params.class_id; // 获取班级ID 8 | const { index } = ctx.request.body; // 获取请求体中的班级index 9 | 10 | // 校验请求参数 11 | if (!index || isNaN(classId)) { 12 | ctx.body = { success: false, message: "参数无效,请提供班级ID和index。" }; 13 | return; 14 | } 15 | 16 | try { 17 | // 1. 根据班级ID查询专业ID 18 | const getMajorIdQuery = ` 19 | SELECT major_id 20 | FROM class_list 21 | WHERE id = ? 22 | `; 23 | const [majorResult] = await db.query(getMajorIdQuery, [classId]); 24 | 25 | if (majorResult.length === 0) { 26 | ctx.body = { success: false, message: "未找到该班级。" }; 27 | return; 28 | } 29 | 30 | const majorId = majorResult[0].major_id; 31 | 32 | // 2. 检查该专业下是否已存在相同的index 33 | const checkDuplicateQuery = ` 34 | SELECT 1 35 | FROM class_list 36 | WHERE major_id = ? AND \`index\` = ? AND id != ? 37 | `; 38 | const [duplicateResult] = await db.query(checkDuplicateQuery, [majorId, index, classId]); 39 | 40 | if (duplicateResult.length > 0) { 41 | ctx.body = { success: false, message: "该专业下已存在相同编号的班级。" }; 42 | return; 43 | } 44 | 45 | // 3. 执行更新操作 46 | const updateQuery = ` 47 | UPDATE class_list 48 | SET \`index\` = ? 49 | WHERE id = ? 50 | `; 51 | const [updateResult] = await db.query(updateQuery, [index, classId]); 52 | 53 | // 4. 检查更新结果 54 | if (updateResult.affectedRows === 0) { 55 | ctx.body = { success: false, message: "更新失败,未找到该班级。" }; 56 | return; 57 | } 58 | 59 | // 5. 返回成功响应 60 | ctx.body = { success: true, message: "班级信息更新成功。" }; 61 | } catch (error) { 62 | console.error(error); 63 | ctx.body = { success: false, message: "系统错误,请稍后再试。" }; 64 | } 65 | }); 66 | 67 | module.exports = router; 68 | -------------------------------------------------------------------------------- /server/src/routes/admin/college/create.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | let db = require("@/db"); 3 | let auth = require("@/modules/auth"); 4 | let { id } = require("lodash-toolkit"); 5 | let router = new Router(); 6 | 7 | router.post("/college", auth(["a"]), async ctx => { 8 | const { name } = ctx.request.body; 9 | 10 | // 检查学院名称是否为空 11 | if (!name) { 12 | ctx.body = { success: false, message: "学院名称不能为空" }; 13 | return; 14 | } 15 | 16 | try { 17 | // 检查是否已有同名学院 18 | const [existingCollege] = await db.query(`SELECT * FROM college WHERE name = ?`, [name]); 19 | 20 | if (existingCollege.length > 0) { 21 | // 如果已存在,返回错误信息 22 | ctx.body = { success: false, message: "学院名称已存在" }; 23 | return; 24 | } 25 | 26 | // 插入新的学院数据 27 | const result = await db.query( 28 | `INSERT INTO college (id,name, create_time) VALUES (?,?, ?)`, 29 | [id(), name, new Date()] // 使用当前时间作为默认创建时间 30 | ); 31 | 32 | // 返回创建成功的结果 33 | ctx.body = { 34 | success: true, 35 | message: "学院创建成功", 36 | data: { 37 | id: result.insertId, 38 | name, 39 | }, 40 | }; 41 | } catch (error) { 42 | ctx.body = { success: false, message: "创建学院失败,请稍后再试" }; 43 | } 44 | }); 45 | 46 | module.exports = router; 47 | -------------------------------------------------------------------------------- /server/src/routes/admin/college/delete.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 数据库模块 3 | const auth = require("@/modules/auth"); // 权限验证模块 4 | const router = new Router(); 5 | 6 | router.delete("/college/:id", auth(["a"]), async ctx => { 7 | const { id } = ctx.params; // 获取路径参数,即学院ID 8 | 9 | try { 10 | // 1. 检查学院下是否有专业 11 | const majorQuery = `SELECT 1 FROM major WHERE college_id = ?`; 12 | const [majorRes] = await db.query(majorQuery, [id]); 13 | if (majorRes.length > 0) { 14 | ctx.body = { success: false, message: "该学院下有专业,无法删除" }; 15 | return; 16 | } 17 | 18 | // 2. 检查学院下是否有教师 19 | const teacherQuery = `SELECT 1 FROM teacher WHERE college_id = ?`; 20 | const [teacherRes] = await db.query(teacherQuery, [id]); 21 | if (teacherRes.length > 0) { 22 | ctx.body = { success: false, message: "该学院下有教师,无法删除" }; 23 | return; 24 | } 25 | 26 | // 3. 检查学院下是否有学生 27 | const studentQuery = `SELECT 1 FROM student WHERE major_id IN (SELECT id FROM major WHERE college_id = ?)`; 28 | const [studentRes] = await db.query(studentQuery, [id]); 29 | if (studentRes.length > 0) { 30 | ctx.body = { success: false, message: "该学院下有学生,无法删除" }; 31 | return; 32 | } 33 | 34 | // 4. 删除学院 35 | const deleteQuery = `DELETE FROM college WHERE id = ?`; 36 | const [deleteRes] = await db.query(deleteQuery, [id]); 37 | 38 | // 判断是否删除成功 39 | if (deleteRes.affectedRows > 0) { 40 | ctx.body = { success: true, message: "学院删除成功" }; 41 | } else { 42 | ctx.body = { success: false, message: "学院删除失败,可能该学院不存在" }; 43 | } 44 | } catch (error) { 45 | console.error(error); 46 | ctx.body = { success: false, message: "删除学院失败,请稍后再试" }; 47 | } 48 | }); 49 | 50 | module.exports = router; 51 | -------------------------------------------------------------------------------- /server/src/routes/admin/college/list.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | let db = require("@/db"); 3 | let auth = require("@/modules/auth"); 4 | let router = new Router(); 5 | 6 | router.get(["/college/:page", "/college"], auth(["a"]), async ctx => { 7 | try { 8 | // 获取分页参数 9 | const page = parseInt(ctx.params.page) || 1; // 默认第一页 10 | const pageSize = parseInt(ctx.query.pageSize) || 10; // 默认每页10条 11 | const keyword = ctx.query.keyword || ""; // 获取关键词参数 12 | 13 | // 计算分页偏移量 14 | const offset = (page - 1) * pageSize; 15 | 16 | // 构建查询条件 17 | let query = ` 18 | SELECT 19 | c.id AS college_id, 20 | c.name AS college_name, 21 | COUNT(DISTINCT t.id) AS teacher_count, 22 | COUNT(DISTINCT m.id) AS major_count, 23 | SUM(IFNULL(s.student_count, 0)) AS student_count 24 | FROM college c 25 | LEFT JOIN major m ON m.college_id = c.id 26 | LEFT JOIN teacher t ON t.college_id = c.id 27 | LEFT JOIN ( 28 | SELECT s.major_id, COUNT(s.id) AS student_count 29 | FROM student s 30 | GROUP BY s.major_id 31 | ) s ON s.major_id = m.id 32 | `; 33 | 34 | // 如果有 keyword,添加模糊查询条件 35 | if (keyword) { 36 | query += ` WHERE c.name LIKE ?`; 37 | } 38 | 39 | // 如果需要分页,添加 LIMIT 子句 40 | let params; 41 | if (ctx.params.page) { 42 | query += ` GROUP BY c.id LIMIT ?, ?`; 43 | params = keyword ? [`%${keyword}%`, offset, pageSize] : [offset, pageSize]; 44 | } else { 45 | query += ` GROUP BY c.id`; // 不分页时不需要 LIMIT 46 | params = keyword ? [`%${keyword}%`] : []; 47 | } 48 | 49 | // 执行查询 50 | const [colleges] = await db.query(query, params); 51 | 52 | // 查询学院总数,用于分页(如果没有分页,仍然需要获取总数) 53 | let countQuery = `SELECT COUNT(*) AS total FROM college`; 54 | if (keyword) { 55 | countQuery += ` WHERE name LIKE ?`; 56 | } 57 | 58 | const countParams = keyword ? [`%${keyword}%`] : []; 59 | const [countRes] = await db.query(countQuery, countParams); 60 | const total = countRes[0]?.total || 0; 61 | 62 | // 返回分页数据或所有数据 63 | ctx.body = { 64 | success: true, 65 | data: { 66 | total, 67 | page: ctx.params.page || 1, 68 | pageSize, 69 | list: colleges, 70 | }, 71 | }; 72 | } catch (error) { 73 | console.error(error); 74 | ctx.body = { success: false, message: "查询失败,请稍后再试" }; 75 | } 76 | }); 77 | 78 | module.exports = router; 79 | -------------------------------------------------------------------------------- /server/src/routes/admin/college/update.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | let db = require("@/db"); 3 | let auth = require("@/modules/auth"); 4 | let router = new Router(); 5 | 6 | router.put("/college/:id", auth(["a"]), async ctx => { 7 | const { id } = ctx.params; // 获取 URL 参数中的学院ID 8 | const { name } = ctx.request.body; // 获取请求体中的新学院名称 9 | 10 | // 校验新学院名称是否为空 11 | if (!name) { 12 | ctx.body = { success: false, message: "学院名称不能为空" }; 13 | return; 14 | } 15 | 16 | try { 17 | // 检查是否已有同名学院 18 | const [existingCollege] = await db.query(`SELECT * FROM college WHERE name = ? ;`, [ 19 | name, 20 | ]); 21 | 22 | if (existingCollege.length > 0) { 23 | // 如果已存在同名学院(排除当前要修改的学院),返回错误信息 24 | ctx.body = { success: false, message: "学院名称已存在" }; 25 | return; 26 | } 27 | 28 | // 更新学院名称 29 | const [result] = await db.query(`UPDATE college SET name = ? WHERE id = ?`, [name, id]); 30 | 31 | if (result.affectedRows === 0) { 32 | // 如果没有修改任何记录,说明未找到该学院 33 | ctx.body = { success: false, message: "学院ID不存在" }; 34 | return; 35 | } 36 | 37 | // 返回成功消息 38 | ctx.body = { 39 | success: true, 40 | message: "学院名称更新成功", 41 | data: { 42 | id, 43 | name, 44 | }, 45 | }; 46 | } catch (error) { 47 | // 捕获异常并返回错误信息 48 | console.error(error); 49 | ctx.body = { success: false, message: "更新失败,请稍后再试" }; 50 | } 51 | }); 52 | 53 | module.exports = router; 54 | -------------------------------------------------------------------------------- /server/src/routes/admin/course/college-course-list.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 假设你的数据库查询模块 3 | const router = new Router(); 4 | 5 | // 根据学院ID查询该学院下的课程列表及所属专业 6 | router.get("/course-by-college/:collegeId", async ctx => { 7 | const collegeId = ctx.params.collegeId; 8 | 9 | try { 10 | // 查询该学院下的所有专业 11 | const majorQuery = `SELECT id FROM major WHERE college_id = ?`; 12 | const [majorResult] = await db.query(majorQuery, [collegeId]); 13 | 14 | if (majorResult.length === 0) { 15 | ctx.body = { success: false, message: "该学院没有专业" }; 16 | return; 17 | } 18 | 19 | // 获取所有专业的ID 20 | const majorIds = majorResult.map(major => major.id); 21 | 22 | // 查询这些专业下的所有课程,并同时查询所属的专业名称 23 | const courseQuery = ` 24 | SELECT c.id AS course_id, c.name AS course_name, c.major AS major_id, m.name AS major_name 25 | FROM course c 26 | LEFT JOIN major m ON c.major = m.id 27 | WHERE c.major IN (?) 28 | `; 29 | const [courseResult] = await db.query(courseQuery, [majorIds]); 30 | 31 | // 返回课程数据,包含课程信息和所属专业名称 32 | ctx.body = { 33 | success: true, 34 | message: "查询成功", 35 | data: courseResult, 36 | }; 37 | } catch (error) { 38 | console.error(error); 39 | ctx.body = { success: false, message: "查询失败,请稍后再试" }; 40 | } 41 | }); 42 | 43 | module.exports = router; 44 | -------------------------------------------------------------------------------- /server/src/routes/admin/course/create.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | let db = require("@/db"); 3 | let auth = require("@/modules/auth"); 4 | let { id } = require("lodash-toolkit"); 5 | let router = new Router(); 6 | 7 | // 创建课程接口 8 | router.post("/course", auth(["a"]), async ctx => { 9 | try { 10 | // 获取请求体中的数据 11 | const { name, credit, total_score, major_id, semester } = ctx.request.body; 12 | 13 | // 校验请求参数 14 | if (!name || !credit || !total_score || !major_id || !semester) { 15 | ctx.body = { success: false, message: "课程名称、学分、总分、专业ID和学期不能为空" }; 16 | return; 17 | } 18 | 19 | // 校验专业是否存在 20 | const majorQuery = `SELECT 1 FROM major WHERE id = ?`; 21 | const [majorRes] = await db.query(majorQuery, [major_id]); 22 | if (majorRes.length === 0) { 23 | ctx.body = { success: false, message: "指定的专业不存在" }; 24 | return; 25 | } 26 | 27 | // 插入新的课程记录 28 | const courseId = id(); // 生成新的课程ID 29 | const insertCourseQuery = `INSERT INTO course (id, name, credit, total_score, major, semester) VALUES (?, ?, ?, ?, ?, ?)`; 30 | const [insertRes] = await db.query(insertCourseQuery, [ 31 | courseId, 32 | name, 33 | credit, 34 | total_score, 35 | major_id, 36 | semester, 37 | ]); 38 | 39 | // 返回成功结果 40 | ctx.body = { 41 | success: true, 42 | message: "课程创建成功", 43 | data: { course_id: insertRes.insertId, name, credit, total_score, major_id, semester }, 44 | }; 45 | } catch (error) { 46 | console.error(error); 47 | ctx.body = { success: false, message: "创建课程失败,请稍后再试" }; 48 | } 49 | }); 50 | 51 | module.exports = router; 52 | -------------------------------------------------------------------------------- /server/src/routes/admin/course/delete.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 假设你的数据库查询模块 3 | const router = new Router(); 4 | const auth = require("@/modules/auth"); // 引入 auth 模块 5 | 6 | router.delete("/course/:courseId", auth(["a"]), async ctx => { 7 | const courseId = ctx.params.courseId; // 获取请求中的课程ID 8 | 9 | if (!courseId) { 10 | ctx.body = { success: false, message: "课程ID不能为空" }; 11 | return; 12 | } 13 | 14 | try { 15 | // 1. 删除教师与课程之间的关联 16 | const deleteTeacherCourseQuery = ` 17 | DELETE FROM teacher_course 18 | WHERE course_id = ?`; 19 | await db.query(deleteTeacherCourseQuery, [courseId]); 20 | 21 | // 2. 删除成绩与课程之间的关联 22 | const deleteScoreCourseQuery = ` 23 | DELETE FROM score 24 | WHERE course_id = ?`; 25 | await db.query(deleteScoreCourseQuery, [courseId]); 26 | 27 | // 3. 删除课程记录 28 | const deleteCourseQuery = ` 29 | DELETE FROM course 30 | WHERE id = ?`; 31 | const [courseDeleteResult] = await db.query(deleteCourseQuery, [courseId]); 32 | 33 | if (courseDeleteResult.affectedRows === 0) { 34 | ctx.body = { success: false, message: "未找到指定课程" }; 35 | return; 36 | } 37 | 38 | // 返回成功信息 39 | ctx.body = { 40 | success: true, 41 | message: "课程删除成功", 42 | }; 43 | } catch (error) { 44 | console.error(error); 45 | ctx.body = { success: false, message: "删除课程失败,请稍后再试" }; 46 | } 47 | }); 48 | 49 | module.exports = router; 50 | -------------------------------------------------------------------------------- /server/src/routes/admin/course/list.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 数据库模块 3 | const router = new Router(); 4 | 5 | router.get("/course", async ctx => { 6 | try { 7 | // 获取查询参数 8 | const { page, pageSize, course_id, major_id, course_name } = ctx.query; 9 | 10 | // 设置分页默认值 11 | const currentPage = page ? parseInt(page) : 1; 12 | const size = pageSize ? parseInt(pageSize) : 10; 13 | const offset = (currentPage - 1) * size; 14 | 15 | // 构建查询条件 16 | let whereClauses = []; 17 | let queryParams = []; 18 | 19 | // 根据课程ID查询 20 | if (course_id) { 21 | whereClauses.push("course.id = ?"); 22 | queryParams.push(course_id); 23 | } 24 | 25 | // 根据专业名称模糊查询 26 | if (major_id) { 27 | whereClauses.push("major.id LIKE ?"); 28 | queryParams.push(`%${major_id}%`); 29 | } 30 | 31 | // 根据专业ID查询 32 | if (course_name) { 33 | whereClauses.push("course.major = ?"); 34 | queryParams.push(course_name); 35 | } 36 | 37 | // 根据学期排序 38 | let orderBy = "ORDER BY course.semester ASC"; 39 | 40 | // 如果有查询条件,拼接WHERE子句 41 | const whereSql = whereClauses.length > 0 ? "WHERE " + whereClauses.join(" AND ") : ""; 42 | 43 | // 查询课程总数 44 | const totalQuery = ` 45 | SELECT COUNT(*) AS total 46 | FROM course 47 | LEFT JOIN major ON course.major = major.id 48 | ${whereSql} 49 | `; 50 | const [totalResult] = await db.query(totalQuery, queryParams); 51 | const total = totalResult[0].total; 52 | 53 | // 查询课程列表 54 | const coursesQuery = ` 55 | SELECT course.id, course.name,course.major, course.credit, course.total_score, course.semester, major.name AS major_name 56 | FROM course 57 | LEFT JOIN major ON course.major = major.id 58 | ${whereSql} 59 | ${orderBy} 60 | LIMIT ? OFFSET ? 61 | `; 62 | queryParams.push(size, offset); 63 | const [courses] = await db.query(coursesQuery, queryParams); 64 | 65 | // 返回结果 66 | ctx.body = { 67 | success: true, 68 | data: { 69 | list: courses, 70 | total, 71 | currentPage, 72 | pageSize: size, 73 | totalPages: Math.ceil(total / size), 74 | }, 75 | }; 76 | } catch (error) { 77 | console.error(error); 78 | ctx.body = { success: false, message: "查询课程失败,请稍后再试" }; 79 | } 80 | }); 81 | 82 | module.exports = router; 83 | -------------------------------------------------------------------------------- /server/src/routes/admin/course/update.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 数据库模块 3 | const auth = require("@/modules/auth"); // 引入 auth 模块 4 | const router = new Router(); 5 | 6 | router.put("/course/:id", auth(["a"]), async ctx => { 7 | const { id } = ctx.params; // 获取路径参数,即课程ID 8 | const { name, credit, total_score, semester, major_id } = ctx.request.body; // 获取请求体中的课程信息 9 | 10 | try { 11 | // 校验必填参数 12 | if (!name || !credit || !total_score || !semester || !major_id) { 13 | ctx.body = { success: false, message: "课程名称、学分、总分、学期、专业ID不能为空" }; 14 | return; 15 | } 16 | 17 | // 校验课程是否存在 18 | const courseQuery = `SELECT 1 FROM course WHERE id = ?`; 19 | const [courseRes] = await db.query(courseQuery, [id]); 20 | if (courseRes.length === 0) { 21 | ctx.body = { success: false, message: "课程不存在" }; 22 | return; 23 | } 24 | 25 | // 更新课程信息 26 | const updateQuery = ` 27 | UPDATE course 28 | SET name = ?, credit = ?, total_score = ?, semester = ?, major = ? 29 | WHERE id = ? 30 | `; 31 | const [updateRes] = await db.query(updateQuery, [ 32 | name, 33 | credit, 34 | total_score, 35 | semester, 36 | major_id, 37 | id, 38 | ]); 39 | 40 | // 判断是否更新成功 41 | if (updateRes.affectedRows > 0) { 42 | ctx.body = { success: true, message: "课程信息更新成功" }; 43 | } else { 44 | ctx.body = { success: false, message: "课程信息未更新" }; 45 | } 46 | } catch (error) { 47 | console.error(error); 48 | ctx.body = { success: false, message: "更新课程失败,请稍后再试" }; 49 | } 50 | }); 51 | 52 | module.exports = router; 53 | -------------------------------------------------------------------------------- /server/src/routes/admin/major/create.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | let db = require("@/db"); 3 | let auth = require("@/modules/auth"); 4 | let { id } = require("lodash-toolkit"); 5 | let router = new Router(); 6 | 7 | router.post("/major", auth(["a"]), async ctx => { 8 | try { 9 | // 获取请求体中的数据 10 | const { college_id, name } = ctx.request.body; 11 | 12 | // 校验请求参数 13 | if (!college_id || !name) { 14 | ctx.body = { success: false, message: "学院ID、专业名称不能为空" }; 15 | return; 16 | } 17 | 18 | // 校验学院是否存在 19 | const collegeQuery = `SELECT 1 FROM college WHERE id = ?`; 20 | const [collegeRes] = await db.query(collegeQuery, [college_id]); 21 | if (collegeRes.length === 0) { 22 | ctx.body = { success: false, message: "指定的学院不存在" }; 23 | return; 24 | } 25 | 26 | // 校验同一学院下是否已经有相同名称的专业 27 | const checkMajorQuery = `SELECT 1 FROM major WHERE college_id = ? AND name = ?;`; 28 | const [majorRes] = await db.query(checkMajorQuery, [college_id, name]); 29 | if (majorRes.length > 0) { 30 | ctx.body = { success: false, message: "该学院下已有相同名称的专业" }; 31 | return; 32 | } 33 | 34 | // 插入新的专业记录 35 | const insertQuery = `INSERT INTO major (id, college_id, name) VALUES (?, ?, ?)`; 36 | const [insertRes] = await db.query(insertQuery, [id(), college_id, name]); 37 | 38 | // 返回成功结果 39 | ctx.body = { 40 | success: true, 41 | message: "专业创建成功", 42 | data: { major_id: insertRes.insertId, college_id, name }, 43 | }; 44 | } catch (error) { 45 | console.error(error); 46 | ctx.body = { success: false, message: "创建专业失败,请稍后再试" }; 47 | } 48 | }); 49 | 50 | module.exports = router; 51 | -------------------------------------------------------------------------------- /server/src/routes/admin/major/delete.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 数据库模块 3 | const auth = require("@/modules/auth"); // 权限验证模块 4 | const router = new Router(); 5 | 6 | router.delete("/major/:id", auth(["a"]), async ctx => { 7 | const { id } = ctx.params; // 获取路径参数,即专业ID 8 | 9 | try { 10 | // 1. 检查专业下是否有课程 11 | const courseQuery = `SELECT 1 FROM course WHERE major = ?`; 12 | const [courseRes] = await db.query(courseQuery, [id]); 13 | if (courseRes.length > 0) { 14 | ctx.body = { success: false, message: "该专业下有课程,无法删除" }; 15 | return; 16 | } 17 | 18 | // 2. 检查专业下是否有学生 19 | const studentQuery = `SELECT 1 FROM student WHERE major_id = ?`; 20 | const [studentRes] = await db.query(studentQuery, [id]); 21 | if (studentRes.length > 0) { 22 | ctx.body = { success: false, message: "该专业下有学生,无法删除" }; 23 | return; 24 | } 25 | 26 | // 3. 检查专业下是否有教师授课 27 | const teacherCourseQuery = `SELECT 1 FROM teacher_course WHERE course_id IN (SELECT id FROM course WHERE major = ?)`; 28 | const [teacherCourseRes] = await db.query(teacherCourseQuery, [id]); 29 | if (teacherCourseRes.length > 0) { 30 | ctx.body = { success: false, message: "该专业下有教师授课,无法删除" }; 31 | return; 32 | } 33 | 34 | // 4. 删除专业 35 | const deleteQuery = `DELETE FROM major WHERE id = ?`; 36 | const [deleteRes] = await db.query(deleteQuery, [id]); 37 | 38 | // 判断是否删除成功 39 | if (deleteRes.affectedRows > 0) { 40 | ctx.body = { success: true, message: "专业删除成功" }; 41 | } else { 42 | ctx.body = { success: false, message: "专业删除失败,可能该专业不存在" }; 43 | } 44 | } catch (error) { 45 | console.error(error); 46 | ctx.body = { success: false, message: "删除专业失败,请稍后再试" }; 47 | } 48 | }); 49 | 50 | module.exports = router; 51 | -------------------------------------------------------------------------------- /server/src/routes/admin/major/list.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | let db = require("@/db"); 3 | let auth = require("@/modules/auth"); 4 | let router = new Router(); 5 | 6 | router.get(["/major/:page", "/major"], auth(["a"]), async ctx => { 7 | try { 8 | // 获取分页参数 9 | const page = parseInt(ctx.params.page); // 获取页码,若不存在则为 NaN 10 | const pageSize = parseInt(ctx.query.pageSize) || 10; // 默认每页10条 11 | const keyword = ctx.query.keyword || ""; // 获取关键字参数 12 | const collegeName = ctx.query.collegeName || ""; // 获取学院名称参数 13 | 14 | // 计算分页偏移量 15 | const offset = (page - 1) * pageSize; 16 | 17 | // 构建查询条件 18 | let query = ` 19 | SELECT 20 | m.id AS major_id, 21 | m.name AS major_name, 22 | c.name AS college_name, 23 | c.id AS college_id, 24 | COUNT(cl.id) AS class_count 25 | FROM major m 26 | LEFT JOIN college c ON c.id = m.college_id 27 | LEFT JOIN class_list cl ON cl.major_id = m.id 28 | `; 29 | 30 | // 如果有 collegeName,添加过滤条件 31 | let conditions = []; 32 | if (collegeName) { 33 | conditions.push(`c.name LIKE ?`); 34 | } 35 | if (keyword) { 36 | conditions.push(`m.name LIKE ?`); 37 | } 38 | 39 | // 构建 WHERE 子句 40 | if (conditions.length > 0) { 41 | query += ` WHERE ` + conditions.join(" AND "); 42 | } 43 | 44 | // 根据是否需要分页来添加 LIMIT 子句 45 | if (page) { 46 | query += ` GROUP BY m.id, c.id ORDER BY c.name LIMIT ?, ?`; 47 | } else { 48 | query += ` GROUP BY m.id, c.id ORDER BY c.name`; // 不分页时不需要 LIMIT 49 | } 50 | 51 | // 设置查询参数 52 | const params = [ 53 | ...(collegeName ? [`%${collegeName}%`] : []), 54 | ...(keyword ? [`%${keyword}%`] : []), 55 | ...(page ? [offset, pageSize] : []), // 如果需要分页,添加分页参数 56 | ]; 57 | 58 | // 执行查询 59 | const [majors] = await db.query(query, params); 60 | 61 | // 查询专业总数,用于分页 62 | let countQuery = `SELECT COUNT(*) AS total FROM major m LEFT JOIN college c ON c.id = m.college_id`; 63 | if (conditions.length > 0) { 64 | countQuery += ` WHERE ` + conditions.join(" AND "); 65 | } 66 | 67 | // 执行总数查询 68 | const [countRes] = await db.query(countQuery, [ 69 | ...(collegeName ? [`%${collegeName}%`] : []), 70 | ...(keyword ? [`%${keyword}%`] : []), 71 | ]); 72 | const total = countRes[0]?.total || 0; 73 | 74 | // 返回分页数据或所有数据 75 | ctx.body = { 76 | success: true, 77 | data: { 78 | total, 79 | page: page || 1, // 如果没有 page 参数,则默认为 1 80 | pageSize, 81 | list: majors, 82 | }, 83 | }; 84 | } catch (error) { 85 | console.log(error); 86 | ctx.body = { success: false, message: "查询失败,请稍后再试" }; 87 | } 88 | }); 89 | 90 | module.exports = router; 91 | -------------------------------------------------------------------------------- /server/src/routes/admin/major/update.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | let db = require("@/db"); 3 | let auth = require("@/modules/auth"); 4 | let router = new Router(); 5 | 6 | router.put("/major/:id", auth(["a"]), async ctx => { 7 | try { 8 | // 获取请求参数 9 | const majorId = parseInt(ctx.params.id); // 专业ID 10 | const { majorName, collegeId } = ctx.request.body; // 新专业名称、新学院ID 11 | 12 | // 校验请求参数 13 | if (!majorName || !collegeId || isNaN(majorId)) { 14 | ctx.body = { success: false, message: "参数无效,请提供专业名称、学院ID。" }; 15 | return; 16 | } 17 | 18 | // 查询要修改的专业是否存在 19 | const checkMajorQuery = "SELECT * FROM major WHERE id = ?"; 20 | const [existingMajor] = await db.query(checkMajorQuery, [majorId]); 21 | if (existingMajor.length === 0) { 22 | ctx.body = { success: false, message: "该专业不存在。" }; 23 | return; 24 | } 25 | 26 | // 校验同一学院和下是否已有相同名称的专业 27 | const checkDuplicateQuery = ` 28 | SELECT 1 FROM major 29 | WHERE college_id = ? AND name = ? ; 30 | `; 31 | const [duplicateMajor] = await db.query(checkDuplicateQuery, [collegeId, majorName]); 32 | 33 | if (duplicateMajor.length > 0) { 34 | ctx.body = { success: false, message: "该学院下已有相同名称的专业" }; 35 | return; 36 | } 37 | console.log(majorName, collegeId, majorId); 38 | 39 | // 更新专业信息 40 | const updateQuery = ` 41 | UPDATE major 42 | SET name = ?, college_id = ? 43 | WHERE id = ?; 44 | `; 45 | const [result] = await db.query(updateQuery, [majorName, collegeId, majorId]); 46 | 47 | // 检查更新结果 48 | if (result.affectedRows === 0) { 49 | ctx.body = { success: false, message: "更新失败,请稍后再试。" }; 50 | return; 51 | } 52 | 53 | // 返回成功响应 54 | ctx.body = { success: true, message: "专业信息更新成功。" }; 55 | } catch (error) { 56 | console.error(error); 57 | ctx.body = { success: false, message: "系统错误,请稍后再试。" }; 58 | } 59 | }); 60 | 61 | module.exports = router; 62 | -------------------------------------------------------------------------------- /server/src/routes/admin/notice/create.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | let db = require("@/db"); // 数据库操作模块 3 | let auth = require("@/modules/auth"); // 身份验证模块 4 | let { id } = require("lodash-toolkit"); 5 | let router = new Router(); 6 | 7 | // 添加通知接口 8 | router.post("/notice", auth(["a"]), async ctx => { 9 | try { 10 | // 获取请求体中的数据 11 | const { title, content, major_id, target, isTop } = ctx.request.body; 12 | 13 | // 校验请求参数 14 | if (!title || !content) { 15 | ctx.body = { success: false, message: "标题、内容和目标不能为空" }; 16 | return; 17 | } 18 | 19 | // 校验目标类型 20 | const validTargets = ["student", "teacher", ""]; // 目标类型 21 | if (!validTargets.includes(target)) { 22 | ctx.body = { success: false, message: "无效的目标类型" }; 23 | return; 24 | } 25 | 26 | // 获取当前时间作为通知创建时间 27 | 28 | // 插入通知数据到数据库 29 | const insertQuery = `INSERT INTO notice (id,title, content, target, isTop,major_id,create_time) 30 | VALUES (?, ?, ?, ?, ?, ?,NOW())`; 31 | const [insertRes] = await db.query(insertQuery, [ 32 | id(), 33 | title, 34 | content, 35 | target, 36 | isTop ? 1 : 0, 37 | JSON.stringify(major_id), 38 | ]); 39 | 40 | const noticeId = insertRes.insertId; 41 | 42 | // 返回成功结果 43 | ctx.body = { 44 | success: true, 45 | message: "通知创建成功", 46 | data: { notice_id: noticeId, title, content, target, isTop, major_id }, 47 | }; 48 | } catch (error) { 49 | console.error("创建通知失败:", error); 50 | ctx.body = { success: false, message: "创建通知失败,请稍后再试" }; 51 | } 52 | }); 53 | 54 | module.exports = router; 55 | -------------------------------------------------------------------------------- /server/src/routes/admin/notice/delete.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | let db = require("@/db"); 3 | let auth = require("@/modules/auth"); 4 | let router = new Router(); 5 | 6 | // 删除通知接口 7 | router.delete("/notice/:id", auth(["a"]), async ctx => { 8 | try { 9 | const noticeId = ctx.params.id; // 获取通知ID 10 | 11 | // 1. 校验通知是否存在 12 | const checkNoticeQuery = `SELECT * FROM notice WHERE id = ?`; 13 | const [noticeRes] = await db.query(checkNoticeQuery, [noticeId]); 14 | 15 | if (noticeRes.length === 0) { 16 | ctx.body = { success: false, message: "通知不存在" }; 17 | return; 18 | } 19 | 20 | // 2. 删除与该通知相关的历史记录 21 | const deleteHistoryQuery = `DELETE FROM notice_history WHERE notice_id = ?`; 22 | await db.query(deleteHistoryQuery, [noticeId]); 23 | 24 | // 3. 删除通知 25 | const deleteNoticeQuery = `DELETE FROM notice WHERE id = ?`; 26 | await db.query(deleteNoticeQuery, [noticeId]); 27 | 28 | // 返回成功消息 29 | ctx.body = { 30 | success: true, 31 | message: "通知删除成功", 32 | }; 33 | } catch (error) { 34 | console.error("删除通知失败:", error); 35 | ctx.body = { success: false, message: "删除通知失败,请稍后再试" }; 36 | } 37 | }); 38 | 39 | module.exports = router; 40 | -------------------------------------------------------------------------------- /server/src/routes/admin/notice/list.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | let db = require("@/db"); 3 | let auth = require("@/modules/auth"); 4 | let router = new Router(); 5 | 6 | // 查询通知接口(带分页,按 isTop 排序) 7 | router.get("/notice", auth(["a", "s", "t"]), async ctx => { 8 | try { 9 | const userId = ctx.id; // 当前用户的ID 10 | const userType = ctx.auth; // 当前用户的角色类型(admin, student, teacher) 11 | 12 | // 获取分页参数,默认第一页每页10条 13 | const page = parseInt(ctx.query.page) || 1; 14 | const limit = parseInt(ctx.query.limit) || 10; 15 | 16 | // 计算 OFFSET 17 | const offset = (page - 1) * limit; 18 | 19 | // 基本查询条件 20 | let whereConditions = []; 21 | let queryParams = []; 22 | 23 | // 管理员权限:根据时间查询通知 24 | if (userType === "admin") { 25 | // 管理员直接按时间查询(可以根据实际需求来扩展时间参数,当前没有携带查询参数) 26 | whereConditions.push("1 = 1"); // 永远成立的条件,查询所有通知 27 | } 28 | 29 | // 学生和教师权限:根据目标类型、专业ID、通知历史来查询通知 30 | if (userType === "student" || userType === "teacher") { 31 | // 根据target筛选通知 32 | whereConditions.push("(target IN ('', ?) OR target = '')"); 33 | queryParams.push(userType); // 只会返回自己相关的通知 34 | 35 | // 如果是学生:根据专业 ID 查询通知 36 | if (userType === "student") { 37 | // 获取学生的专业 ID 38 | const studentQuery = `SELECT major_id FROM student WHERE id = ?`; 39 | const [studentRes] = await db.query(studentQuery, [userId]); 40 | if (studentRes.length > 0) { 41 | const studentMajorId = studentRes[0].major_id; 42 | whereConditions.push("JSON_CONTAINS(major_id, ?) OR JSON_LENGTH(major_id) = 0"); 43 | queryParams.push(`[${studentMajorId}]`); 44 | } 45 | } 46 | } 47 | 48 | // 根据查询条件生成 SQL 49 | const whereClause = whereConditions.length > 0 ? "WHERE " + whereConditions.join(" AND ") : ""; 50 | 51 | // 修改查询语句,按 isTop 排序,isTop 为 1 的通知排在前面 52 | const query = ` 53 | SELECT * 54 | FROM notice ${whereClause} 55 | ORDER BY ${ 56 | userType === "student" || userType === "teacher" ? "isTop DESC," : "" 57 | } create_time DESC 58 | LIMIT ? OFFSET ?`; 59 | queryParams.push(limit, offset); // 添加分页参数:limit 和 offset 60 | 61 | const [notices] = await db.query(query, queryParams); 62 | 63 | // 如果是学生或教师,查询阅读历史并添加 isRead 字段 64 | if (userType === "student" || userType === "teacher") { 65 | const noticeIds = notices.map(notice => notice.id); 66 | 67 | if (noticeIds.length > 0) { 68 | // 查询用户的阅读历史 69 | const historyQuery = ` 70 | SELECT notice_id 71 | FROM notice_history 72 | WHERE user_id = ? AND notice_id IN (?)`; 73 | const [historyRes] = await db.query(historyQuery, [userId, noticeIds]); 74 | 75 | // 将已阅读的通知标记为 isRead: true 76 | const readNoticeIds = historyRes.map(history => history.notice_id); 77 | notices.forEach(notice => { 78 | notice.isRead = readNoticeIds.includes(notice.id); 79 | }); 80 | } else { 81 | // 如果没有通知,默认未读 82 | notices.forEach(notice => { 83 | notice.isRead = false; // 默认未读 84 | }); 85 | } 86 | } 87 | 88 | // 获取总记录数,用于分页信息 89 | const countQuery = `SELECT COUNT(*) AS total FROM notice ${whereClause}`; 90 | const [countRes] = await db.query(countQuery, queryParams); 91 | const totalRecords = countRes[0].total; 92 | 93 | // 返回查询结果 94 | ctx.body = { 95 | success: true, 96 | data: notices, 97 | pagination: { 98 | total: totalRecords, 99 | page: page, 100 | limit: limit, 101 | totalPages: Math.ceil(totalRecords / limit), // 计算总页数 102 | }, 103 | }; 104 | } catch (error) { 105 | console.error("查询通知失败:", error); 106 | ctx.body = { success: false, message: "查询通知失败,请稍后再试" }; 107 | } 108 | }); 109 | 110 | module.exports = router; 111 | -------------------------------------------------------------------------------- /server/src/routes/admin/password.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 数据库查询模块 3 | const router = new Router(); 4 | const auth = require("@/modules/auth"); // 权限模块 5 | 6 | router.put("/admin-password", auth(["a"]), async ctx => { 7 | const adminId = ctx.id; // 从认证信息中获取管理员ID 8 | const { password } = ctx.request.body; 9 | 10 | // 校验请求参数 11 | if (!password) { 12 | ctx.body = { success: false, message: "新密码不能为空" }; 13 | return; 14 | } 15 | 16 | try { 17 | // 更新管理员密码 18 | const updatePasswordQuery = ` 19 | UPDATE admin 20 | SET password = ? 21 | WHERE id = ?; 22 | `; 23 | const result = await db.query(updatePasswordQuery, [password, adminId]); 24 | 25 | if (result.affectedRows === 0) { 26 | ctx.body = { success: false, message: "未找到指定管理员" }; 27 | return; 28 | } 29 | 30 | ctx.body = { success: true, message: "密码修改成功" }; 31 | } catch (error) { 32 | console.error(error); 33 | ctx.body = { success: false, message: "密码修改失败,请稍后再试" }; 34 | } 35 | }); 36 | 37 | module.exports = router; 38 | -------------------------------------------------------------------------------- /server/src/routes/admin/semester/update.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); 3 | const auth = require("@/modules/auth"); 4 | const router = new Router(); 5 | 6 | // 修改当前学期接口 7 | router.put("/semester", auth(["a"]), async ctx => { 8 | try { 9 | const { semester } = ctx.request.body; // 从请求体中获取新的学期值 10 | 11 | // 验证新的学期值是否为空 12 | if (!semester) { 13 | ctx.body = { success: false, message: "学期值不能为空" }; 14 | return; 15 | } 16 | 17 | // 查询 option 表中是否存在 key = 'semester' 的记录 18 | const query = "SELECT * FROM options WHERE `key` = 'semester'"; 19 | const [rows] = await db.query(query); 20 | 21 | if (rows.length > 0) { 22 | // 如果存在,更新该记录的 value 字段 23 | const updateQuery = "UPDATE options SET value = ? WHERE `key` = 'semester'"; 24 | await db.query(updateQuery, [semester]); 25 | 26 | ctx.body = { success: true, message: "学期更新成功" }; 27 | } else { 28 | // 如果不存在,返回错误信息 29 | ctx.body = { success: false, message: "没有找到学期设置" }; 30 | } 31 | } catch (error) { 32 | console.error("修改学期失败:", error); 33 | ctx.body = { success: false, message: "修改学期失败,请稍后再试" }; 34 | } 35 | }); 36 | 37 | module.exports = router; 38 | -------------------------------------------------------------------------------- /server/src/routes/admin/semester/value.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); 3 | const router = new Router(); 4 | 5 | // 查询当前学期接口 6 | router.get("/semester", async ctx => { 7 | try { 8 | // 查询 option 表中 key = 'semester' 的记录 9 | const query = "SELECT * FROM options WHERE `key` = 'semester'"; 10 | const [rows] = await db.query(query); 11 | 12 | if (rows.length > 0) { 13 | // 如果找到记录,返回当前学期 14 | const currentSemester = rows[0].value; 15 | ctx.body = { success: true, currentSemester }; 16 | } else { 17 | // 如果没有找到学期设置,返回错误信息 18 | ctx.body = { success: false, message: "没有找到当前学期" }; 19 | } 20 | } catch (error) { 21 | console.error("查询当前学期失败:", error); 22 | ctx.body = { success: false, message: "查询当前学期失败,请稍后再试" }; 23 | } 24 | }); 25 | 26 | module.exports = router; 27 | -------------------------------------------------------------------------------- /server/src/routes/admin/student/class-tree.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 假设你的数据库查询模块 3 | const router = new Router(); 4 | const auth = require("@/modules/auth"); // 引入 auth 模块 5 | 6 | // 查询整个树状 学院 专业 班级 7 | router.get("/tree/college-major-class", auth(["a"]), async ctx => { 8 | try { 9 | // 查询所有学院 10 | const collegeQuery = ` 11 | SELECT id, name FROM college 12 | `; 13 | const [colleges] = await db.query(collegeQuery); 14 | 15 | if (colleges.length === 0) { 16 | ctx.body = { success: false, message: "没有找到学院数据" }; 17 | return; 18 | } 19 | 20 | // 查询所有专业 21 | const majorQuery = ` 22 | SELECT id, name, college_id FROM major 23 | `; 24 | const [majors] = await db.query(majorQuery); 25 | 26 | if (majors.length === 0) { 27 | ctx.body = { success: false, message: "没有找到专业数据" }; 28 | return; 29 | } 30 | 31 | // 查询所有班级 32 | const classQuery = ` 33 | SELECT * FROM class_list 34 | `; 35 | const [classes] = await db.query(classQuery); 36 | 37 | if (classes.length === 0) { 38 | ctx.body = { success: false, message: "没有找到班级数据" }; 39 | return; 40 | } 41 | 42 | // 构建树形结构 43 | const result = colleges.map(college => { 44 | // 找到该学院下的所有专业 45 | const collegeMajors = majors.filter(major => major.college_id === college.id); 46 | 47 | const majorData = collegeMajors.map(major => { 48 | // 找到该专业下的所有班级 49 | const majorClasses = classes.filter(cls => cls.major_id === major.id); 50 | 51 | return { 52 | id: major.id, 53 | name: major.name, 54 | class_data: majorClasses.map(cls => ({ 55 | class_id: cls.id, 56 | class_index: cls.index, 57 | })), 58 | }; 59 | }); 60 | 61 | return { 62 | college_id: college.id, 63 | college_name: college.name, 64 | major_data: majorData, 65 | }; 66 | }); 67 | 68 | // 返回树形结构 69 | ctx.body = { 70 | success: true, 71 | message: "查询成功", 72 | data: result, 73 | }; 74 | } catch (error) { 75 | console.error(error); 76 | ctx.body = { success: false, message: "查询失败,请稍后再试" }; 77 | } 78 | }); 79 | 80 | module.exports = router; 81 | -------------------------------------------------------------------------------- /server/src/routes/admin/student/create.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 假设你的数据库查询模块 3 | const router = new Router(); 4 | const auth = require("@/modules/auth"); // 引入 auth 模块 5 | let { id } = require("lodash-toolkit"); 6 | 7 | router.post("/student", auth(["a"]), async ctx => { 8 | const { name, age, sex, major_id, class_id } = ctx.request.body; 9 | 10 | // 校验参数 11 | if (!name || !age || !sex || !major_id || !class_id) { 12 | ctx.body = { success: false, message: "参数缺失" }; 13 | return; 14 | } 15 | 16 | try { 17 | // 查询专业是否存在 18 | const majorQuery = `SELECT * FROM major WHERE id = ?`; 19 | const [major] = await db.query(majorQuery, [major_id]); 20 | 21 | if (!major || major.length === 0) { 22 | ctx.body = { success: false, message: "指定的专业不存在" }; 23 | return; 24 | } 25 | 26 | // 查询班级是否存在 27 | const classQuery = `SELECT * FROM class_list WHERE id = ?`; 28 | const [classInfo] = await db.query(classQuery, [class_id]); 29 | 30 | if (!classInfo || classInfo.length === 0) { 31 | ctx.body = { success: false, message: "指定的班级不存在" }; 32 | return; 33 | } 34 | 35 | // 生成学生ID 36 | const studentId = id(); // 生成学生ID 37 | 38 | // 插入学生信息 39 | const studentInsertQuery = ` 40 | INSERT INTO student (id, name, age, sex, major_id, class_id,password, create_time) 41 | VALUES (?, ?, ?, ?, ?, ?,?, NOW()) 42 | `; 43 | await db.query(studentInsertQuery, [studentId, name, age, sex, major_id, class_id, studentId]); 44 | 45 | // 返回成功信息 46 | ctx.body = { 47 | success: true, 48 | message: "学生添加成功", 49 | data: { student_id: studentId }, 50 | }; 51 | } catch (error) { 52 | console.error(error); 53 | ctx.body = { success: false, message: "添加学生失败,请稍后再试" }; 54 | } 55 | }); 56 | 57 | module.exports = router; 58 | -------------------------------------------------------------------------------- /server/src/routes/admin/student/delete.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 假设你的数据库查询模块 3 | const router = new Router(); 4 | const auth = require("@/modules/auth"); // 引入 auth 模块 5 | 6 | router.delete("/student/:studentId", auth(["a"]), async ctx => { 7 | const studentId = ctx.params.studentId; // 获取请求中的学生ID 8 | 9 | try { 10 | // 开启事务 11 | await db.query("START TRANSACTION"); 12 | 13 | // 删除成绩单中的成绩记录 14 | const deleteScoresQuery = `DELETE FROM score WHERE student_id = ?`; 15 | const [deleteScoresResult] = await db.query(deleteScoresQuery, [studentId]); 16 | 17 | // 删除学生记录 18 | const deleteStudentQuery = `DELETE FROM student WHERE id = ?`; 19 | const [deleteStudentResult] = await db.query(deleteStudentQuery, [studentId]); 20 | 21 | // 检查学生是否存在并被删除 22 | if (deleteStudentResult.affectedRows === 0) { 23 | // 如果没有删除任何记录,回滚事务并返回失败信息 24 | await db.query("ROLLBACK"); 25 | ctx.body = { success: false, message: "未找到指定学生" }; 26 | return; 27 | } 28 | 29 | // 提交事务 30 | await db.query("COMMIT"); 31 | 32 | // 返回成功信息 33 | ctx.body = { 34 | success: true, 35 | message: "学生及相关成绩记录删除成功", 36 | data: { student_id: studentId }, 37 | }; 38 | } catch (error) { 39 | // 回滚事务 40 | await db.query("ROLLBACK"); 41 | console.error(error); 42 | ctx.body = { success: false, message: "删除学生失败,请稍后再试" }; 43 | } 44 | }); 45 | 46 | module.exports = router; 47 | -------------------------------------------------------------------------------- /server/src/routes/admin/student/list.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 假设你的数据库查询模块 3 | const router = new Router(); 4 | const auth = require("@/modules/auth"); // 引入 auth 模块 5 | 6 | router.get("/student", auth(["a"]), async ctx => { 7 | const { student_id, class_id, page = 1, limit = 10 } = ctx.query; // 获取查询参数并设置默认分页参数 8 | 9 | // 如果没有传入学生ID或班级ID,则返回错误 10 | if (!student_id && !class_id) { 11 | ctx.body = { success: false, message: "缺少查询参数:学生ID或班级ID" }; 12 | return; 13 | } 14 | 15 | try { 16 | let query = ""; 17 | let countQuery = ""; 18 | let params = []; 19 | const offset = (page - 1) * limit; // 计算偏移量 20 | 21 | if (student_id) { 22 | // 根据学生ID查询 23 | query = `SELECT * FROM student WHERE id = ? LIMIT ? OFFSET ?`; 24 | countQuery = `SELECT COUNT(*) as total FROM student WHERE id = ?`; 25 | params.push(student_id, parseInt(limit), offset); 26 | } else if (class_id) { 27 | // 根据班级ID查询 28 | query = `SELECT * FROM student WHERE class_id = ? LIMIT ? OFFSET ?`; 29 | countQuery = `SELECT COUNT(*) as total FROM student WHERE class_id = ?`; 30 | params.push(class_id, parseInt(limit), offset); 31 | } 32 | 33 | // 查询数据 34 | const [students] = await db.query(query, params); 35 | 36 | // 查询总数 37 | const [countResult] = await db.query(countQuery, [student_id || class_id]); 38 | const total = countResult[0].total; 39 | 40 | // 如果没有找到数据 41 | if (students.length === 0) { 42 | ctx.body = { success: false, message: "没有找到符合条件的学生" }; 43 | return; 44 | } 45 | 46 | // 返回分页查询结果 47 | ctx.body = { 48 | success: true, 49 | message: "查询成功", 50 | data: { 51 | list: students, 52 | total, 53 | page: parseInt(page), 54 | limit: parseInt(limit), 55 | }, 56 | }; 57 | } catch (error) { 58 | console.error(error); 59 | ctx.body = { success: false, message: "查询失败,请稍后再试" }; 60 | } 61 | }); 62 | 63 | module.exports = router; 64 | -------------------------------------------------------------------------------- /server/src/routes/admin/student/update.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 假设你的数据库查询模块 3 | const router = new Router(); 4 | const auth = require("@/modules/auth"); // 引入 auth 模块 5 | 6 | router.put("/student/:studentId", auth(["a"]), async ctx => { 7 | const studentId = ctx.params.studentId; // 获取请求中的学生ID 8 | const { name, age, sex, major_id, class_id } = ctx.request.body; // 获取需要更新的数据 9 | 10 | // 检查参数是否齐全 11 | if (!name || !age || !sex || !major_id || !class_id) { 12 | ctx.body = { success: false, message: "参数缺失" }; 13 | return; 14 | } 15 | 16 | try { 17 | // 更新学生信息 18 | const updateStudentQuery = ` 19 | UPDATE student 20 | SET name = ?, age = ?, sex = ?, major_id = ?, class_id = ? 21 | WHERE id = ?`; 22 | const [updateResult] = await db.query(updateStudentQuery, [ 23 | name, 24 | age, 25 | sex, 26 | major_id, 27 | class_id, 28 | studentId, 29 | ]); 30 | 31 | // 检查是否成功更新 32 | if (updateResult.affectedRows === 0) { 33 | ctx.body = { success: false, message: "未找到指定学生或更新失败" }; 34 | return; 35 | } 36 | 37 | // 返回成功信息 38 | ctx.body = { 39 | success: true, 40 | message: "学生信息更新成功", 41 | data: { student_id: studentId }, 42 | }; 43 | } catch (error) { 44 | console.error(error); 45 | ctx.body = { success: false, message: "更新学生信息失败,请稍后再试" }; 46 | } 47 | }); 48 | 49 | module.exports = router; 50 | -------------------------------------------------------------------------------- /server/src/routes/admin/system-details.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); 3 | const auth = require("@/modules/auth"); 4 | const router = new Router(); 5 | 6 | // 查询系统详情接口 7 | router.get("/system/details", auth(["a", "s", "t"]), async ctx => { 8 | try { 9 | // 获取当前日期和7天前的日期 10 | const currentDate = new Date(); 11 | const startDate = new Date(currentDate); 12 | startDate.setDate(currentDate.getDate() - 7); // 7天前的日期 13 | 14 | // 格式化时间为 YYYY-MM-DD HH:mm:ss 15 | const formatDateTime = date => { 16 | const year = date.getFullYear(); 17 | const month = String(date.getMonth() + 1).padStart(2, "0"); 18 | const day = String(date.getDate()).padStart(2, "0"); 19 | const hours = String(date.getHours()).padStart(2, "0"); 20 | const minutes = String(date.getMinutes()).padStart(2, "0"); 21 | const seconds = String(date.getSeconds()).padStart(2, "0"); 22 | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; 23 | }; 24 | 25 | // 构造过去7天的日期 26 | const dates = []; 27 | for (let i = 0; i < 7; i++) { 28 | const targetDate = new Date(startDate); 29 | targetDate.setDate(startDate.getDate() + i); 30 | const formattedDate = formatDateTime(targetDate).split(" ")[0]; // 只取日期部分 YYYY-MM-DD 31 | dates.push(formattedDate); 32 | } 33 | 34 | // 查询学生、学院、教师、专业、班级的数量 35 | const countQuery = ` 36 | SELECT 37 | (SELECT COUNT(*) FROM student) AS student_count, 38 | (SELECT COUNT(*) FROM college) AS college_count, 39 | (SELECT COUNT(*) FROM teacher) AS teacher_count, 40 | (SELECT COUNT(*) FROM major) AS major_count, 41 | (SELECT COUNT(*) FROM class_list) AS class_count 42 | `; 43 | const [countResults] = await db.query(countQuery); 44 | 45 | // 使用 Promise.all 来并行查询过去7天每天的新增学生数量 46 | const studentCountPromises = dates.map(date => { 47 | const query = ` 48 | SELECT COUNT(*) AS count 49 | FROM student 50 | WHERE create_time <= ? 51 | `; 52 | return db.query(query, [date]).then(([rows]) => { 53 | return { 54 | date, 55 | count: rows[0]?.count || 0, 56 | }; 57 | }); 58 | }); 59 | 60 | // 执行所有查询 61 | const studentCountChange = await Promise.all(studentCountPromises); 62 | 63 | // 返回系统详情和学生数量变化 64 | ctx.body = { 65 | success: true, 66 | data: { 67 | systemStats: countResults[0], 68 | studentCountChange, 69 | }, 70 | }; 71 | } catch (error) { 72 | console.error("查询系统详情失败:", error); 73 | ctx.body = { success: false, message: "查询系统详情失败,请稍后再试" }; 74 | } 75 | }); 76 | 77 | module.exports = router; 78 | -------------------------------------------------------------------------------- /server/src/routes/admin/teacher/create.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 假设你的数据库查询模块 3 | const router = new Router(); 4 | const auth = require("@/modules/auth"); // 引入 auth 模块 5 | let { id } = require("lodash-toolkit"); 6 | 7 | router.post("/teacher", auth(["a"]), async ctx => { 8 | const { name, age, sex, college_id, course_data } = ctx.request.body; 9 | 10 | if (!name || !age || !sex || !college_id || !course_data) { 11 | ctx.body = { success: false, message: "参数缺失" }; 12 | return; 13 | } 14 | 15 | let teacherId = id(); 16 | 17 | try { 18 | // 插入教师信息 19 | const teacherInsertQuery = `INSERT INTO teacher (id,name, age, sex, college_id, password, create_time) VALUES (?,?, ?, ?, ?, ?, NOW())`; 20 | const [teacherResult] = await db.query(teacherInsertQuery, [ 21 | teacherId, 22 | name, 23 | age, 24 | sex, 25 | college_id, 26 | teacherId, 27 | ]); 28 | 29 | // 处理课程数据 30 | for (const course of course_data) { 31 | const { course_id, class_id } = course; 32 | 33 | // 检查该课程是否已经存在该教师的记录 34 | const courseCheckQuery = `SELECT * FROM teacher_course WHERE teacher_id = ? AND course_id = ?`; 35 | const [existingCourseResult] = await db.query(courseCheckQuery, [teacherId, course_id]); 36 | 37 | if (existingCourseResult.length > 0) { 38 | // 如果课程已存在,则合并班级ID 39 | const existingClassIds = JSON.parse(existingCourseResult[0].class_id); 40 | const mergedClassIds = Array.from(new Set([...existingClassIds, ...class_id])); 41 | 42 | // 更新已存在的课程记录,合并班级ID 43 | const updateClassIdsQuery = `UPDATE teacher_course SET class_id = ? WHERE teacher_id = ? AND course_id = ?`; 44 | await db.query(updateClassIdsQuery, [JSON.stringify(mergedClassIds), teacherId, course_id]); 45 | } else { 46 | // 如果课程没有记录,插入新的记录 47 | const insertCourseQuery = `INSERT INTO teacher_course (id,teacher_id, course_id, class_id) VALUES (?,?, ?, ?)`; 48 | await db.query(insertCourseQuery, [id(), teacherId, course_id, JSON.stringify(class_id)]); 49 | } 50 | } 51 | 52 | // 返回成功信息 53 | ctx.body = { 54 | success: true, 55 | message: "教师添加成功", 56 | data: { teacher_id: teacherId }, 57 | }; 58 | } catch (error) { 59 | console.error(error); 60 | ctx.body = { success: false, message: "添加教师失败,请稍后再试" }; 61 | } 62 | }); 63 | 64 | module.exports = router; 65 | -------------------------------------------------------------------------------- /server/src/routes/admin/teacher/delete.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 假设你的数据库查询模块 3 | const router = new Router(); 4 | const auth = require("@/modules/auth"); // 引入 auth 模块 5 | 6 | router.delete("/teacher/:teacherId", auth(["a"]), async ctx => { 7 | const teacherId = ctx.params.teacherId; // 获取请求中的教师ID 8 | 9 | if (!teacherId) { 10 | ctx.body = { success: false, message: "教师ID不能为空" }; 11 | return; 12 | } 13 | 14 | try { 15 | // 1. 删除教师与课程之间的关联 16 | const deleteTeacherCourseQuery = ` 17 | DELETE FROM teacher_course 18 | WHERE teacher_id = ?`; 19 | await db.query(deleteTeacherCourseQuery, [teacherId]); 20 | 21 | // 2. 删除教师记录 22 | const deleteTeacherQuery = ` 23 | DELETE FROM teacher 24 | WHERE id = ?`; 25 | const [teacherDeleteResult] = await db.query(deleteTeacherQuery, [teacherId]); 26 | 27 | if (teacherDeleteResult.affectedRows === 0) { 28 | ctx.body = { success: false, message: "未找到指定教师" }; 29 | return; 30 | } 31 | 32 | // 返回成功信息 33 | ctx.body = { 34 | success: true, 35 | message: "教师删除成功", 36 | }; 37 | } catch (error) { 38 | console.error(error); 39 | ctx.body = { success: false, message: "删除教师失败,请稍后再试" }; 40 | } 41 | }); 42 | 43 | module.exports = router; 44 | -------------------------------------------------------------------------------- /server/src/routes/admin/teacher/list.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 假设你的数据库查询模块 3 | const auth = require("@/modules/auth"); // 引入 auth 模块 4 | const router = new Router(); 5 | 6 | // 查询教师列表 7 | router.get("/teacher", auth(["a"]), async ctx => { 8 | try { 9 | // 获取分页参数 10 | const page = parseInt(ctx.query.page) || 1; 11 | const limit = parseInt(ctx.query.limit) || 10; 12 | const offset = (page - 1) * limit; 13 | 14 | // 获取查询条件 15 | const { name, course_id, class_id, teacher_id, college_id } = ctx.query; 16 | 17 | // 构建查询条件 18 | let conditions = []; 19 | let params = []; 20 | 21 | // 根据教师名字进行模糊查询 22 | if (name) { 23 | conditions.push("teacher.name LIKE ?"); 24 | params.push(`%${name}%`); 25 | } 26 | 27 | // 根据课程ID进行查询 28 | if (course_id) { 29 | conditions.push("teacher_course.course_id = ?"); 30 | params.push(course_id); 31 | } 32 | 33 | // 根据班级ID进行查询 34 | if (class_id) { 35 | conditions.push("JSON_CONTAINS(teacher_course.class_id, JSON_QUOTE(?))"); 36 | params.push(class_id); 37 | } 38 | 39 | // 根据教师ID进行查询 40 | if (teacher_id) { 41 | conditions.push("teacher.id = ?"); 42 | params.push(teacher_id); 43 | } 44 | 45 | // 根据学院ID进行查询 46 | if (college_id) { 47 | conditions.push("teacher.college_id = ?"); 48 | params.push(college_id); 49 | } 50 | 51 | // 构建最终查询语句 52 | let query = ` 53 | SELECT 54 | teacher.id, 55 | teacher.name, 56 | teacher.age, 57 | teacher.sex, 58 | teacher.college_id, 59 | college.name AS college_name -- 获取学院名称 60 | FROM teacher 61 | LEFT JOIN teacher_course ON teacher.id = teacher_course.teacher_id 62 | LEFT JOIN college ON teacher.college_id = college.id -- 联接学院表 63 | ${conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : ""} 64 | LIMIT ? OFFSET ? 65 | `; 66 | 67 | // 添加分页参数 68 | params.push(limit, offset); 69 | 70 | // 查询教师信息 71 | const [teachers] = await db.query(query, params); 72 | 73 | if (teachers.length === 0) { 74 | ctx.body = { success: false, message: "没有找到符合条件的教师", data: [] }; 75 | return; 76 | } 77 | 78 | // 遍历所有教师,查询每个教师所教的课程和对应的班级 79 | const result = []; 80 | 81 | for (const teacher of teachers) { 82 | const teacherId = teacher.id; 83 | 84 | // 查询该教师所教的所有课程及其班级 85 | const courseQuery = ` 86 | SELECT 87 | teacher_course.course_id, 88 | teacher_course.class_id, 89 | course.name AS course_name 90 | FROM teacher_course 91 | LEFT JOIN course ON teacher_course.course_id = course.id 92 | WHERE teacher_course.teacher_id = ? 93 | `; 94 | 95 | const [courses] = await db.query(courseQuery, [teacherId]); 96 | 97 | // 如果没有课程信息,跳过当前教师 98 | if (courses.length === 0) { 99 | result.push({ 100 | id: teacher.id, 101 | name: teacher.name, 102 | age: teacher.age, 103 | sex: teacher.sex, 104 | college_id: teacher.college_id, 105 | college_name: teacher.college_name, // 返回学院名称 106 | course_data: [], 107 | }); 108 | continue; 109 | } 110 | 111 | // 格式化每个教师的课程数据 112 | const courseData = []; 113 | 114 | for (const course of courses) { 115 | // 确保 class_id 格式正确,如果是字符串的话先尝试解析为数组 116 | let classIdArray = []; 117 | 118 | try { 119 | // 尝试解析 class_id 字符串为 JSON 格式的数组 120 | classIdArray = course.class_id; 121 | } catch (e) { 122 | // 如果 JSON 解析失败,将 class_id 视为逗号分隔的字符串,转为数组 123 | classIdArray = course.class_id.split(",").map(id => parseInt(id.trim(), 10)); 124 | } 125 | 126 | // 查询班级的详细信息 127 | const classDetails = []; 128 | if (classIdArray.length > 0) { 129 | for (const classId of classIdArray) { 130 | const classQuery = ` 131 | SELECT 132 | class_list.id AS class_id, 133 | class_list.index AS class_index, 134 | major.name AS major_name 135 | FROM class_list 136 | LEFT JOIN major ON class_list.major_id = major.id 137 | WHERE class_list.id = ? 138 | `; 139 | 140 | const [classInfo] = await db.query(classQuery, [classId]); 141 | if (classInfo.length > 0) { 142 | classDetails.push({ 143 | class_id: classInfo[0].class_id, 144 | class_index: classInfo[0].class_index, 145 | major_name: classInfo[0].major_name, 146 | }); 147 | } 148 | } 149 | } 150 | 151 | // 将课程数据格式化 152 | courseData.push({ 153 | course_id: course.course_id, 154 | course_name: course.course_name, 155 | class_data: classDetails, // 返回详细的班级数据 156 | }); 157 | } 158 | 159 | // 将教师数据和课程数据组合 160 | result.push({ 161 | id: teacher.id, 162 | name: teacher.name, 163 | age: teacher.age, 164 | sex: teacher.sex, 165 | college_id: teacher.college_id, 166 | college_name: teacher.college_name, // 返回学院名称 167 | course_data: courseData, 168 | }); 169 | } 170 | 171 | // 返回分页结果 172 | ctx.body = { 173 | success: true, 174 | message: "查询成功", 175 | data: result, 176 | page: page, 177 | limit: limit, 178 | }; 179 | } catch (error) { 180 | console.error(error); 181 | ctx.body = { success: false, message: "查询失败,请稍后再试" }; 182 | } 183 | }); 184 | 185 | module.exports = router; 186 | -------------------------------------------------------------------------------- /server/src/routes/admin/teacher/update.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 假设你的数据库查询模块 3 | const router = new Router(); 4 | const auth = require("@/modules/auth"); // 引入 auth 模块 5 | let { id } = require("lodash-toolkit"); 6 | 7 | router.put("/teacher/:teacherId", auth(["a"]), async ctx => { 8 | const teacherId = ctx.params.teacherId; // 获取请求中的教师ID 9 | const { name, age, sex, college_id, course_data } = ctx.request.body; 10 | 11 | if (!name || !age || !sex || !college_id || !course_data) { 12 | ctx.body = { success: false, message: "参数缺失" }; 13 | return; 14 | } 15 | 16 | try { 17 | // 更新教师信息 18 | const teacherUpdateQuery = ` 19 | UPDATE teacher 20 | SET name = ?, age = ?, sex = ?, college_id = ? 21 | WHERE id = ?`; 22 | const [teacherUpdateResult] = await db.query(teacherUpdateQuery, [ 23 | name, 24 | age, 25 | sex, 26 | college_id, 27 | teacherId, 28 | ]); 29 | 30 | if (teacherUpdateResult.affectedRows === 0) { 31 | ctx.body = { success: false, message: "未找到指定教师" }; 32 | return; 33 | } 34 | 35 | // 处理课程数据 36 | for (const course of course_data) { 37 | const { course_id, class_id } = course; 38 | 39 | // 检查该课程是否已经存在该教师的记录 40 | const courseCheckQuery = `SELECT * FROM teacher_course WHERE teacher_id = ? AND course_id = ?`; 41 | const [existingCourseResult] = await db.query(courseCheckQuery, [teacherId, course_id]); 42 | 43 | if (existingCourseResult.length > 0) { 44 | // 如果课程已存在,则合并班级ID 45 | const existingClassIds = existingCourseResult[0].class_id; 46 | // const mergedClassIds = Array.from(new Set([...existingClassIds, ...class_id])); 47 | const mergedClassIds = class_id; 48 | 49 | // 更新已存在的课程记录,合并班级ID 50 | const updateClassIdsQuery = `UPDATE teacher_course SET class_id = ? WHERE teacher_id = ? AND course_id = ?`; 51 | await db.query(updateClassIdsQuery, [JSON.stringify(mergedClassIds), teacherId, course_id]); 52 | } else { 53 | // 如果课程没有记录,插入新的记录 54 | const insertCourseQuery = ` 55 | INSERT INTO teacher_course (id, teacher_id, course_id, class_id) 56 | VALUES (?, ?, ?, ?)`; 57 | await db.query(insertCourseQuery, [id(), teacherId, course_id, JSON.stringify(class_id)]); 58 | } 59 | } 60 | 61 | // 返回成功信息 62 | ctx.body = { 63 | success: true, 64 | message: "教师信息更新成功", 65 | data: { teacher_id: teacherId }, 66 | }; 67 | } catch (error) { 68 | console.error(error); 69 | ctx.body = { success: false, message: "更新教师信息失败,请稍后再试" }; 70 | } 71 | }); 72 | 73 | module.exports = router; 74 | -------------------------------------------------------------------------------- /server/src/routes/admin/update.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 你的数据库模块 3 | const auth = require("@/modules/auth"); // 你的权限验证模块 4 | 5 | const router = new Router(); 6 | 7 | router.put("/admin/password", auth(), async ctx => { 8 | try { 9 | const { id } = ctx; 10 | const { password } = ctx.request.body; 11 | 12 | await db.query("UPDATE admin SET password = ? WHERE id = ?", [password, id]); 13 | 14 | ctx.body = { 15 | success: true, 16 | message: "密码修改成功", 17 | }; 18 | } catch (error) { 19 | console.error(error); 20 | ctx.body = { 21 | success: false, 22 | message: "密码修改失败", 23 | error: error.message, 24 | }; 25 | } 26 | }); 27 | 28 | module.exports = router; 29 | -------------------------------------------------------------------------------- /server/src/routes/common/sign.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | const sign = require("@/modules/signToken"); 3 | let db = require("@/db"); 4 | let router = new Router(); 5 | 6 | router.post("/sign/:auth", async ctx => { 7 | try { 8 | // 获取身份类型:admin, student, teacher 9 | const authType = ctx.params.auth; 10 | 11 | let query = ""; 12 | let params = []; 13 | 14 | // 根据身份类型构建查询语句和参数 15 | if (authType === "admin") { 16 | query = `SELECT * FROM admin WHERE id=? AND password=?`; 17 | params = [ctx.request.body.username, ctx.request.body.password]; 18 | } else if (authType === "student") { 19 | query = `SELECT * FROM student WHERE id=? AND password=?`; 20 | params = [ctx.request.body.username, ctx.request.body.password]; // 学号和密码 21 | } else if (authType === "teacher") { 22 | query = `SELECT * FROM teacher WHERE id=? AND password=?`; 23 | params = [ctx.request.body.username, ctx.request.body.password]; // 教师ID和密码 24 | } else { 25 | ctx.body = { success: false, message: "无效的身份类型" }; 26 | return; 27 | } 28 | 29 | // 执行查询 30 | let [res] = await db.query(query, params); 31 | 32 | // 如果有结果,说明验证成功 33 | if (res.length) { 34 | const user = res[0]; 35 | let token = sign({ 36 | id: user.id, 37 | username: user.username || user.name, // 用户名可以是 'username' 或 'name' 38 | type: authType, // 记录身份类型 39 | }); 40 | 41 | ctx.body = { 42 | success: true, 43 | message: "登录成功", 44 | data: { id: user.id, username: user.username || user.name }, 45 | token: token, 46 | }; 47 | } else { 48 | ctx.body = { success: false, message: "用户名或密码错误" }; 49 | } 50 | } catch (error) { 51 | console.log(error); 52 | 53 | ctx.body = { success: false, message: "登录失败,请稍后再试" }; 54 | } 55 | }); 56 | 57 | module.exports = router; 58 | -------------------------------------------------------------------------------- /server/src/routes/common/user-info.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | const sign = require("@/modules/signToken"); 3 | let db = require("@/db"); 4 | let auth = require("@/modules/auth"); 5 | let router = new Router(); 6 | 7 | router.get("/info", auth(), async ctx => { 8 | try { 9 | // 根据 ctx.auth 获取用户身份类型 10 | const authType = ctx.auth; 11 | 12 | let query = ""; 13 | let params = []; 14 | 15 | // 根据身份类型构建查询语句和参数 16 | if (authType === "admin") { 17 | query = `SELECT * FROM admin WHERE id=?`; 18 | params = [ctx.id]; 19 | } else if (authType === "student") { 20 | query = `SELECT * FROM student WHERE id=?`; 21 | params = [ctx.id]; 22 | } else if (authType === "teacher") { 23 | query = `SELECT * FROM teacher WHERE id=?`; 24 | params = [ctx.id]; 25 | } else { 26 | ctx.body = { success: false, message: "无效的身份类型" }; 27 | return; 28 | } 29 | 30 | // 执行查询 31 | let [res] = await db.query(query, params); 32 | 33 | // 如果查询到结果,返回对应的信息 34 | if (res.length) { 35 | const user = res[0]; 36 | const userInfo = { 37 | id: user.id, 38 | username: user.username || user.name, // 对于不同身份的字段名不同,可能是 username 或 name 39 | auth: authType, 40 | }; 41 | 42 | ctx.body = { 43 | success: true, 44 | message: "获取信息成功", 45 | data: userInfo, 46 | }; 47 | } else { 48 | ctx.body = { success: false, message: "未找到相关信息" }; 49 | } 50 | } catch (error) { 51 | ctx.body = { success: false, message: "获取信息失败,请稍后再试" }; 52 | } 53 | }); 54 | 55 | module.exports = router; 56 | -------------------------------------------------------------------------------- /server/src/routes/index.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | let router = new Router(); 3 | 4 | router.get("/", async ctx => { 5 | ctx.body = ` 6 | 7 |

8 | Koa server 9 |

10 |
代写毕设、项目、课设、论文。 11 | QQ:1974109227 12 | 微信:webzhizuo 13 | 14 | 详情见README.md
15 | `; 16 | }); 17 | module.exports = router; 18 | -------------------------------------------------------------------------------- /server/src/routes/student/password.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 数据库查询模块 3 | const router = new Router(); 4 | const auth = require("@/modules/auth"); // 权限模块 5 | 6 | router.put("/student-password", auth(["s"]), async ctx => { 7 | const teacherId = ctx.id; // 从认证信息中获取教师ID 8 | const { password } = ctx.request.body; 9 | 10 | // 校验请求参数 11 | if (!password) { 12 | ctx.body = { success: false, message: "新密码不能为空" }; 13 | return; 14 | } 15 | 16 | try { 17 | // 更新教师密码 18 | const updatePasswordQuery = ` 19 | UPDATE student 20 | SET password = ?, create_time = NOW() 21 | WHERE id = ?; 22 | `; 23 | const result = await db.query(updatePasswordQuery, [password, teacherId]); 24 | 25 | if (result.affectedRows === 0) { 26 | ctx.body = { success: false, message: "未找到指定学生" }; 27 | return; 28 | } 29 | 30 | ctx.body = { success: true, message: "密码修改成功" }; 31 | } catch (error) { 32 | console.error(error); 33 | ctx.body = { success: false, message: "密码修改失败,请稍后再试" }; 34 | } 35 | }); 36 | 37 | module.exports = router; 38 | -------------------------------------------------------------------------------- /server/src/routes/student/score.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 数据库查询模块 3 | const router = new Router(); 4 | const auth = require("@/modules/auth"); // 权限模块 5 | 6 | router.get("/student-score", auth(["a", "t", "s"]), async ctx => { 7 | try { 8 | const studentId = ctx.id; // 获取当前登录学生的ID 9 | 10 | if (!studentId) { 11 | ctx.body = { success: false, message: "学生ID无效" }; 12 | return; 13 | } 14 | 15 | // 查询当前学期 16 | const semesterQuery = "SELECT value FROM options WHERE `key` = 'semester' LIMIT 1;"; 17 | const [semesterResult] = await db.query(semesterQuery); 18 | 19 | if (semesterResult.length === 0) { 20 | ctx.body = { success: false, message: "无法获取当前学期" }; 21 | return; 22 | } 23 | 24 | const currentSemester = semesterResult[0].value; // 获取当前学期 25 | 26 | // 查询当前学生本学期的课程及其成绩,并关联授课教师信息 27 | const query = ` 28 | SELECT 29 | c.id AS course_id, 30 | c.name AS course_name, 31 | c.credit AS course_credit, 32 | c.total_score AS course_total_score, 33 | IFNULL(sc.score, NULL) AS student_score, -- 如果没有成绩,返回null 34 | s.id AS student_id, 35 | s.name AS student_name, 36 | t.id AS teacher_id, 37 | t.name AS teacher_name 38 | FROM course c 39 | LEFT JOIN score sc ON sc.course_id = c.id AND sc.student_id = ? -- 左连接score表,确保没有成绩时返回null 40 | LEFT JOIN student s ON s.id = ? -- 确保返回学生信息 41 | LEFT JOIN teacher_course tc ON tc.course_id = c.id 42 | LEFT JOIN teacher t ON tc.teacher_id = t.id 43 | WHERE c.semester = ? -- 过滤出本学期的课程 44 | ORDER BY c.id; 45 | `; 46 | 47 | const [result] = await db.query(query, [studentId, studentId, currentSemester]); 48 | 49 | // 返回数据 50 | ctx.body = { 51 | success: true, 52 | message: "查询成功", 53 | data: result, 54 | }; 55 | } catch (error) { 56 | console.error(error); 57 | ctx.body = { 58 | success: false, 59 | message: "查询失败,请稍后再试", 60 | }; 61 | } 62 | }); 63 | 64 | module.exports = router; 65 | -------------------------------------------------------------------------------- /server/src/routes/teacher/info.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 假设你的数据库查询模块 3 | const router = new Router(); 4 | const auth = require("@/modules/auth"); // 引入 auth 模块 5 | 6 | router.get("/teacher-info", auth(["a", "t"]), async ctx => { 7 | try { 8 | // 获取教师ID 9 | const teacherId = ctx.query.id || ctx.id; 10 | 11 | if (!teacherId) { 12 | ctx.body = { success: false, message: "教师ID不能为空" }; 13 | return; 14 | } 15 | 16 | // 查询教师信息 17 | const teacherQuery = ` 18 | SELECT 19 | teacher.id, 20 | teacher.name, 21 | teacher.age, 22 | teacher.sex, 23 | teacher.college_id, 24 | college.name AS college_name 25 | FROM teacher 26 | LEFT JOIN college ON teacher.college_id = college.id 27 | WHERE teacher.id = ?`; 28 | 29 | const [teacherResult] = await db.query(teacherQuery, [teacherId]); 30 | 31 | if (teacherResult.length === 0) { 32 | ctx.body = { success: false, message: "未找到指定教师" }; 33 | return; 34 | } 35 | 36 | const teacher = teacherResult[0]; 37 | 38 | // 查询该教师所教的所有课程及其班级 39 | const courseQuery = ` 40 | SELECT 41 | teacher_course.course_id, 42 | teacher_course.class_id, 43 | course.name AS course_name, 44 | course.semester AS course_semester 45 | FROM teacher_course 46 | LEFT JOIN course ON teacher_course.course_id = course.id 47 | WHERE teacher_course.teacher_id = ?`; 48 | 49 | const [courses] = await db.query(courseQuery, [teacherId]); 50 | 51 | // 获取班级信息 52 | const classDetails = []; 53 | for (const course of courses) { 54 | const classIdArray = course.class_id; 55 | const classQuery = ` 56 | SELECT 57 | class_list.id AS class_id, 58 | class_list.index AS class_index, 59 | major.name AS major_name 60 | FROM class_list 61 | LEFT JOIN major ON class_list.major_id = major.id 62 | WHERE class_list.id IN (?)`; 63 | 64 | const [classInfo] = await db.query(classQuery, [classIdArray]); 65 | 66 | classDetails.push({ 67 | course_id: course.course_id, 68 | course_name: course.course_name, 69 | course_semester: course.course_semester, 70 | class_data: classInfo, 71 | }); 72 | } 73 | 74 | // 返回查询结果 75 | ctx.body = { 76 | success: true, 77 | message: "查询成功", 78 | data: { 79 | teacher: { 80 | id: teacher.id, 81 | name: teacher.name, 82 | age: teacher.age, 83 | sex: teacher.sex, 84 | college_id: teacher.college_id, 85 | college_name: teacher.college_name, 86 | }, 87 | course_data: classDetails, 88 | }, 89 | }; 90 | } catch (error) { 91 | console.error(error); 92 | ctx.body = { success: false, message: "查询失败,请稍后再试" }; 93 | } 94 | }); 95 | 96 | module.exports = router; 97 | -------------------------------------------------------------------------------- /server/src/routes/teacher/notice_history.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | let db = require("@/db"); 3 | let auth = require("@/modules/auth"); 4 | let router = new Router(); 5 | let { id } = require("lodash-toolkit"); 6 | 7 | // 标记通知为已读接口 8 | router.post("/notice/read", auth(["s", "t"]), async ctx => { 9 | try { 10 | const userId = ctx.id; // 当前用户的ID 11 | const { notice_ids } = ctx.request.body; // 获取前端传递的 notice_id 数组 12 | 13 | // 参数校验:确保 notice_ids 是一个数组并且不能为空 14 | if (!Array.isArray(notice_ids) || notice_ids.length === 0) { 15 | ctx.body = { 16 | success: false, 17 | message: "请传递有效的通知 ID 数组。", 18 | }; 19 | return; 20 | } 21 | 22 | // 验证每个 notice_id 是否存在并有效 23 | const validNoticeIdsQuery = `SELECT id FROM notice WHERE id IN (?)`; 24 | const [validNotices] = await db.query(validNoticeIdsQuery, [notice_ids]); 25 | 26 | if (validNotices.length !== notice_ids.length) { 27 | ctx.body = { 28 | success: false, 29 | message: "部分通知 ID 无效或不存在。", 30 | }; 31 | return; 32 | } 33 | 34 | // 插入通知历史记录 35 | const insertHistoryQuery = ` 36 | INSERT INTO notice_history (id,user_id, notice_id) 37 | VALUES (?,?, ?) 38 | `; 39 | 40 | const insertPromises = notice_ids.map(noticeId => { 41 | return db.query(insertHistoryQuery, [id(), userId, noticeId]); 42 | }); 43 | 44 | // 等待所有插入完成 45 | await Promise.all(insertPromises); 46 | 47 | // 返回成功响应 48 | ctx.body = { 49 | success: true, 50 | message: "通知已成功标记为已读。", 51 | }; 52 | } catch (error) { 53 | console.error("标记通知为已读失败:", error); 54 | ctx.body = { 55 | success: false, 56 | message: "标记通知为已读失败,请稍后再试。", 57 | }; 58 | } 59 | }); 60 | 61 | module.exports = router; 62 | -------------------------------------------------------------------------------- /server/src/routes/teacher/password.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 数据库查询模块 3 | const router = new Router(); 4 | const auth = require("@/modules/auth"); // 权限模块 5 | 6 | router.put("/teacher-password", auth(["t"]), async ctx => { 7 | const teacherId = ctx.id; // 从认证信息中获取教师ID 8 | const { password } = ctx.request.body; 9 | 10 | // 校验请求参数 11 | if (!password) { 12 | ctx.body = { success: false, message: "新密码不能为空" }; 13 | return; 14 | } 15 | 16 | try { 17 | // 更新教师密码 18 | const updatePasswordQuery = ` 19 | UPDATE teacher 20 | SET password = ?, create_time = NOW() 21 | WHERE id = ?; 22 | `; 23 | const result = await db.query(updatePasswordQuery, [password, teacherId]); 24 | 25 | if (result.affectedRows === 0) { 26 | ctx.body = { success: false, message: "未找到指定教师" }; 27 | return; 28 | } 29 | 30 | ctx.body = { success: true, message: "密码修改成功" }; 31 | } catch (error) { 32 | console.error(error); 33 | ctx.body = { success: false, message: "密码修改失败,请稍后再试" }; 34 | } 35 | }); 36 | 37 | module.exports = router; 38 | -------------------------------------------------------------------------------- /server/src/routes/teacher/score/create.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 数据库查询模块 3 | const router = new Router(); 4 | const auth = require("@/modules/auth"); // 权限模块 5 | let { id } = require("lodash-toolkit"); 6 | 7 | router.put("/score", auth(["a", "t"]), async ctx => { 8 | const { student_id, course_id, score } = ctx.request.body; 9 | 10 | // 校验请求参数 11 | if (!student_id || !course_id || score === undefined) { 12 | ctx.body = { success: false, message: "参数缺失" }; 13 | return; 14 | } 15 | 16 | try { 17 | // 查询成绩表是否已经存在该学生和课程的成绩记录 18 | const checkQuery = ` 19 | SELECT id 20 | FROM score 21 | WHERE student_id = ? AND course_id = ?; 22 | `; 23 | const [existingScore] = await db.query(checkQuery, [student_id, course_id]); 24 | 25 | if (existingScore.length > 0) { 26 | // 如果成绩记录已存在,执行更新 27 | const updateQuery = ` 28 | UPDATE score 29 | SET score = ?, create_time = NOW() 30 | WHERE student_id = ? AND course_id = ?; 31 | `; 32 | await db.query(updateQuery, [score, student_id, course_id]); 33 | ctx.body = { success: true, message: "成绩更新成功" }; 34 | } else { 35 | // 如果成绩记录不存在,执行插入 36 | const insertQuery = ` 37 | INSERT INTO score (id,student_id, course_id, score, create_time) 38 | VALUES (?,?, ?, ?, NOW()); 39 | `; 40 | await db.query(insertQuery, [id(), student_id, course_id, score]); 41 | ctx.body = { success: true, message: "成绩添加成功" }; 42 | } 43 | } catch (error) { 44 | console.error(error); 45 | ctx.body = { success: false, message: "操作失败,请稍后再试" }; 46 | } 47 | }); 48 | 49 | module.exports = router; 50 | -------------------------------------------------------------------------------- /server/src/routes/teacher/score/tree-student.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 数据库查询模块 3 | const router = new Router(); 4 | const auth = require("@/modules/auth"); // 权限模块 5 | 6 | router.get(["/teacher-tree", "/teacher-tree/:teacherId"], auth(["a", "t"]), async ctx => { 7 | const teacherId = ctx.id || ctx.params.teacherId; // 教师ID 8 | 9 | try { 10 | // 查询 option 表获取当前学期 11 | const semesterQuery = "SELECT value FROM options WHERE `key` = 'semester' LIMIT 1;"; 12 | const [semesterResult] = await db.query(semesterQuery); 13 | 14 | if (!semesterResult || semesterResult.length === 0) { 15 | ctx.body = { 16 | success: false, 17 | message: "无法获取学期信息,请检查配置", 18 | }; 19 | return; 20 | } 21 | 22 | const semester = semesterResult[0].value; 23 | 24 | // 查询教师所教的专业、课程(按学期过滤)、班级、学生和成绩数据 25 | const query = ` 26 | SELECT 27 | m.id AS major_id, 28 | m.name AS major_name, 29 | c.id AS course_id, 30 | c.name AS course_name, 31 | c.total_score AS course_total_score, 32 | cl.id AS class_id, 33 | cl.index AS class_index, 34 | s.id AS student_id, 35 | s.name AS student_name, 36 | s.age AS student_age, 37 | s.sex AS student_sex, 38 | sc.score AS student_score 39 | FROM teacher_course tc 40 | JOIN course c ON tc.course_id = c.id AND c.semester = ? 41 | JOIN major m ON c.major = m.id 42 | JOIN class_list cl ON JSON_CONTAINS(tc.class_id, CAST(cl.id AS JSON), '$') 43 | LEFT JOIN student s ON cl.id = s.class_id 44 | LEFT JOIN score sc ON s.id = sc.student_id AND c.id = sc.course_id 45 | WHERE tc.teacher_id = ? 46 | ORDER BY m.id, c.id, cl.id, s.id; 47 | `; 48 | 49 | const [result] = await db.query(query, [semester, teacherId]); 50 | 51 | // 构建树形结构 52 | const tree = {}; 53 | result.forEach(row => { 54 | const { 55 | major_id, 56 | major_name, 57 | course_id, 58 | course_name, 59 | course_total_score, 60 | class_id, 61 | class_index, 62 | student_id, 63 | student_name, 64 | student_age, 65 | student_sex, 66 | student_score, 67 | } = row; 68 | 69 | // 专业层级 70 | if (!tree[major_id]) { 71 | tree[major_id] = { 72 | major_id, 73 | major_name, 74 | course_data: {}, 75 | }; 76 | } 77 | 78 | // 课程层级 79 | if (!tree[major_id].course_data[course_id]) { 80 | tree[major_id].course_data[course_id] = { 81 | course_id, 82 | course_name, 83 | course_total_score, 84 | class_data: {}, 85 | }; 86 | } 87 | 88 | // 班级层级 89 | if (!tree[major_id].course_data[course_id].class_data[class_id]) { 90 | tree[major_id].course_data[course_id].class_data[class_id] = { 91 | class_id, 92 | class_index, 93 | student_data: [], 94 | }; 95 | } 96 | 97 | // 学生数据 98 | if (student_id) { 99 | tree[major_id].course_data[course_id].class_data[class_id].student_data.push({ 100 | student_id, 101 | student_name, 102 | student_age, 103 | student_sex, 104 | student_score, 105 | }); 106 | } 107 | }); 108 | 109 | // 转换树形结构为数组 110 | const treeArray = Object.values(tree).map(major => ({ 111 | ...major, 112 | course_data: Object.values(major.course_data).map(course => ({ 113 | ...course, 114 | class_data: Object.values(course.class_data), 115 | })), 116 | })); 117 | 118 | ctx.body = { 119 | success: true, 120 | message: "查询成功", 121 | data: treeArray, 122 | }; 123 | } catch (error) { 124 | console.error(error); 125 | ctx.body = { 126 | success: false, 127 | message: "查询失败,请稍后再试", 128 | }; 129 | } 130 | }); 131 | 132 | module.exports = router; 133 | -------------------------------------------------------------------------------- /server/src/routes/teacher/xlsx/course-class.js: -------------------------------------------------------------------------------- 1 | const Router = require("koa-router"); 2 | const db = require("@/db"); // 假设 db 是已配置好的数据库模块 3 | const xlsx = require("node-xlsx"); // 引入 node-xlsx 插件 4 | const fs = require("fs"); 5 | const path = require("path"); 6 | const router = new Router(); 7 | const auth = require("@/modules/auth"); // 权限模块 8 | 9 | // 要检查并创建的文件夹路径 10 | const dirPath = path.join("public", "xlsx"); 11 | let exist = fs.existsSync(dirPath); 12 | if (!exist) { 13 | fs.mkdirSync(dirPath); 14 | } 15 | //删除之前的表格 16 | fs.readdirSync(dirPath).forEach(item => { 17 | if (item.endsWith(".xlsx")) { 18 | try { 19 | fs.unlinkSync(path.join(dirPath, item)); 20 | } catch (error) {} 21 | } 22 | }); 23 | 24 | // 根据课程ID和班级ID生成成绩表格接口 25 | router.get("/generate-grade-sheet", auth(["a", "t"]), async ctx => { 26 | const { course_id, class_id } = ctx.query; // 获取课程ID和班级ID参数 27 | 28 | if (!course_id || !class_id) { 29 | ctx.body = { success: false, message: "缺少课程ID或班级ID参数" }; 30 | return; 31 | } 32 | 33 | try { 34 | // 查询班级学生成绩 35 | const query = ` 36 | SELECT s.id AS student_id, s.name AS student_name, c.name AS course_name, g.score 37 | FROM student s 38 | JOIN score g ON s.id = g.student_id 39 | JOIN course c ON c.id = g.course_id 40 | WHERE g.course_id = ? AND s.class_id = ? 41 | `; 42 | 43 | const [result] = await db.query(query, [course_id, class_id]); 44 | 45 | if (result.length === 0) { 46 | ctx.body = { success: false, message: "没有找到相关成绩数据" }; 47 | return; 48 | } 49 | 50 | let [classData] = await db.query(`select * from class_list where id=?`, [class_id]); 51 | let [courseData] = await db.query(`select * from course where id=?`, [course_id]); 52 | let [majorData] = await db.query(`select * from major where id=?`, [courseData[0].major]); 53 | 54 | // 生成表格数据 55 | const data = [ 56 | ["学号", "姓名", `课程(${courseData[0].name})`, "成绩"], // 表头 57 | ...result.map(item => [item.student_id, item.student_name, item.course_name, item.score]), 58 | ]; 59 | 60 | // 使用 node-xlsx 生成 Excel 文件 61 | const buffer = xlsx.build([{ name: "成绩单", data }]); 62 | let fileName = `${majorData[0].name}(${classData[0].index})班-${ 63 | courseData[0].name 64 | }成绩-${+new Date()}.xlsx`; 65 | // 保存 Excel 文件到磁盘 66 | const filePath = path.join(dirPath, fileName); 67 | try { 68 | fs.writeFileSync(filePath, buffer); 69 | } catch (error) {} 70 | 71 | // 返回文件下载路径或文件流给客户端 72 | ctx.body = { 73 | success: true, 74 | message: "成绩表生成成功", 75 | filePath: `/xlsx/${fileName}`, // 文件下载路径(假设你有静态文件服务提供) 76 | }; 77 | 78 | setTimeout(() => { 79 | try { 80 | fs.mkdirSync(filePath); 81 | } catch (error) {} 82 | }, 1800000); 83 | } catch (error) { 84 | console.error("生成成绩表失败:", error); 85 | ctx.body = { success: false, message: "生成成绩表失败,请稍后再试" }; 86 | } 87 | }); 88 | 89 | module.exports = router; 90 | -------------------------------------------------------------------------------- /server/src/routes/upload.js: -------------------------------------------------------------------------------- 1 | let Router = require("koa-router"); 2 | let multer = require("@koa/multer"); 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | let { uuid } = require("lodash-toolkit"); 6 | 7 | let router = new Router(); 8 | 9 | // 要检查并创建的文件夹路径 10 | const dirPath = path.join("public", "image"); 11 | let exist = fs.existsSync(dirPath); 12 | if (!exist) { 13 | fs.mkdirSync(dirPath); 14 | } 15 | 16 | let uploadOption = multer({ 17 | storage: multer.memoryStorage(), 18 | limits: { 19 | files: 1, 20 | fileSize: 1024 * 1024 * 50, //50MB 21 | }, 22 | }); 23 | router.post("/static", uploadOption.single("image"), async ctx => { 24 | try { 25 | let buffer = ctx.file.buffer; 26 | let fileName = `${uuid().replace(/-/g, "")}.${ctx.file.originalname.split(".").slice(-1)[0]}`; 27 | fs.writeFileSync(`public/image/${fileName}`, buffer); 28 | ctx.body = { success: true, message: "上传成功", data: fileName }; 29 | } catch (error) { 30 | ctx.body = { success: false, message: "上传错误" }; 31 | } 32 | }); 33 | module.exports = router; 34 | --------------------------------------------------------------------------------