├── 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 |
2 |
3 | role
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/client/src/components/admin/Access.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | access
4 |
5 |
6 |
7 |
12 |
13 |
16 |
--------------------------------------------------------------------------------
/client/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
2 |
3 | 首页
4 | {{level1}}
5 | {{level2}}
6 |
7 |
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 | 
11 | * 主页
12 | 
13 | * 学生列表
14 | 
15 | * 增加学生
16 | 
17 | * 模糊搜索
18 | 
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 |
2 |
3 |
4 |
5 |
6 |
7 | 所有课程
8 |
9 |
10 |
11 |
12 |
13 | 我的课程
14 |
15 |
16 |
17 |
18 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
44 |
45 |
76 |
--------------------------------------------------------------------------------
/client/src/components/student/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 用户管理
9 |
10 |
11 | 学生列表
12 | 教师列表
13 |
14 |
15 |
16 |
17 |
18 | 权限管理
19 |
20 |
21 | 角色列表
22 | 权限列表
23 |
24 |
25 |
26 |
27 |
28 | 课程管理
29 |
30 |
31 | 课程列表
32 |
33 |
34 |
35 |
36 |
37 | 数据统计
38 |
39 |
40 | 选课情况
41 |
42 |
43 |
44 |
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 |
2 |
3 |
4 |
5 |
6 |
选课后台管理系统
7 |
8 |
9 |
10 |
15 |
16 |
17 |
18 |
19 | 周杰伦
20 |
21 |
22 |
23 | 用户信息
24 | 注销
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
61 |
62 |
104 |
--------------------------------------------------------------------------------
/client/src/components/student/HeadNav.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
选修课程报名系统
7 |
8 |
9 |
10 |
15 |
16 |
17 |
18 |
19 | 周杰伦
20 |
21 |
22 |
23 | 用户信息
24 | 注销
25 |
26 |
27 |
28 |
29 |
30 |
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 |
2 |
3 |
4 |
后台管理系统
5 |
6 |
40 |
41 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | 添加教师
44 |
45 |
46 |
47 |
48 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | 编辑
66 | 删除
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
85 |
86 |
87 |
88 |
89 |
90 |
231 |
232 |
--------------------------------------------------------------------------------
/client/src/components/user/Teacher.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | 添加教师
44 |
45 |
46 |
47 |
48 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | 编辑
66 | 删除
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
85 |
86 |
87 |
88 |
89 |
90 |
231 |
232 |
--------------------------------------------------------------------------------
/client/src/components/course/AllCourse.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | 添加课程
63 |
64 |
65 |
66 |
67 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | 编辑
87 | 删除
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
106 |
107 |
108 |
109 |
110 |
111 |
277 |
278 |
--------------------------------------------------------------------------------
/client/src/components/user/Student.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
50 |
51 |
52 |
58 | 点击上传
59 | 只能上传 .xlsx 表格文件
60 |
61 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | 上传名单
73 |
74 |
75 | 添加学生
76 |
77 |
78 |
79 |
80 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | 编辑
98 | 删除
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
117 |
118 |
119 |
120 |
121 |
122 |
269 |
270 |
--------------------------------------------------------------------------------
/client/src/components/student/MyCourse.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
50 |
51 |
52 |
58 | 点击上传
59 | 只能上传 .xlsx 表格文件
60 |
61 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | 上传名单
73 |
74 |
75 | 添加学生
76 |
77 |
78 |
79 |
80 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | 编辑
98 | 删除
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
117 |
118 |
119 |
120 |
121 |
122 |
269 |
270 |
285 |
--------------------------------------------------------------------------------