├── client ├── README.md ├── babel.config.js ├── src │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── admin │ │ │ ├── Role.vue │ │ │ └── Access.vue │ │ ├── cuscom │ │ │ └── MyBread.vue │ │ ├── student │ │ │ ├── LeftMenu.vue │ │ │ ├── Index.vue │ │ │ ├── HeadNav.vue │ │ │ ├── CourseList.vue │ │ │ └── MyCourse.vue │ │ ├── home │ │ │ ├── Index.vue │ │ │ ├── LeftMenu.vue │ │ │ └── HeadNav.vue │ │ ├── login │ │ │ └── login.vue │ │ ├── user │ │ │ ├── Teacher.vue │ │ │ └── Student.vue │ │ └── course │ │ │ └── AllCourse.vue │ ├── App.vue │ ├── store.js │ ├── main.js │ ├── plugins │ │ └── http.js │ └── router.js ├── public │ ├── index.html │ └── css │ │ └── reset.css ├── package.json └── vue.config.js ├── config ├── keys.js └── passport.js ├── uploads ├── upload_1033f782d717444ce1320776d158c67a.xlsx ├── upload_142a2c441f784264db5ed628d3b63339.xlsx ├── upload_3d15a8adea8d76f21b64807473720110.xlsx ├── upload_808cdf711ce4f019981c6ca6261715a7.xlsx ├── upload_81e59e311fe48337ad963d5acfc7b5b3.xlsx └── upload_8626390fbf807d60a1cacf981c5f6bf2.xlsx ├── models ├── Admin.js ├── User.js ├── Teacher.js ├── Course.js └── Student.js ├── README.md ├── package.json ├── app.js ├── routes └── api │ ├── admins.js │ ├── courses.js │ ├── teachers.js │ └── students.js └── conctrller ├── adminCourseCtrl.js └── adminStudentCtrl.js /client/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /config/keys.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mongoURI: 'mongodb://localhost/ecrs', 3 | secretOrKey: 'secret' 4 | } -------------------------------------------------------------------------------- /client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dekuuuu/coursemanager-node/HEAD/client/src/assets/logo.png -------------------------------------------------------------------------------- /uploads/upload_1033f782d717444ce1320776d158c67a.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dekuuuu/coursemanager-node/HEAD/uploads/upload_1033f782d717444ce1320776d158c67a.xlsx -------------------------------------------------------------------------------- /uploads/upload_142a2c441f784264db5ed628d3b63339.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dekuuuu/coursemanager-node/HEAD/uploads/upload_142a2c441f784264db5ed628d3b63339.xlsx -------------------------------------------------------------------------------- /uploads/upload_3d15a8adea8d76f21b64807473720110.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dekuuuu/coursemanager-node/HEAD/uploads/upload_3d15a8adea8d76f21b64807473720110.xlsx -------------------------------------------------------------------------------- /uploads/upload_808cdf711ce4f019981c6ca6261715a7.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dekuuuu/coursemanager-node/HEAD/uploads/upload_808cdf711ce4f019981c6ca6261715a7.xlsx -------------------------------------------------------------------------------- /uploads/upload_81e59e311fe48337ad963d5acfc7b5b3.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dekuuuu/coursemanager-node/HEAD/uploads/upload_81e59e311fe48337ad963d5acfc7b5b3.xlsx -------------------------------------------------------------------------------- /uploads/upload_8626390fbf807d60a1cacf981c5f6bf2.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dekuuuu/coursemanager-node/HEAD/uploads/upload_8626390fbf807d60a1cacf981c5f6bf2.xlsx -------------------------------------------------------------------------------- /client/src/components/admin/Role.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /client/src/components/admin/Access.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /client/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /client/src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | export default new Vuex.Store({ 7 | state: { 8 | 9 | }, 10 | mutations: { 11 | 12 | }, 13 | actions: { 14 | 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /models/Admin.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const adminSchema = new Schema({ 5 | pid: { 6 | type: String, 7 | tequired: true 8 | }, 9 | name: { 10 | type: String, 11 | tequired: true 12 | }, 13 | password:{ 14 | type: String, 15 | required:true 16 | }, 17 | identity:{ 18 | type: String, 19 | default: 'admin' 20 | } 21 | }); 22 | 23 | module.exports = Admin = mongoose.model('admins', adminSchema); 24 | 25 | 26 | -------------------------------------------------------------------------------- /client/src/components/cuscom/MyBread.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### 运行 2 | 1. npm install 3 | 2. cd client 4 | 3. npm install 5 | 4. cd ../ 6 | 5. npm run dev 7 | 8 | #### 项目界面 9 | * 登录界面 10 | ![login.jpg](https://i.loli.net/2019/08/16/jETyStIDa4GfRNU.jpg) 11 | * 主页 12 | ![home.jpg](https://i.loli.net/2019/08/16/pKGuRiEYPdtoD2m.jpg) 13 | * 学生列表 14 | ![studentlist.jpg](https://i.loli.net/2019/08/16/lIKG5hpCeYxEogj.jpg) 15 | * 增加学生 16 | ![add.jpg](https://i.loli.net/2019/08/16/DoFGyqKtU42HWhg.jpg) 17 | * 模糊搜索 18 | ![search.jpg](https://i.loli.net/2019/08/16/LYCVg5MsT2yGIr3.jpg) 19 | 20 | 21 | -------------------------------------------------------------------------------- /models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | // 创建 Schema 5 | const UserSchema = new Schema({ 6 | name:{ 7 | type: String, 8 | required:true //设定是否必填 9 | }, 10 | email:{ 11 | type: String, 12 | required:true 13 | }, 14 | password:{ 15 | type: String, 16 | required:true 17 | }, 18 | avatar:{ 19 | type: String 20 | }, 21 | identity:{ 22 | type: String, 23 | required:true 24 | }, 25 | date:{ 26 | type: Date, 27 | default:Date.now 28 | } 29 | }) 30 | 31 | module.exports = User = mongoose.model('users', UserSchema); -------------------------------------------------------------------------------- /client/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import myServerHttp from '@/plugins/http.js' 6 | import MyBread from '@/components/cuscom/MyBread.vue' 7 | 8 | import ElementUI from 'element-ui' 9 | import 'element-ui/lib/theme-chalk/index.css' 10 | 11 | // 使用 vue 插件 12 | Vue.use(ElementUI) 13 | // 使用 axios 14 | Vue.use(myServerHttp) 15 | 16 | Vue.config.productionTip = false 17 | 18 | // 全局定义面包屑组件 19 | Vue.component(MyBread.name, MyBread) 20 | 21 | new Vue({ 22 | router, 23 | store, 24 | render: h => h(App) 25 | }).$mount('#app') 26 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | client 10 | 11 | 12 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /models/Teacher.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | // Create Schema 5 | const UserSchema = new Schema({ 6 | name:{ 7 | type: String, 8 | required:true //设定是否必填 9 | }, 10 | pid:{ 11 | type: String, 12 | required:true 13 | }, 14 | firstpwd:{ 15 | type: String, 16 | required:true 17 | }, 18 | avatar:{ 19 | type: String 20 | }, 21 | identity:{ 22 | type: String, 23 | default:'teacher' 24 | }, 25 | date:{ 26 | type: Date, 27 | default:Date.now 28 | }, 29 | changePassword:{ 30 | type: Boolean, 31 | default:false 32 | }, 33 | course:{ 34 | type: Array, 35 | default: [] 36 | } 37 | }) 38 | 39 | module.exports = Teacher = mongoose.model('teachers', UserSchema); -------------------------------------------------------------------------------- /models/Course.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const courseSchema = new mongoose.Schema({ 4 | pid: { 5 | type: String, 6 | tequired: true 7 | }, 8 | name:{ 9 | type: String, 10 | required:true 11 | }, 12 | tname:{ 13 | type: String, 14 | required:true 15 | }, 16 | time:{ 17 | type: String, 18 | required:true 19 | }, 20 | num:{ 21 | type: Number, 22 | required:true 23 | }, 24 | lnum:{ 25 | type: Number, 26 | default: 0 27 | 28 | }, 29 | grade:{ 30 | type: String, 31 | required: true 32 | }, 33 | student:{ 34 | type: Array, 35 | default: [] 36 | }, 37 | }) 38 | 39 | courseSchema.statics.addOneCourse = function(data, callback){ 40 | const c = new Course(data); 41 | c.save(callback); 42 | } 43 | 44 | const Course = mongoose.model('courses', courseSchema); 45 | module.exports = Course; -------------------------------------------------------------------------------- /client/src/components/student/LeftMenu.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 25 | 26 | 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project_electivecourseregistersationsystem", 3 | "version": "1.0.0", 4 | "description": "选课系统", 5 | "main": "app.js", 6 | "scripts": { 7 | "client-install": "npm install --prefix client", 8 | "client": "npm start --prefix client", 9 | "start": "node app.js", 10 | "server": "nodemon app.js", 11 | "dev": "concurrently \"npm run server\" \"npm run client\"" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "bcrypt": "^3.0.6", 17 | "body-parser": "^1.19.0", 18 | "concurrently": "^4.1.1", 19 | "date-format": "^2.1.0", 20 | "ejs": "^2.6.2", 21 | "express": "^4.17.1", 22 | "express-session": "^1.16.2", 23 | "formidable": "^1.2.1", 24 | "gravatar": "^1.8.0", 25 | "jsonwebtoken": "^8.5.1", 26 | "mongoose": "^5.6.7", 27 | "node-xlsx": "^0.15.0", 28 | "passport": "^0.4.0", 29 | "passport-jwt": "^4.0.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /client/public/css/reset.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | font: inherit; 19 | vertical-align: baseline; 20 | } 21 | /* HTML5 display-role reset for older browsers */ 22 | article, aside, details, figcaption, figure, 23 | footer, header, hgroup, menu, nav, section { 24 | display: block; 25 | } 26 | body { 27 | line-height: 1; 28 | } 29 | ol, ul { 30 | list-style: none; 31 | } 32 | blockquote, q { 33 | quotes: none; 34 | } 35 | blockquote:before, blockquote:after, 36 | q:before, q:after { 37 | content: ''; 38 | content: none; 39 | } 40 | table { 41 | border-collapse: collapse; 42 | border-spacing: 0; 43 | } -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "start": "npm run serve" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.19.0", 12 | "core-js": "^2.6.5", 13 | "vue": "^2.6.10", 14 | "vue-router": "^3.0.3", 15 | "vuex": "^3.0.1" 16 | }, 17 | "devDependencies": { 18 | "@vue/cli-plugin-babel": "^3.10.0", 19 | "@vue/cli-plugin-eslint": "^3.10.0", 20 | "@vue/cli-service": "^3.10.0", 21 | "babel-eslint": "^10.0.1", 22 | "eslint": "^5.16.0", 23 | "eslint-plugin-vue": "^5.0.0", 24 | "vue-template-compiler": "^2.6.10" 25 | }, 26 | "eslintConfig": { 27 | "root": true, 28 | "env": { 29 | "node": true 30 | }, 31 | "extends": [ 32 | "plugin:vue/essential", 33 | "eslint:recommended" 34 | ], 35 | "rules": { 36 | "no-console": "off" 37 | }, 38 | "parserOptions": { 39 | "parser": "babel-eslint" 40 | } 41 | }, 42 | "postcss": { 43 | "plugins": { 44 | "autoprefixer": {} 45 | } 46 | }, 47 | "browserslist": [ 48 | "> 1%", 49 | "last 2 versions" 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /client/src/components/home/Index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 44 | 45 | 76 | -------------------------------------------------------------------------------- /client/src/components/student/Index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 44 | 45 | 76 | -------------------------------------------------------------------------------- /config/passport.js: -------------------------------------------------------------------------------- 1 | const JwtStrategy = require('passport-jwt').Strategy, 2 | ExtractJwt = require('passport-jwt').ExtractJwt; 3 | const mongoose = require('mongoose'); 4 | const Student = mongoose.model('students'); 5 | const keys = require('./keys'); 6 | 7 | const opts = {} 8 | opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); 9 | opts.secretOrKey = keys.secretOrKey; 10 | 11 | module.exports = passport => { 12 | passport.use(new JwtStrategy(opts, (jwt_payload, done) => { 13 | if(jwt_payload.identity == 'student') { 14 | // console.log(jwt_payload) 15 | Student.findById(jwt_payload.id) 16 | .then(user => { 17 | console.log(user) 18 | if(user){ 19 | return done(null, user); 20 | } 21 | 22 | return done(null, false); 23 | }) 24 | .catch(err => console.log(err)); 25 | }else if(jwt_payload.identity == 'teacher') { 26 | // console.log(jwt_payload) 27 | Teacher.findById(jwt_payload.id) 28 | .then(user => { 29 | if(user){ 30 | return done(null, user); 31 | } 32 | 33 | return done(null, false); 34 | }) 35 | .catch(err => console.log(err)); 36 | }else if(jwt_payload.identity == 'admin') { 37 | // console.log(jwt_payload) 38 | Admin.findById(jwt_payload.id) 39 | .then(user => { 40 | if(user){ 41 | return done(null, user); 42 | } 43 | 44 | return done(null, false); 45 | }) 46 | .catch(err => console.log(err)); 47 | } 48 | 49 | })); 50 | } -------------------------------------------------------------------------------- /client/src/plugins/http.js: -------------------------------------------------------------------------------- 1 | // 插件模块 2 | import axios from 'axios' 3 | import { Loading, Message } from 'element-ui' 4 | import router from 'vue-router' 5 | const myHttpServer = {} 6 | 7 | myHttpServer.install = function (Vue) { 8 | 9 | axios.defaults.baseURL = 'http://localhost:3000/api' 10 | // 添加实例方法 11 | Vue.prototype.$http = axios 12 | } 13 | 14 | // 15 | 16 | let loading 17 | function startLoading () { 18 | loading = Loading.service({ 19 | lock: true, 20 | text: '加载中', 21 | background: 'rgba(0,0,0,0,7)' 22 | }) 23 | } 24 | 25 | function endLoading () { 26 | loading.close() 27 | } 28 | 29 | // 请求拦截 30 | axios.interceptors.request.use(config => { 31 | // 加载动画 32 | startLoading() 33 | // console.log(config) 34 | if (localStorage.token) { 35 | // 设置统一的请求头 header 36 | config.headers.Authorization = localStorage.token 37 | } 38 | return config 39 | }, error => { 40 | // 41 | Message.error('登录已过期,请重新登录!') 42 | // 跳转到登录页面 43 | this.$router.push('/login') 44 | return Promise.reject(error) 45 | }) 46 | 47 | // 响应拦截 48 | axios.interceptors.response.use(response => { 49 | // 结束加载动画 50 | endLoading() 51 | return response 52 | }, error => { 53 | endLoading() 54 | Message.error(error.response.data) 55 | // 获取错误状态码 56 | const{ status } = error.response; 57 | if(status == 401) { 58 | Message.error('登录已过期,请重新登录!') 59 | // 清除 token 60 | localStorage.removeItem('token') 61 | // 跳转到登录页面 62 | this.$router.push('/login') 63 | }; 64 | return Promise.reject(error) 65 | 66 | }) 67 | export default myHttpServer -------------------------------------------------------------------------------- /models/Student.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const studentSchema = new Schema({ 5 | pid: { 6 | type: String, 7 | tequired: true 8 | }, 9 | name:{ 10 | type: String, 11 | required:true 12 | }, 13 | grade:{ 14 | type: String, 15 | required:true 16 | }, 17 | firstpwd:{ 18 | type: String, 19 | required:true 20 | }, 21 | identity:{ 22 | type: String, 23 | default: 'student' 24 | }, 25 | changePassword:{ 26 | type: Boolean, 27 | default:false 28 | }, 29 | course:{ 30 | type: Array 31 | } 32 | }); 33 | 34 | studentSchema.statics.updateStudent = function(studentList){ 35 | var str = 'ABCDEFGHIJKLMNPQRSTUVWSYZabcdefghijklmnpqrstuvwsyz123456789@#$%*'; 36 | var gradeArr = ['初一','初二','初三','高一','高二','高三'] 37 | mongoose.connection.collection('students').drop(() => { 38 | for(let i = 0; i < 6; i ++){ 39 | for(let j = 1; j < studentList[i].data.length; j ++){ 40 | let firstpwd = ''; 41 | for(let m = 0; m < 6; m ++){ 42 | firstpwd += str.charAt(parseInt(str.length * Math.random())); 43 | }; 44 | var s = new Student({ 45 | 'pid' : studentList[i].data[j][0], 46 | 'name' : studentList[i].data[j][1], 47 | 'grade' : gradeArr[i], 48 | 'firstpwd' : firstpwd 49 | }); 50 | s.save(); 51 | }; 52 | }; 53 | }); 54 | }; 55 | 56 | // 新增一个学生 57 | studentSchema.statics.addOneStudent = function(data){ 58 | const s = new Student(data); 59 | s.save(); 60 | }; 61 | module.exports = Student = mongoose.model('students', studentSchema); 62 | 63 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const mongoose = require('mongoose') 3 | 4 | const students = require('./routes/api/students') 5 | const courses = require('./routes/api/courses') 6 | const teachers = require('./routes/api/teachers') 7 | const admins = require('./routes/api/admins') 8 | 9 | const bodyParser = require('body-parser') 10 | const passport = require('passport') 11 | 12 | // 创建 app 13 | const app = new express() 14 | 15 | //设置跨域 16 | app.all('*', function(req, res, next) { 17 |    res.header("Access-Control-Allow-Origin", "*"); 18 |   res.header("Access-Control-Allow-Headers","Origin, X-Requested-With, content-Type, Accept, Authorization"); 19 |    res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS"); 20 |    res.header("X-Powered-By",' 3.2.1'); 21 |    res.header("Content-Type", "application/json;charset=utf-8"); 22 |    next(); 23 | }); 24 | 25 | // db config 26 | const db = require('./config/keys').mongoURI; 27 | 28 | // 使用 body-parser 中间件 29 | app.use(bodyParser.urlencoded({extended: false})); 30 | app.use(bodyParser.json()); 31 | 32 | // passport 初始化 33 | app.use(passport.initialize()); 34 | 35 | require('./config/passport')(passport); 36 | // // connect to mongodb 37 | mongoose.connect(db, { useNewUrlParser: true }) 38 | .then(() => console.log('MongoDB Connected')) 39 | .catch(err => console.log(err)); 40 | 41 | // app.use(express.static('public')); 42 | app.get('/', (req, res) => { 43 | res.send('nihao') 44 | }) 45 | // 使用 routes 46 | app.use('/api/users', students) 47 | app.use('/api/users', teachers) 48 | app.use('/api/users', admins) 49 | app.use('/api/users', courses) 50 | app.listen(3000); -------------------------------------------------------------------------------- /client/src/components/home/LeftMenu.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 51 | 52 | 63 | -------------------------------------------------------------------------------- /client/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Login from '@/components/login/login' 4 | import Index from '@/components/home/Index' 5 | import Student from '@/components/user/Student' 6 | import Teacher from '@/components/user/Teacher' 7 | import Access from '@/components/admin/Access' 8 | import Role from '@/components/admin/Role' 9 | import StudentIndex from '@/components/student/Index' 10 | import CourseList from '@/components/student/CourseList' 11 | import MyCourse from '@/components/student/MyCourse' 12 | import AllCourse from '@/components/course/AllCourse' 13 | 14 | Vue.use(Router) 15 | 16 | const router = new Router ({ 17 | mode: 'history', 18 | base: process.env.BASE_URL, 19 | routes: [ 20 | { 21 | name: 'login', 22 | path: '/login', 23 | component: Login 24 | }, 25 | { 26 | name: 'index', 27 | path: '/', 28 | component: Index, 29 | children: [ 30 | { 31 | name: 'students', 32 | path: 'students', 33 | component: Student 34 | }, 35 | { 36 | name: 'teachers', 37 | path: 'teachers', 38 | component: Teacher 39 | }, 40 | { 41 | name: 'accesss', 42 | path: 'accesss', 43 | component: Access 44 | }, 45 | { 46 | name: 'roles', 47 | path: 'roles', 48 | component: Role 49 | }, 50 | { 51 | name: 'allcourse', 52 | path: 'allcourse', 53 | component: AllCourse 54 | } 55 | ] 56 | }, 57 | { 58 | name: 'student', 59 | path: '/student', 60 | component: StudentIndex, 61 | children: [ 62 | { 63 | name: '/courselist', 64 | path: '/courselist', 65 | component: CourseList 66 | }, 67 | { 68 | name: '/mycourse', 69 | path: '/mycourse', 70 | component: MyCourse 71 | }, 72 | ] 73 | } 74 | ] 75 | }) 76 | 77 | // 路由守卫 78 | router.beforeEach((to, from, next) => { 79 | const isLogin = localStorage.token 80 | if (to.path == '/login') { 81 | next() 82 | } else { 83 | isLogin ? next() : next('/login') 84 | } 85 | }) 86 | export default router -------------------------------------------------------------------------------- /client/vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const debug = process.env.NODE_ENV !== 'production' 3 | 4 | module.exports = { 5 | publicPath: '/', // 根域上下文目录 6 | outputDir: 'dist', // 构建输出目录 7 | assetsDir: 'assets', // 静态资源目录 (js, css, img, fonts) 8 | lintOnSave: false, // 是否开启eslint保存检测,有效值:ture | false | 'error' 9 | runtimeCompiler: true, // 运行时版本是否需要编译 10 | transpileDependencies: [], // 默认babel-loader忽略mode_modules,这里可增加例外的依赖包名 11 | productionSourceMap: true, // 是否在构建生产包时生成 sourceMap 文件,false将提高构建速度 12 | configureWebpack: config => { // webpack配置,值位对象时会合并配置,为方法时会改写配置 13 | if (debug) { // 开发环境配置 14 | config.devtool = 'cheap-module-eval-source-map' 15 | } else { // 生产环境配置 16 | } 17 | // Object.assign(config, { // 开发生产共同配置 18 | // resolve: { 19 | // alias: { 20 | // '@': path.resolve(__dirname, './src'), 21 | // '@c': path.resolve(__dirname, './src/components'), 22 | // 'vue$': 'vue/dist/vue.esm.js' 23 | // } 24 | // } 25 | // }) 26 | }, 27 | chainWebpack: config => { // webpack链接API,用于生成和修改webapck配置,https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md 28 | if (debug) { 29 | // 本地开发配置 30 | } else { 31 | // 生产开发配置 32 | } 33 | }, 34 | parallel: require('os').cpus().length > 1, // 构建时开启多进程处理babel编译 35 | pluginOptions: { // 第三方插件配置 36 | }, 37 | pwa: { // 单页插件相关配置 https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa 38 | }, 39 | devServer: { 40 | open: true, 41 | host: 'localhost', 42 | port: 8080, 43 | https: false, 44 | hotOnly: false, 45 | proxy: { // 配置跨域 46 | '/api': { 47 | target: 'http://localhost:3000/api/', 48 | ws: true, 49 | changOrigin: true, 50 | pathRewrite: { 51 | '^/api': '' 52 | } 53 | } 54 | }, 55 | before: app => { } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /client/src/components/home/HeadNav.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 61 | 62 | 104 | -------------------------------------------------------------------------------- /client/src/components/student/HeadNav.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 61 | 62 | 104 | -------------------------------------------------------------------------------- /routes/api/admins.js: -------------------------------------------------------------------------------- 1 | // @login & register 2 | const express = require('express'); 3 | const router = express.Router(); 4 | const bcrypt = require('bcrypt'); 5 | const jwt = require('jsonwebtoken'); 6 | const gravatar = require('gravatar'); 7 | const keys = require('../../config/keys'); 8 | const passport = require('passport'); 9 | const Admin = require('../../models/Admin'); 10 | 11 | // @route POST api/users/register 模拟注册 12 | // @desc 返回的请求的json数据 13 | // @access public 14 | router.post('/adminregister', (req, res) => { 15 | // 查询数据库中是否拥有邮箱 16 | Student.findOne({ pid: req.body.pid }).then(admin => { 17 | if (admin) { 18 | return res.status(400).json('id已被注册!'); 19 | } else { 20 | const avatar = gravatar.url(req.body.gravatar, { 21 | s: '200', 22 | r: 'pg', 23 | d: 'mm' 24 | }); 25 | 26 | const newAdmin = new Admin({ 27 | pid: req.body.pid, 28 | name: req.body.name, 29 | avatar, 30 | password: req.body.password, 31 | identity: req.body.identity 32 | }); 33 | 34 | bcrypt.genSalt(10, function(err, salt) { 35 | bcrypt.hash(newAdmin.password, salt, (err, hash) => { 36 | if (err) throw err; 37 | 38 | newAdmin.password = hash; 39 | 40 | newAdmin 41 | .save() 42 | .then(admin => res.json(admin)) 43 | .catch(err => console.log(err)); 44 | }); 45 | }); 46 | } 47 | }); 48 | }); 49 | 50 | // @route POST api/users/studentlogin 学生身份登录 51 | // @desc 返回token jwt passport 52 | // @access public 53 | router.post('/adminlogin', (req, res) => { 54 | const pid = req.body.pid; 55 | const password = req.body.password; 56 | // 查询数据库 57 | Admin.findOne({ pid }).then(admin => { 58 | if (!admin) { 59 | return res.status(404).json('用户不存在!') 60 | } 61 | 62 | // 密码匹配 63 | bcrypt.compare(password, admin.password).then(isMatch => { 64 | if (isMatch) { 65 | const rule = { 66 | id: admin.id, 67 | pid: admin.pid, 68 | name: admin.name, 69 | avatar: admin.avatar, 70 | identity: admin.identity 71 | } 72 | jwt.sign(rule, keys.secretOrKey, { expiresIn: 3600 }, (err, token) => { 73 | if (err) throw err; 74 | res.json({ 75 | success: true, 76 | token: 'Bearer ' + token 77 | }); 78 | }); 79 | // res.json({msg:"success"}); 80 | } else { 81 | return res.status(400).json('密码错误!'); 82 | } 83 | }); 84 | }); 85 | }); 86 | 87 | // @route GET api/users/current 验证 token 88 | // @desc return current user 89 | // @access Private 90 | router.get( 91 | '/acurrent', 92 | passport.authenticate('jwt', { session: false }), 93 | (req, res) => { 94 | console.log(req) 95 | res.json({ 96 | pid: req.user.pid, 97 | name: req.user.name, 98 | identity: req.user.identity 99 | }); 100 | } 101 | ); 102 | 103 | module.exports = router; 104 | -------------------------------------------------------------------------------- /conctrller/adminCourseCtrl.js: -------------------------------------------------------------------------------- 1 | // const formidable = require('formidable'); 2 | const url = require('url'); 3 | const Course = require('../models/Course'); 4 | 5 | // 添加课程 6 | exports.doCourseAdd = (req, res) => { 7 | var form = new formidable.IncomingForm(); 8 | form.parse(req, (err, fields, files) => { 9 | const newCourse = { 10 | 'cid': fields.cid, 11 | 'cName': fields.cName, 12 | 'tName': fields.tName, 13 | 'sNum': fields.sNum, 14 | 'profile': fields.profile, 15 | 'grade': fields.grade, 16 | 'weekDate': fields.weekDate 17 | } 18 | Course.addOneCourse(newCourse); 19 | console.log(newCourse) 20 | res.json({result: 1}); 21 | }); 22 | 23 | }; 24 | 25 | // 全部学生数据 26 | exports.getAllCourse = (req, res) => { 27 | // 拿到参数 28 | var rows = Number(url.parse(req.url,true).query.rows); 29 | var page = Number(url.parse(req.url, true).query.page); 30 | var sidx = url.parse(req.url,true).query.sidx; 31 | var sord = url.parse(req.url,true).query.sord; 32 | var keyword = url.parse(req.url,true).query.keyword; 33 | 34 | var sordNumber = sord == 'asc' ? 1 : -1; 35 | // 根据是否有 keyword 的请求参数,来决定接口的用途。 36 | if(keyword == undefined || keyword == ''){ 37 | var findFiler = {}; // 检索全部 38 | }else{// 使用正则表达式的构造函数将字符串转为再正则对象 39 | var regexp = new RegExp(keyword, 'g'); 40 | var findFiler = { 41 | $or : [ 42 | {'cid': regexp}, 43 | {"cName": regexp}, 44 | {'tName': regexp}, 45 | {'sNum': regexp}, 46 | {'profile': regexp}, 47 | {'grade': regexp}, 48 | {'weekDate': regexp} 49 | ] 50 | }; 51 | }; 52 | // 分页算法 53 | Course.countDocuments(findFiler, (err, count) => { 54 | var total = Math.ceil(count / rows); 55 | // 排序、分页 56 | var sortobj = {}; 57 | sortobj[sidx] = sordNumber; 58 | 59 | 60 | // 模糊查询,全字段查询 61 | Course.find(findFiler).sort(sortobj).skip(rows * (page - 1)).limit(rows).exec(function(err,results){ 62 | res.json({'records': count, 'page': page, 'total': total, 'rows':results}); 63 | }); 64 | }); 65 | }; 66 | 67 | // 修改课程数据 68 | exports.changeCourseData = (req, res) => { 69 | var cid = req.params.cid; 70 | var form = new formidable.IncomingForm(); 71 | form.parse(req, (err, fields, files) => { 72 | var key = fields.cellname; 73 | var value = fields.value; 74 | Course.find({'cid': cid}, (err, results) => { 75 | if(results.length == 0){ 76 | res.send({'result': -1}); 77 | return; 78 | }; 79 | var theCourse = results[0]; 80 | theCourse[key] = value; 81 | theCourse.save(function(err){ 82 | if(err){ 83 | res.send({'result': -2}); 84 | return; 85 | }; 86 | res.send({'result': 1}); 87 | }); 88 | }); 89 | }); 90 | }; 91 | 92 | // 删除一门课程 93 | exports.doCourseDel = (req, res) => { 94 | const form = new formidable.IncomingForm(); 95 | form.parse(req, (err, fields, files) => { 96 | Course.remove({'cid': fields.arr}, function(err){ 97 | if(err){ 98 | res.json({'result': -1}); 99 | }else{ 100 | res.json({'result': 1}); 101 | }; 102 | }); 103 | }); 104 | }; -------------------------------------------------------------------------------- /routes/api/courses.js: -------------------------------------------------------------------------------- 1 | // @login & register 2 | const express = require("express"); 3 | const router = express.Router(); 4 | const bcrypt = require("bcrypt"); 5 | const jwt = require("jsonwebtoken"); 6 | const gravatar = require("gravatar"); 7 | const keys = require("../../config/keys"); 8 | const passport = require("passport"); 9 | const Teacher = require("../../models/Teacher"); 10 | const Course = require("../../models/Course"); 11 | 12 | const url = require("url"); 13 | 14 | 15 | // 增加单个课程 16 | router.post( 17 | "/addcourse", 18 | // passport.authenticate("jwt", { session: false }), 19 | (req, res) => { 20 | console.log(req.body) 21 | const courseFields = {}; 22 | if (req.body.pid) courseFields.pid = req.body.pid; 23 | if (req.body.name) courseFields.name = req.body.name; 24 | if (req.body.time) courseFields.time = req.body.time; 25 | if (req.body.tname) courseFields.tname = req.body.tname; 26 | if (req.body.num) courseFields.num = req.body.num; 27 | if (req.body.info) courseFields.info = req.body.info; 28 | if (req.body.grade) courseFields.grade = req.body.grade; 29 | new Course(courseFields).save().then(course => { 30 | res.json(course); 31 | }); 32 | } 33 | ); 34 | 35 | // 获取所有教师 36 | router.get( 37 | "/allcourses", 38 | // passport.authenticate("jwt", { session: false }), 39 | (req, res) => { 40 | // 拿到参数 41 | const page_size = Number(url.parse(req.url, true).query.page_size) 42 | const page_index = Number(url.parse(req.url, true).query.page_index) - 1 43 | const sort = url.parse(req.url, true).query.sort 44 | const keyword = url.parse(req.url, true).query.keyword 45 | const sortItem = url.parse(req.url, true).query.sortItem 46 | // 模糊查询 47 | if(keyword == undefined || keyword == ''){ 48 | var findFiler = {}; // 检索全部 49 | }else{// 使用正则表达式的构造函数将字符串转为再正则对象 50 | const regexp = new RegExp(keyword, 'g'); 51 | var findFiler = { 52 | $or : [ 53 | {'pid': regexp}, 54 | {'name': regexp}, 55 | {'tname': regexp}, 56 | {'info': regexp}, 57 | {'time': regexp} 58 | ] 59 | } 60 | } 61 | let sortNumber 62 | if(sort == 'ascending'){ 63 | sortNumber = -1 64 | }else if(sort == 'descending') { 65 | sortNumber = 1 66 | }else{ 67 | sortNumber = -1 68 | } 69 | 70 | // const sidx = url.parse(req.url, true).query.sidx; 71 | // const sord = url.parse(req.url, true).query.sord; 72 | // const keyword = url.parse(req.url, true).query.keyword; 73 | Course.countDocuments(findFiler, (err, count) => { 74 | const sortobj = {} 75 | sortobj[sortItem] = sortNumber 76 | Course.find(findFiler).sort(sortobj).limit(page_size).skip(page_size * page_index).exec((err, course) => { 77 | if (!course) { 78 | return res.status(404).json("没有任何内容") 79 | } 80 | res.json({'total': count, 'data': course}) 81 | }) 82 | }) 83 | } 84 | ) 85 | 86 | // 更改教师 87 | router.post('/changeteacher', passport.authenticate('jwt', { session: false }), 88 | (req, res) => { 89 | const updatefileds = {} 90 | const _id = req.body._id 91 | updatefileds.pid = req.body.pid 92 | updatefileds.course = req.body.course 93 | updatefileds.name = req.body.name 94 | Teacher.findOneAndUpdate( 95 | {_id: _id}, 96 | {$set: updatefileds}, 97 | {new: true}).then(teacher => { 98 | res.json(teacher) 99 | }) 100 | }) 101 | 102 | // 删除课程 103 | router.delete( 104 | '/deletecou', 105 | // passport.authenticate('jwt', { session: false }), 106 | (req, res) => { 107 | Course.findOneAndRemove({ _id: url.parse(req.url, true).query._id }) 108 | .then( course => { 109 | course.save().then(course => res.json(course)) 110 | }) 111 | .catch(err => res.status(404).json('删除失败!')); 112 | } 113 | ) 114 | module.exports = router; 115 | -------------------------------------------------------------------------------- /client/src/components/login/login.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 118 | 119 | 161 | -------------------------------------------------------------------------------- /conctrller/adminStudentCtrl.js: -------------------------------------------------------------------------------- 1 | const xlsx = require('node-xlsx'); 2 | const formidable = require('formidable'); 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | const Student = require('../models/Student'); 7 | const dateformat = require('date-format'); 8 | 9 | 10 | 11 | // 显示学生清单界面 12 | exports.showStudentList = (req, res) => { 13 | res.render('./teacher/studentList',{ 14 | page: 'list' 15 | }) 16 | }; 17 | // 显示学生名单上传界面 18 | exports.showStudentListUpdate = (req, res) => { 19 | res.render('./teacher/studentListUpdate',{ 20 | page: 'update' 21 | }); 22 | }; 23 | 24 | //显示增加学生界面 25 | exports.showStudentAdd = (req, res) => { 26 | res.render('./teacher/studentadd', { 27 | page: 'add' 28 | }); 29 | }; 30 | // 显示学生密码管理界面 31 | exports.showStudentPassword = (req, res) => { 32 | res.render('./teacher/studentPassword', { 33 | page: 'password' 34 | }) 35 | }; 36 | 37 | 38 | // 执行表格的上传 39 | exports.doAdminStudentUpdate = (req, res) => { 40 | const form = new formidable.IncomingForm(); 41 | form.uploadDir = './uploads'; 42 | form.keepExtensions = true; 43 | form.parse(req, (err, fields, files) => { 44 | if(path.extname(files.studentexcel.path) != '.xlsx'){ 45 | //删除这个不正确的文件 46 | 47 | fs.unlink('./' + files.studentexcel.path, (err) => { 48 | if(err){ 49 | console.log('删除文件错误'); 50 | return; 51 | }; 52 | res.send('文件格式有误,请重新上传'); 53 | }); 54 | return; 55 | }; 56 | const studentList = xlsx.parse('./' + files.studentexcel.path); 57 | // console.log(studentList[1]); 58 | if(studentList.length != 6){ 59 | res.send('分表缺失,请检查'); 60 | return; 61 | }; 62 | for(var i = 0; i < 6; i ++){ 63 | if(studentList[i].data[0][0] != '学号' || studentList[i].data[0][1] != '姓名'){ 64 | res.send('第' + (i+1) + '页分表表头错误,请检查'); 65 | return; 66 | }; 67 | }; 68 | Student.updateStudent(studentList); 69 | 70 | res.send('学生名单更新成功!'); 71 | }); 72 | }; 73 | 74 | // 全部学生数据 75 | exports.getAllStudent = (req, res) => { 76 | // 拿到参数 77 | var rows = Number(url.parse(req.url,true).query.rows); 78 | var page = Number(url.parse(req.url, true).query.page); 79 | var sidx = url.parse(req.url,true).query.sidx; 80 | var sord = url.parse(req.url,true).query.sord; 81 | var keyword = url.parse(req.url,true).query.keyword; 82 | 83 | var sordNumber = sord == 'asc' ? 1 : -1; 84 | // 根据是否有 keyword 的请求参数,来决定接口的用途。 85 | if(keyword == undefined || keyword == ''){ 86 | var findFiler = {}; // 检索全部 87 | }else{// 使用正则表达式的构造函数将字符串转为再正则对象 88 | var regexp = new RegExp(keyword, 'g'); 89 | var findFiler = { 90 | $or : [ 91 | {'sid': regexp}, 92 | {'name': regexp}, 93 | {'grade': regexp} 94 | ] 95 | }; 96 | }; 97 | // 分页算法 98 | Student.countDocuments(findFiler, (err, count) => { 99 | var total = Math.ceil(count / rows); 100 | // 排序、分页 101 | var sortobj = {}; 102 | sortobj[sidx] = sordNumber; 103 | 104 | 105 | // 模糊查询,全字段查询 106 | Student.find(findFiler).sort(sortobj).skip(rows * (page - 1)).limit(rows).exec(function(err,results){ 107 | res.json({'records': count, 'page': page, 'total': total, 'rows':results}); 108 | }); 109 | }); 110 | }; 111 | 112 | // 修改学生数据 113 | exports.changeStudentData = (req, res) => { 114 | var sid = parseInt(req.params.sid); 115 | var form = new formidable.IncomingForm(); 116 | form.parse(req, (err, fields, files) => { 117 | var key = fields.cellname; 118 | var value = fields.value; 119 | Student.find({'sid': sid}, (err, results) => { 120 | console.log(results) 121 | if(results.length == 0){ 122 | res.send({'result': -1}); 123 | return; 124 | }; 125 | var thestudent = results[0]; 126 | thestudent[key] = value; 127 | thestudent.save(function(err){ 128 | if(err){ 129 | res.send({'result': -2}); 130 | return; 131 | }; 132 | res.send({'result': 1}); 133 | }); 134 | }); 135 | }); 136 | }; 137 | 138 | // 增加一名学生 139 | exports.doStudentAdd = (req, res) => { 140 | const regSid = /^[0-9]/; 141 | const regName = /^[A-z]+$|^[\u4E00-\u9FA5]+$/; 142 | const form = new formidable.IncomingForm(); 143 | form.parse(req, (err, fields, files) => { 144 | const sid = fields.sidTxt; 145 | const name = fields.nameTxt; 146 | const grade = fields.gradeSelect; 147 | const password = fields.passwordTxt; 148 | const submit = fields.submit; 149 | Student.find({sid: sid}, (err, result) => { 150 | if(result.length != 0){ 151 | res.json({result: -1}); 152 | return; 153 | }else if(submit == "1" && regSid.test(sid) && regName.test(name)){ 154 | res.json({result: 1}); 155 | Student.addOneStudent({ 156 | 'sid': sid, 157 | 'name': name, 158 | 'grade': grade, 159 | 'password': password 160 | }); 161 | }; 162 | }); 163 | 164 | }); 165 | }; 166 | 167 | // 删除一名学生 168 | exports.doStudentDel = (req, res) => { 169 | const form = new formidable.IncomingForm(); 170 | form.parse(req, (err, fields, files) => { 171 | Student.remove({'sid': fields.arr}, function(err){ 172 | if(err){ 173 | res.json({'result': -1}); 174 | }else{ 175 | res.json({'result': 1}); 176 | }; 177 | }); 178 | }); 179 | }; 180 | 181 | // 下载学生清单表格 182 | exports.downloadStudentXlsx = (req, res) => { 183 | // 整理数据 184 | var tableR = []; 185 | var gradeArr = ['初一', '初二', '三', '高一', '高二', '高三']; 186 | function iterator(i){ // 迭代器 187 | if(i == 6){ 188 | var buffer = xlsx.build(tableR); 189 | var filename = dateformat('yyyy年mm月dd日hhmmss', new Date()); 190 | fs.writeFile('./public/xlsx/' + '学生清单' + filename + '.xlsx', buffer, function(err){ 191 | res.redirect('/xlsx/' + '学生清单' + filename + '.xlsx'); 192 | }) 193 | return; 194 | }; 195 | Student.find({'grade': gradeArr[i]}, function(err, results){ 196 | var sheetR = []; 197 | results.forEach(function(item){ 198 | sheetR.push([ 199 | item.sid, 200 | item.name, 201 | item.grade, 202 | item.password 203 | ]); 204 | }); 205 | tableR.push({'name': gradeArr[i], data: sheetR}); 206 | iterator(++ i); 207 | }); 208 | }; 209 | iterator(0); 210 | 211 | }; 212 | 213 | -------------------------------------------------------------------------------- /routes/api/teachers.js: -------------------------------------------------------------------------------- 1 | // @login & register 2 | const express = require("express"); 3 | const router = express.Router(); 4 | const bcrypt = require("bcrypt"); 5 | const jwt = require("jsonwebtoken"); 6 | const gravatar = require("gravatar"); 7 | const keys = require("../../config/keys"); 8 | const passport = require("passport"); 9 | const Teacher = require("../../models/Teacher"); 10 | 11 | const url = require("url"); 12 | 13 | // @route POST api/users/register 14 | // @desc 返回的请求的json数据 15 | // @access public 16 | router.post('/teacherregister', (req, res) => { 17 | // 查询数据库中是否拥有邮箱 18 | Teacher.findOne({ tid: req.body.pid }).then(user => { 19 | if (user) { 20 | return res.status(400).json('邮箱已被注册!'); 21 | } else { 22 | const avatar = gravatar.url(req.body.gravatar, { 23 | s: '200', 24 | r: 'pg', 25 | d: 'mm' 26 | }); 27 | 28 | const newTeacher = new Teacher({ 29 | name: req.body.name, 30 | pid: req.body.pid, 31 | avatar, 32 | password: req.body.password, 33 | identity: req.body.identity 34 | }); 35 | 36 | bcrypt.genSalt(10, function(err, salt) { 37 | bcrypt.hash(newTeacher.password, salt, (err, hash) => { 38 | if (err) throw err; 39 | 40 | newTeacher.password = hash; 41 | 42 | newTeacher 43 | .save() 44 | .then(user => res.json(user)) 45 | .catch(err => console.log(err)); 46 | }); 47 | }); 48 | } 49 | }); 50 | }); 51 | 52 | // @route POST api/users/login 53 | // @desc 返回token jwt passport 54 | // @access public 55 | 56 | router.post('/teacherlogin', (req, res) => { 57 | const pid = req.body.pid; 58 | const password = req.body.password; 59 | // 查询数据库 60 | Teacher.findOne({ pid }).then(teacher => { 61 | if (!teacher) { 62 | return res.status(404).json('用户不存在!'); 63 | } 64 | 65 | // 密码匹配 66 | bcrypt.compare(password, teacher.password).then(isMatch => { 67 | if (isMatch) { 68 | const rule = { 69 | id: teacher.id, 70 | pid: teacher.pid, 71 | name: teacher.name, 72 | avatar: teacher.avatar, 73 | identity: teacher.identity 74 | }; 75 | jwt.sign(rule, keys.secretOrKey, { expiresIn: 3600 }, (err, token) => { 76 | if (err) throw err; 77 | res.json({ 78 | success: true, 79 | token: 'Bearer ' + token 80 | }); 81 | }); 82 | // res.json({msg:"success"}); 83 | } else { 84 | return res.status(400).json('密码错误!'); 85 | } 86 | }); 87 | }); 88 | }); 89 | 90 | // @route GET api/users/current 91 | // @desc return current user 92 | // @access Private 93 | router.get( 94 | '/tcurrent', 95 | passport.authenticate('jwt', { session: false }), 96 | (req, res) => { 97 | console.log(req.user) 98 | res.json({ 99 | id: req.user.id, 100 | name: req.user.name, 101 | email: req.user.email, 102 | identity: req.user.identity 103 | }); 104 | } 105 | ); 106 | 107 | // 增加单个教师 108 | router.post( 109 | "/addteacher", 110 | passport.authenticate("jwt", { session: false }), 111 | (req, res) => { 112 | const teacherFields = {}; 113 | const str = 114 | "ABCDEFGHIJKLMNPQRSTUVWSYZabcdefghijklmnpqrstuvwsyz123456789@#$%*"; 115 | if (req.body.pid) teacherFields.pid = req.body.pid; 116 | if (req.body.name) teacherFields.name = req.body.name; 117 | if (req.body.course) teacherFields.course = req.body.course; 118 | let firstpwd = ""; 119 | for (let m = 0; m < 6; m++) { 120 | firstpwd += str.charAt(parseInt(str.length * Math.random())); 121 | } 122 | teacherFields.firstpwd = firstpwd; 123 | new Teacher(teacherFields).save().then(teacher => { 124 | res.json(teacher); 125 | }); 126 | } 127 | ); 128 | 129 | // 获取所有教师 130 | router.get( 131 | "/allteacher", 132 | passport.authenticate("jwt", { session: false }), 133 | (req, res) => { 134 | // 拿到参数 135 | const page_size = Number(url.parse(req.url, true).query.page_size) 136 | const page_index = Number(url.parse(req.url, true).query.page_index) - 1 137 | const sort = url.parse(req.url, true).query.sort 138 | const keyword = url.parse(req.url, true).query.keyword 139 | const sortItem = url.parse(req.url, true).query.sortItem 140 | // 模糊查询 141 | if(keyword == undefined || keyword == ''){ 142 | var findFiler = {}; // 检索全部 143 | }else{// 使用正则表达式的构造函数将字符串转为再正则对象 144 | const regexp = new RegExp(keyword, 'g'); 145 | var findFiler = { 146 | $or : [ 147 | {'pid': regexp}, 148 | {'name': regexp}, 149 | {'grade': regexp} 150 | ] 151 | } 152 | } 153 | let sortNumber 154 | if(sort == 'ascending'){ 155 | sortNumber = -1 156 | }else if(sort == 'descending') { 157 | sortNumber = 1 158 | }else{ 159 | sortNumber = -1 160 | } 161 | 162 | // const sidx = url.parse(req.url, true).query.sidx; 163 | // const sord = url.parse(req.url, true).query.sord; 164 | // const keyword = url.parse(req.url, true).query.keyword; 165 | Teacher.countDocuments(findFiler, (err, count) => { 166 | const sortobj = {} 167 | sortobj[sortItem] = sortNumber 168 | Teacher.find(findFiler).sort(sortobj).limit(page_size).skip(page_size * page_index).exec((err, teacher) => { 169 | if (!teacher) { 170 | return res.status(404).json("没有任何内容") 171 | } 172 | res.json({'total': count, 'data': teacher}) 173 | }) 174 | }) 175 | } 176 | ) 177 | 178 | // 更改教师 179 | router.post('/changeteacher', passport.authenticate('jwt', { session: false }), 180 | (req, res) => { 181 | const updatefileds = {} 182 | const _id = req.body._id 183 | updatefileds.pid = req.body.pid 184 | updatefileds.course = req.body.course 185 | updatefileds.name = req.body.name 186 | Teacher.findOneAndUpdate( 187 | {_id: _id}, 188 | {$set: updatefileds}, 189 | {new: true}).then(teacher => { 190 | res.json(teacher) 191 | }) 192 | }) 193 | 194 | // 删除学生 195 | router.delete( 196 | '/deletetea', 197 | passport.authenticate('jwt', { session: false }), 198 | (req, res) => { 199 | console.log(url.parse(req.url, true).query._id) 200 | Teacher.findOneAndRemove({ _id: url.parse(req.url, true).query._id }) 201 | .then(teacher => { 202 | teacher.save().then(teacher => res.json(teacher)) 203 | }) 204 | .catch(err => res.status(404).json('删除失败!')); 205 | } 206 | ) 207 | module.exports = router; 208 | -------------------------------------------------------------------------------- /routes/api/students.js: -------------------------------------------------------------------------------- 1 | // @login & register 2 | const express = require("express"); 3 | const router = express.Router(); 4 | const bcrypt = require("bcrypt"); 5 | const jwt = require("jsonwebtoken"); 6 | const gravatar = require("gravatar"); 7 | const keys = require("../../config/keys"); 8 | const passport = require("passport"); 9 | const Student = require("../../models/Student"); 10 | const formidable = require("formidable"); 11 | const path = require("path"); 12 | const fs = require("fs"); 13 | const url = require("url"); 14 | const xlsx = require("node-xlsx"); 15 | 16 | // @route POST api/users/register 模拟注册 17 | // @desc 返回的请求的json数据 18 | // @access public 19 | router.post("/studentregister", (req, res) => { 20 | // 查询数据库中是否拥有邮箱 21 | Student.findOne({ pid: req.body.pid }).then(student => { 22 | if (student) { 23 | return res.status(400).json("学号已被注册!"); 24 | } else { 25 | const avatar = gravatar.url(req.body.gravatar, { 26 | s: "200", 27 | r: "pg", 28 | d: "mm" 29 | }); 30 | 31 | const newStudent = new Student({ 32 | pid: req.body.pid, 33 | name: req.body.name, 34 | grade: req.body.grade, 35 | avatar, 36 | password: req.body.password, 37 | identity: req.body.identity 38 | }); 39 | 40 | bcrypt.genSalt(10, function(err, salt) { 41 | bcrypt.hash(newStudent.password, salt, (err, hash) => { 42 | if (err) throw err; 43 | 44 | newStudent.password = hash; 45 | 46 | newStudent 47 | .save() 48 | .then(user => res.json(user)) 49 | .catch(err => console.log(err)); 50 | }); 51 | }); 52 | } 53 | }); 54 | }); 55 | 56 | // @route POST api/users/studentlogin 学生身份登录 57 | // @desc 返回token jwt passport 58 | // @access public 59 | router.post("/studentlogin", (req, res) => { 60 | console.log(req.body); 61 | const pid = req.body.pid; 62 | const password = req.body.password; 63 | // 查询数据库 64 | Student.findOne({ pid }).then(student => { 65 | if (!student) { 66 | return res.status(401).json("用户不存在!"); 67 | } 68 | // 密码匹配 69 | bcrypt.compare(password, student.password).then(isMatch => { 70 | if (isMatch) { 71 | const rule = { 72 | id: student.id, 73 | pid: student.pid, 74 | name: student.name, 75 | avatar: student.avatar, 76 | identity: student.identity 77 | }; 78 | jwt.sign(rule, keys.secretOrKey, { expiresIn: 3600 }, (err, token) => { 79 | if (err) throw err; 80 | res.json({ 81 | success: true, 82 | token: "Bearer " + token 83 | }); 84 | }); 85 | // res.json({msg:"success"}); 86 | } else { 87 | return res.status(400).json("密码错误!"); 88 | } 89 | }); 90 | }); 91 | }); 92 | 93 | // @route GET api/users/current 验证 token 94 | // @desc return current user 95 | // @access Private 96 | router.get( 97 | "/scurrent", 98 | passport.authenticate("jwt", { session: false }), 99 | (req, res) => { 100 | // console.log(req) 101 | res.json({ 102 | sid: req.user.sid, 103 | name: req.user.name, 104 | grade: req.user.grade, 105 | identity: req.user.identity 106 | }); 107 | } 108 | ); 109 | 110 | router.get( 111 | "/allstudent", 112 | passport.authenticate("jwt", { session: false }), 113 | (req, res) => { 114 | // 拿到参数 115 | const page_size = Number(url.parse(req.url, true).query.page_size) 116 | const page_index = Number(url.parse(req.url, true).query.page_index) - 1 117 | const sort = url.parse(req.url, true).query.sort 118 | const keyword = url.parse(req.url, true).query.keyword 119 | const sortItem = url.parse(req.url, true).query.sortItem 120 | // 模糊查询 121 | if(keyword == undefined || keyword == ''){ 122 | var findFiler = {}; // 检索全部 123 | }else{// 使用正则表达式的构造函数将字符串转为再正则对象 124 | const regexp = new RegExp(keyword, 'g'); 125 | var findFiler = { 126 | $or : [ 127 | {'pid': regexp}, 128 | {'name': regexp}, 129 | {'grade': regexp}, 130 | {'course': regexp} 131 | ] 132 | } 133 | } 134 | let sortNumber 135 | if(sort == 'ascending'){ 136 | sortNumber = -1 137 | }else if(sort == 'descending') { 138 | sortNumber = 1 139 | }else{ 140 | sortNumber = -1 141 | } 142 | 143 | // const sidx = url.parse(req.url, true).query.sidx; 144 | // const sord = url.parse(req.url, true).query.sord; 145 | // const keyword = url.parse(req.url, true).query.keyword; 146 | Student.countDocuments(findFiler, (err, count) => { 147 | const sortobj = {} 148 | sortobj[sortItem] = sortNumber 149 | Student.find(findFiler).sort(sortobj).limit(page_size).skip(page_size * page_index).exec((err, student) => { 150 | if (!student) { 151 | return res.status(404).json("没有任何内容") 152 | } 153 | res.json({'total': count, 'data': student}) 154 | }) 155 | }) 156 | } 157 | ); 158 | 159 | // @route POST api/users/addstudent 160 | // @desc 创建信息接口 161 | // @access Private 162 | router.post( 163 | "/addstudent", 164 | passport.authenticate("jwt", { session: false }), 165 | (req, res) => { 166 | const studentFields = {}; 167 | const str = 168 | "ABCDEFGHIJKLMNPQRSTUVWSYZabcdefghijklmnpqrstuvwsyz123456789@#$%*"; 169 | if (req.body.pid) studentFields.pid = req.body.pid; 170 | if (req.body.name) studentFields.name = req.body.name; 171 | if (req.body.grade) studentFields.grade = req.body.grade; 172 | let firstpwd = ""; 173 | for (let m = 0; m < 6; m++) { 174 | firstpwd += str.charAt(parseInt(str.length * Math.random())); 175 | } 176 | studentFields.firstpwd = firstpwd; 177 | new Student(studentFields).save().then(student => { 178 | res.json(student); 179 | }); 180 | } 181 | ); 182 | 183 | // @route POST api/users/studentupdate 184 | // @desc 创建信息接口 185 | // @access 186 | router.post( 187 | "/studentupdate", 188 | // passport.authenticate("jwt", { session: false }), 189 | (req, res) => { 190 | const form = new formidable.IncomingForm(); 191 | form.uploadDir = "./uploads"; 192 | form.keepExtensions = true; 193 | form.parse(req, (err, fields, files) => { 194 | if (path.extname(files.studentexcel.path) != ".xlsx") { 195 | //删除这个不正确的文件 196 | 197 | fs.unlink("./" + files.studentexcel.path, err => { 198 | if (err) { 199 | console.log("删除文件错误"); 200 | return; 201 | } 202 | res.send("文件格式有误,请重新上传"); 203 | }); 204 | return; 205 | } 206 | const studentList = xlsx.parse("./" + files.studentexcel.path); 207 | if (studentList.length != 6) { 208 | res.send("分表缺失,请检查"); 209 | return; 210 | } 211 | for (var i = 0; i < 6; i++) { 212 | if ( 213 | studentList[i].data[0][0] != "学号" || 214 | studentList[i].data[0][1] != "姓名" 215 | ) { 216 | res.send("第" + (i + 1) + "页分表表头错误,请检查"); 217 | return; 218 | } 219 | } 220 | Student.updateStudent(studentList); 221 | 222 | // res.send("学生名单更新成功!"); 223 | }); 224 | } 225 | ); 226 | 227 | // 更改学生 228 | router.post('/changestudent', passport.authenticate('jwt', { session: false }), 229 | (req, res) => { 230 | const updatefileds = {} 231 | const _id = req.body._id 232 | updatefileds.pid = req.body.pid 233 | updatefileds.grade = req.body.grade 234 | updatefileds.name = req.body.name 235 | Student.findOneAndUpdate( 236 | {_id: _id}, 237 | {$set: updatefileds}, 238 | {new: true}).then(student => { 239 | res.json(student) 240 | }) 241 | }) 242 | 243 | // 删除学生 244 | router.delete( 245 | '/deletestu', 246 | passport.authenticate('jwt', { session: false }), 247 | (req, res) => { 248 | console.log(url.parse(req.url, true).query._id) 249 | Student.findOneAndRemove({ _id: url.parse(req.url, true).query._id }) 250 | .then(student => { 251 | student.save().then(student => res.json(student)) 252 | }) 253 | .catch(err => res.status(404).json('删除失败!')); 254 | } 255 | ); 256 | module.exports = router; 257 | -------------------------------------------------------------------------------- /client/src/components/student/CourseList.vue: -------------------------------------------------------------------------------- 1 | 89 | 90 | 231 | 232 | -------------------------------------------------------------------------------- /client/src/components/user/Teacher.vue: -------------------------------------------------------------------------------- 1 | 89 | 90 | 231 | 232 | -------------------------------------------------------------------------------- /client/src/components/course/AllCourse.vue: -------------------------------------------------------------------------------- 1 | 110 | 111 | 277 | 278 | -------------------------------------------------------------------------------- /client/src/components/user/Student.vue: -------------------------------------------------------------------------------- 1 | 121 | 122 | 269 | 270 | -------------------------------------------------------------------------------- /client/src/components/student/MyCourse.vue: -------------------------------------------------------------------------------- 1 | 121 | 122 | 269 | 270 | 285 | --------------------------------------------------------------------------------