這是我畢業項目,從0到1,先後臺獨立開發完成。功能很少,在此記錄,溫故而知新!項目github地址: https://github.com/FinGet/Exam ,博客地址: https://finget.github.io/
更新記錄:2018-4-9,md5加密
window下安裝mongodb,須要參考的能夠移步個人博客中:win10安裝mongodbjavascript
本次項目使用的是express4 + vue2+ + elementUI1+ + mongodb3.4+css
先看項目文件目錄結構:
vue
我頁面用的vue因此server/views
和server/public
都沒有用
vue init webpack exam
項目中先後臺是寫在一個項目中的:java
npm i -g express-generator // 在項目文件根目錄下 express server
因爲先後臺都是寫在一個項目中的,我就將server
下的package.json
和vue
下的package.json
合併了
node
npm i axios --save
首先axios不支持vue.use()式聲明mysql
// 在main.js中以下聲明使用 import axios from 'axios'; Vue.prototype.$axios=axios; // 那麼在其餘vue組件中就能夠this.$axios調用使用
npm i element-ui --save
webpack
import ElementUI from 'element-ui' // 加載ElementUI import 'element-ui/lib/theme-default/index.css' Vue.use(ElementUI) // 全局使用elementUI
npm i vue-lazyload --save
ios
// main.js import VueLazyLoad from 'vue-lazyload' Vue.use(VueLazyLoad, { // 全局使用圖片懶加載 loading: 'static/loading-svg/loading-bars.svg', // 圖片還沒加載時的svg圖片 try: 1 // default 1 })
使用懶加載:git
<img width="300" height="53" v-lazy="logoSrc" alt=""> logoSrc:require('../common/img/logo.png') // 不能寫成:<img width="300" height="53" v-lazy="../common/img/logo.png" alt="">
npm i mongoose --save
github
就不一一列舉全部的插件了(沒有用vuex)
// commonFun.js //獲取sessionStorage function getSessionStorage(key, format) { var data; if (sessionStorage.getItem(key)) { if (format == 'json') { data = JSON.parse(sessionStorage.getItem(key)); } else { data = sessionStorage.getItem(key); } } else { data = false } return data; } //寫入sessionStorage function setSessionStorage(key, content, format) { var data; if (format == 'json') { data = JSON.stringify(content); } else { data = content; } sessionStorage.setItem(key, data); } export var mySessionStorage = { get: getSessionStorage, set: setSessionStorage }
全局掛載
// main.js import * as commonFun from './common/js/commonFun.js' Vue.prototype.$mySessionStorage = commonFun.mySessionStorage;
在頁面中使用
this.$mySessionStorage.set(key,content,format); this.$mySessionStorage.get(key);
// main.js // 登陸判斷 router.beforeEach((to, from, next) => { var userdata = getUserData(); if (to.path != '/managelogin'&&to.name!='404'&&to.path != '/'&&to.path != "/frontregister"&&to.path!='/manageregister') { // 判斷是否登陸 if(!userdata.userName){ ElementUI.Message.error('抱歉,您尚未登陸!'); if(to.path.indexOf('front')>0){ router.push({path:'/'}); } else { router.push({path:'/managelogin'}); } } else { next(); } } else { next(); } })
綁定麪包屑要根據實際狀況來定,可是
this.$router.currentRoute.matched
是最主要的
<template> <div class="bread"> <el-breadcrumb separator="/"> <el-breadcrumb-item v-for="(item, index) in breadData" :key="item.id" :to="{ name: item.meta.breadName=='管理系統'?'Index':item.name }">{{item.meta.breadName}}</el-breadcrumb-item> </el-breadcrumb> </div> </template> <script type="text/ecmascript-6"> export default { data() { return { breadData:[] } }, watch: { $route () { this.initBreadData(); } }, methods:{ //麪包屑 initBreadData(){ this.breadData=this.$router.currentRoute.matched; // console.log(this.breadData) } }, created(){ this.initBreadData(); } } </script>
路由部分:
根據實際狀況來,不能套用,要看你的路由怎麼寫的this.$router.currentRoute.path
:default-active="activeIndex"
// conponents/sidebar.vue //初始化列表active狀態 ... methods:{ initActiveIndex(){ // var str =this.$router.currentRoute.path; this.activeIndex=this.$router.currentRoute.path; // console.log(str) } }, watch:{ '$route':'initActiveIndex' }, created(){ this.initActiveIndex(); } ...
要想請求到後臺數據,這一步是必須的
配置代理以後,localhost:8088/api/ -> localhost:3000/api/
config/index.js proxyTable: { // proxy all requests starting with /api to jsonplaceholder '/api': { target: 'http://127.0.0.1:3000/api', // 端口號根據後臺設置來,默認是3000 changeOrigin: true, pathRewrite: { '^/api': '' // 若target中沒有/api、這裏又爲空,則404; } } },
<div v-if="dialogForm.type!='judgement'&&dialogForm.type!='Q&A'"> <el-form-item v-for="(item,index) in dialogForm.surveyQuestionOptionList" :key="item.key" :label="'選項'+(index+1) +':'" :prop="'surveyQuestionOptionList.' + index + '.optionContent'" :rules="{ required:true, message:'選項不能爲空', trigger:'blur' }" > // 最重要的是prop 必定要帶上`.optionContent`,也就是你綁定值的key <el-input placeholder="請輸入選項" class="dialog_input" v-model="item.optionContent"></el-input> <i class="el-icon-delete delete-icon" @click="deleteDlalogOption(index)"></i> </el-form-item> <el-button type="primary" size="small" class="marginB10" @click="addDialogOption">添加選項</el-button> </div>
goToExam(id){ // params傳參只能用name引入 this.$router.push({name:'ForntExam',params:{id:id}}); }
<div class="single"> <h4>單選題(只有一個正確答案)</h4> <ul> <li class="marginB10" v-for="(item,index) in singleQuestions" :key="item.id"> <p class="question-title">{{index+1}} 、{{item.name}}()</p> <span class="option" v-if="item.type!='judgement'&&item.type!='Q&A'"item v-for="(item1,index1) in item.selection" :key="item1.id"> <el-radio v-model="item.sanswer" :label="options[index1]" :key="index1">{{options[index1]}}、{{item1}}</el-radio> </span> </li> </ul> </div>
init(){ if(this.id == '' || !this.id ){ this.$router.push({path:'forntexamindex'}); return } else { this.$axios.get('/api/getExamInfo',{ params:{ id: this.id } }).then(response => { let res = response.data; if(res.status == '0') { for(let key in this.paperData) { this.paperData[key] = res.result[key]; } res.result._questions.forEach(item => { if(item.type=='single'){ item.sanswer = ''; // 重要的在這 給他新增一個屬性,用來存答案 this.singleQuestions.push(item); } else if(item.type == 'multi'){ item.sanswer = []; // 多選題 this.multiQuestions.push(item); } else if(item.type == 'Q&A') { item.sanswer = ''; this.QAQuestions.push(item); } else if(item.type == 'judgement'){ item.sanswer = ''; this.judgeQuestions.push(item); } }) } }).catch(err => { this.$message.error(err); }) } }
在server根目錄下新建db.js
// db.js var mongoose = require('mongoose'); var dbUrl = 'mongodb://127.0.0.1:27017/examSystem'; var db = mongoose.connect(dbUrl); db.connection.on('error',function(error) { console.log('數據庫連接失敗:'+ error); }); db.connection.on('connected',function() { console.log('數據庫連接成功!'); }); db.connection.on('disconnected',function() { console.log('Mongoose connection disconnected'); }); module.exports = db;
// server/app.js // 連接數據庫 require('./db');
須要express-session
和 cookie-parser
插件
// app.js // 加載解析session的中間件 // session 的 store 有四個經常使用選項:1)內存 2)cookie 3)緩存 4)數據庫 // 數據庫 session。除非你很熟悉這一塊,知道本身要什麼,不然仍是老老實實用緩存吧 須要用到(connect-mongo插件 line 7) // app.use(sessionParser({ 會在數據庫中新建一個session集合存儲session // secret: 'express', // store: new mongoStore({ // url:'mongodb://127.0.0.1:27017/examSystem', // collection:'session' // }) // })); // 默認使用內存來存 session,對於開發調試來講很方便 app.use(sessionParser({ secret: '12345', // 建議使用 128 個字符的隨機字符串 name: 'userInfo', cookie: { maxAge: 1800000 }, // 時間能夠長點 resave:true, rolling:true, saveUninitialized:false }));
默認的使用方式:
// appi.js var index = require('./routes/index'); app.use('/', index);
// routes/index var express = require('express'); var router = express.Router(); /* GET home page. */ router.get('/', function(req, res, next) { res.render('index', { title: 'Express' }); }); module.exports = router;
我以前作的一個電子商城採用的這種方式:github地址
個人項目中:
// app.js var indexs = require('./routes/index'); var routes = require('./routes/routes'); indexs(app); routes(app);
// routes/index.js module.exports = function(app) { app.get('/api', (req, res) => { res.render('index', {title: 'Express'}); }) }
兩種方式有什麼不一樣:
goods.js
,index.js
,users.js
……),你都須要去app.js中引入// app.js var index = require('./routes/index'); var users = require('./routes/users'); var goods = require('./routes/goods'); app.use('/', index); app.use('/users', users); app.use('/goods', goods);
在前臺請求的時候:
// goods.js .... router.get("/list", function (req, res, next) { ... }
// xxx.vue ... this.$axios.get('/goods/list').then()... // 不能忘了加上goods,也就是你在app.js中定義的一級路由 ...
若是沒看懂,能夠去 GitHub上看一下實際代碼,有助於理解
不用在app.js中引入各個路由文件,一個route.js
就搞定了
// route.js var Teacher = require('../controllers/teacher'), Student = require('../controllers/student'); module.exports = function(app) { /*----------------------教師用戶----------------------*/ app.post('/api/register',Teacher.register); // 用戶登陸 app.post('/api/login', Teacher.signup); // 登出 app.post("/api/logout", Teacher.signout); // 獲取用戶信息 app.post('/api/getUserInfo',Teacher.getUserInfo); // 修改用戶信息 app.post('/api/updateUser', Teacher.updateUser); // 獲取試卷(分頁、模糊查詢) app.get('/api/mypapers', Teacher.getPapers); // 保存試卷 app.post('/api/savePaper', Teacher.savePaper); // 發佈試卷 app.post('/api/publishPaper', Teacher.publishPaper); // 刪除試卷 app.post('/api/deletePaper', Teacher.deletePaper); // 查找試卷 app.post('/api/findPaper', Teacher.findPaper); // 修改試題 app.post('/api/updateQuestion', Teacher.updateQuestion); // 修改試卷 app.post('/api/updatePaper', Teacher.updatePaper); // 獲取全部的考試 app.get('/api/getAllExams',Teacher.getAllExams); // 獲取已考試的試卷 app.get('/api/getExams',Teacher.getExams); // 獲取學生考試成績 app.get('/api/getScores', Teacher.getScores); // 批閱試卷 app.get('/api/getCheckPapers', Teacher.getCheckPapers); // 打分提交 app.get('/api/submitScore', Teacher.submitScore); /*----------------------學生用戶----------------------*/ // 學生註冊 app.post('/api/studentregister',Student.register); // 學生登陸 app.post('/api/studentlogin', Student.signup); // 學生登出 app.post('/api/studentlogout', Student.signout); // 修改信息 app.post('/api/updateStudent', Student.updateStudent); // 獲取考試記錄 app.get('/api/getexamlogs', Student.getExamLogs); // 獲取我的信息 app.get('/api/studentinfo', Student.getInfo); // 獲取考試信息 app.get('/api/getExamsPaper',Student.getExams); // 獲取試卷信息 app.get('/api/getExamInfo',Student.getExamInfo); // 提交考試信息 app.post('/api/submitExam',Student.submitExam); }
能夠看到,我將每一個路由的方法都是提取出去的,這樣能夠避免這個文件不會有太多的代碼,可讀性下降,將代碼分離開來,也有助於維護
在使用的時候:
// xxx.vue ... this.$axios.get('/api/getexamlogs').then()... ...
我此次用mongodb,主要是由於能夠用js來操做,對我來講比較簡單,mysql我不會用。在實際開發過程當中發現,考試系統各個表(集合)都是須要關聯,mongodb這種非關係型數據庫,作起來反而麻煩了很多。在此將一些數據庫增刪改查的方法回顧一下。
若是對mongodb,mongoose沒有基礎的瞭解,建議看一看 mongoose深刻淺出 , mongoose基礎操做
// controllers/student.js const Student = require('../model/student'); var mongoose = require('mongoose'); var Schema = mongoose.Schema; var student = new Student({ userId: 12001, // 學號 userName: '張三', // 用戶名 passWord: '123321', // 密碼 grade: 3, // 年級 1~6 分別表明一年級到六年級 class: 3, // 班級 exams:[{ // 參加的考試 _paper:Schema.Types.ObjectId("5a40a4ef485a584d44764ff1"), // 這個是_id,在mongodb自動生成的,從數據庫複製過來,初始化一個學生,應該是沒有參加考試的 score:100, date: new Date(), answers: [] }] }) // 保存 student.save((err,doc) => { console.log(err); });
exports.register = function (req,res) { let userInfo = req.body.userInfo; // req.body 獲取post方式傳遞的參數 Student.findOne(userInfo,(err,doc) => { if(err) { ... } else { if(doc) { res.json({ status:'2', msg: '用戶已存在' }) } else { userInfo.exams = []; // userInfo 是個對象,包含了用戶相關的信息 Student.create(userInfo,(err1,doc1) => { if(err1) { ... }else { if(doc1) { ... } else { ... } } }) } } }) };
以下圖是個人student
集合:
在該集合中,學生參加過的考試記錄,存在exams
數組中,當想實現分頁查詢幾條數據的時候,須要用到$slice
$slice:[start,size]
第一個參數表示,數組開始的下標,第二個表示截取的數量
在後臺接收到前臺傳遞的pageSize
和pageNumber
時,須要計算出當前須要截取的下標,即let skip = (pageNumber-1)*pageSize
exports.getExamLogs = function (req, res){ let userName =req.session.userName; let name = req.param('name'); // 經過req.param()取到的值都是字符串,而limit()須要一個數字做爲參數 let pageSize = parseInt(req.param('pageSize')); let pageNumber = parseInt(req.param('pageNumber')); let skip = (pageNumber-1)*pageSize; // 跳過幾條 let reg = new RegExp(name,'i'); // 在nodejs中,必需要使用RegExp,來構建正則表達式對象。 Student.findOne({"userName":userName},{"exams":{$slice:[skip,pageSize]}}).populate({path:'exams._paper',match:{name: reg}}) .exec((err,doc) => { if (err) { ... } else { if (doc) { res.json({ status: '0', msg:'success', result:doc, count: doc.exams.length?doc.exams.length:0 }) } else { ... } } }) };
每一個試卷都是獨立的文檔,經過他們的名稱
name
實現模糊查詢
// 獲取考試信息 exports.getExams = function (req,res) { let userName =req.session.userName; let name = req.param('name'); // 經過req.param()取到的值都是字符串,而limit()須要一個數字做爲參數 let pageSize = parseInt(req.param('pageSize')); let pageNumber = parseInt(req.param('pageNumber')); let skip = (pageNumber-1)*pageSize; // 跳過幾條 let reg = new RegExp(name,'i'); // 在nodejs中,必需要使用RegExp,來構建正則表達式對象。 Student.findOne({"userName":userName},(err,doc)=>{ if(err) { res.json({ status: '1', msg: err.message }) } else { if(doc) { // 關鍵在這裏 Paper.find({startTime:{$exists:true},name:reg}).skip(skip).limit(pageSize).populate({path:'_questions'}).exec((err1,doc1)=>{ .... }) };
先經過
populate
查詢除關聯文檔,在模糊分頁查詢
exports.getPapers = function (req, res) { // console.log(req.session.userName); let name = req.param('name'), // 經過req.param()取到的值都是字符串,而limit()須要一個數字做爲參數 pageSize = parseInt(req.param('pageSize')), pageNumber = parseInt(req.param('pageNumber')), userName = req.session.userName; let skip = (pageNumber-1)*pageSize; // 跳過幾條 let reg = new RegExp(name,'i'); // 在nodejs中,必需要使用RegExp,來構建正則表達式對象。 let params = { name: reg }; Teacher.findOne({'userName':userName}).populate({path:'_papers',match:{name: reg},options:{skip:skip,limit:pageSize}}) .exec((err, doc) => { .... }) };
mongodb原本就是非關係型的數據庫,可是有不少時候不一樣的集合直接是須要關聯的,這是就用到了mongoose提供的populate
直接看圖,不一樣集合直接的關聯,用的就是_id
,好比下圖中,學生參加的考試,關聯了試卷,試卷裏面又關聯了題目
怎麼查詢呢:
Student.findOne({}).populate({path:'exams._paper'}).exec(....)
更多的能夠看看我項目中的實際代碼都在server/controllers
下面
在系統中,教師能夠增長試卷,這個時候我就不知道該怎麼保存前臺傳過來的數據。數據中既有試卷的信息,也有不少題目。題目都屬於該試卷,改試卷又屬於當前登陸系統的老師(即建立試卷的老師)。
怎麼才能讓試卷、教師、問題關聯起來啊,ref存的是_id,然而這些新增的數據,是保存以後纔有_id的。
exports.savePaper = function (req, res) { let paperForm = req.body.paperForm; let userName = req.session.userName; if(paperForm == {}){ res.json({ status:'5', msg: '數據不能爲空' }) } // 第一步查找當前登陸的教師 Teacher.findOne({"userName": userName}, (err,doc)=>{ if (err) { ... } else { if (doc) { let paperData = { name:paperForm.name, totalPoints:paperForm.totalPoints, time:paperForm.time, _teacher: doc._id, // 這裏就能夠拿到教師的_id _questions: [], examnum:0 } // 第二步建立試卷 Paper.create(paperData,function (err1,doc1) { if (err1) { ... } else { if (doc1) { doc._papers.push(doc1._id); // 教師中添加該試卷的_id doc.save(); // 很重要 不save則沒有數據 // 第三步 建立問題 paperForm._questions.forEach(item => { item._papers = []; item._papers.push(doc1._id); // 試卷中存入試卷的_id,由於此時已經建立了試卷,因此能夠拿到_id item._teacher = doc._id; // 試卷中存入教師的_id }) Question.create(paperForm._questions,function (err2,doc2) { if (err2) { ... } else { if (doc2) { doc2.forEach(item => { doc1._questions.push(item._id); // 當問題建立成功,則在試卷中存入問題的_id }) doc1.save(); res.json({ status:'0', msg: 'success' }) } else { ... } } }) } else { ... } } }) } else { ... } } }) };
刪除某一個試卷,既要刪除教師中對應的試卷_id,也要刪除問題中對應的試卷_id
// 刪除試卷 exports.deletePaper = function (req, res) { let id = req.body.id; let userName = req.session.userName; // 第一步 刪除教師中的_id _papers是一個數組,因此用到了`$pull` Teacher.update({"userName":userName},{'$pull':{'_papers':{$in:id}}}, (err,doc)=>{ if (err) { res.json({ status:'1', msg: err.message }) } else { if (doc) { // 第二步 刪除試卷 即 移除一個文檔 Paper.remove({"_id":{$in:id}},function (err1,doc1){ if(err1) { res.json({ status:'1', msg: err1.message }) } else { if (doc1) { // 第三步 updateMany刪除多個問題中的_id 這裏並無刪除試卷中包含的問題,是爲了之後題庫作準備 Question.updateMany({'_papers':{$in:id}},{'$pull':{'_papers':{$in:id}}},function (err2,doc2) { if(err2){ ... } else { if (doc2){ ... } } }) } else { ... } } }) } else { ... } } }) };
// 修改試卷-修改試卷 exports.updatePaper = function (req,res) { let userName = req.session.userName; let params = req.body.params; let paperParams = { // 試卷須要更新的字段 name: params.name, totalPoints: params.totalPoints, time: params.time } let updateQuestion = []; // 須要更新的題目 let addQuestion = []; // 須要新增的題目 params._questions.forEach(item => { if(item._id) { // 經過判斷是否有_id區分已有的或者是新增的 updateQuestion.push(item); } else { addQuestion.push(item); } }) Teacher.findOne({'userName':userName},(err,doc)=>{ if (err) { ... } else { if (doc) { Paper.findOneAndUpdate({"_id":params._id},paperParams,(err1,doc1) => { if(err1) { ... }else { if(doc1){ updateQuestion.forEach((item,index)=>{ // 循環更新題目,好像很傻的方法,可能有更好的辦法 Question.update({"_id":item._id},item,(err2,doc2)=>{ if(err2){ res.json({ status:'1', msg: err2.message }) }else { if(doc2){ if(index == (updateQuestion.length-1)){ if (addQuestion.length>0){ addQuestion.forEach(item => { item._papers = []; item._papers.push(doc1._id); item._teacher = doc._id; }) // 建立新增題目 Question.create(addQuestion,(err3,doc3) => { if(err3) { ... } else { if(doc3) { doc3.forEach(item => { doc1._questions.push(item._id); // 還要將新增的題目關聯到試卷當中 }) doc1.save(); // 很重要 不save則沒有數據 res.json({ status:'0', msg: 'success' }) // .......................判斷太長省略........................ }) };
// 打分提交 exports.submitScore = function (req, res) { let name = req.param('userName'), date = req.param('date'), score = req.param('score') - 0, userName = req.session.userName; Teacher.findOne({'userName':userName},(err,doc) => { if(err) { ... } else { if(doc) { Student.update({"userName":name,"exams.date":date},{$set:{"exams.$.score":score,"exams.$.isSure":true}},(err1, doc1) => { if(err1) { ... } else { if(doc1) { ... } else { ... } } }) } else { ... } } }) };
//student.js const crypto = require('crypto'); let mdHash = function(data){ // hash 的定義要寫在這個方法內,否則會報錯Digest already called **** const hash = crypto.createHash('md5'); return hash.update(data).digest('hex'); } // 使用 //註冊 exports.register = function (req,res) { let userInfo = req.body.userInfo; 獲取到前臺傳過來的密碼,先加密再存儲 userInfo.passWord = mdHash(userInfo.passWord); ...