Forráskód Böngészése

添加 exam 模块

reghao 1 éve
szülő
commit
5a343188ba

+ 38 - 0
src/api/exam.js

@@ -0,0 +1,38 @@
+import { get, post } from '@/utils/request'
+
+const examApi = {
+  postExamPaper: '/api/content/exam/paper',
+  getExamQuestion: '/api/content/exam/question',
+  getExamResult: '/api/content/exam/result',
+  getExamPapers: '/api/content/exam/paper',
+  getExamPaperScore: '/api/content/exam/paper/score',
+  getExam: '/api/content/exam'
+}
+
+export function getExams(pageNumber) {
+  return get(examApi.getExam)
+}
+
+export function getExam(paperId) {
+  return get(examApi.getExam + '/' + paperId)
+}
+
+export function getExamInfoById(data) {
+  return get(examApi.getExamPapers, data)
+}
+
+export function getQuestionByIds(data) {
+  return get(examApi.getExamQuestion, data)
+}
+
+export function submitExamPaper(data) {
+  return post(examApi.postExamPaper, data)
+}
+
+export function getExamResult(examId) {
+  return get(examApi.getExamResult + '/' + examId)
+}
+
+export function getExamPaperScore(examId) {
+  return get(examApi.getExamPaperScore + '/' + examId)
+}

+ 80 - 0
src/assets/css/exam-page.scss

@@ -0,0 +1,80 @@
+
+* {
+  font-weight: 800;
+}
+
+.el-container {
+  width: 100%;
+  height: 100%;
+}
+
+.startExam {
+  color: #160f58;
+  border-bottom: 4px solid #ffd550;
+  font-size: 18px;
+  font-weight: 700;
+  padding-bottom: 10px
+}
+
+.examTitle {
+  font-size: 18px;
+  color: #cbcacf;
+  margin-left: 20px;
+  font-weight: 700;
+}
+
+.el-radio-group label {
+  display: block;
+  width: 400px;
+  padding: 48px 20px 10px 20px;
+  border-radius: 4px;
+  border: 1px solid #dcdfe6;
+  margin-bottom: 10px;
+  cursor: pointer;
+  position: relative;
+
+  span {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    font-size: 16px;
+  }
+}
+
+.el-radio-group label:hover {
+  background-color: rgb(245, 247, 250);
+}
+
+/*当前选中的答案*/
+.active {
+  border: 1px solid #1f90ff !important;
+  opacity: .5;
+}
+
+/*做过的题目的高亮颜色*/
+.done {
+  background-color: rgb(87, 148, 247);
+}
+
+/*未做题目的高亮颜色*/
+.noAnswer {
+  background-color: rgb(238, 238, 238);
+}
+
+/*当前在做的题目高亮的颜色*/
+.orange {
+  background-color: rgb(255, 213, 80);
+}
+
+.num {
+  display: inline-block;
+  background: url('../../assets/imgs/examTitle.png') no-repeat 100% 100%;
+  background-size: contain;
+  height: 37px;
+  width: 37px;
+  line-height: 30px;
+  color: #fff;
+  font-size: 20px;
+  text-align: center;
+  margin-right: 15px;
+}

+ 104 - 0
src/assets/css/examResult.scss

@@ -0,0 +1,104 @@
+
+* {
+  font-weight: 800;
+}
+
+.el-container {
+  width: 100%;
+  height: 100%;
+}
+
+.el-container {
+  animation: leftMoveIn .7s ease-in;
+}
+
+@keyframes leftMoveIn {
+  0% {
+    transform: translateX(-100%);
+    opacity: 0;
+  }
+  100% {
+    transform: translateX(0%);
+    opacity: 1;
+  }
+}
+
+.examName {
+  color: #160f58;
+  border-bottom: 4px solid #ffd550;
+  font-size: 18px;
+  font-weight: 700;
+  padding-bottom: 10px
+}
+
+.examTime {
+  font-size: 16px;
+  color: #cbcacf;
+  margin-left: 20px;
+  font-weight: 700;
+}
+
+.el-radio-group label {
+  display: block;
+  width: 400px;
+  padding: 48px 20px 10px 20px;
+  border-radius: 4px;
+  border: 1px solid #dcdfe6;
+  margin-bottom: 10px;
+  position: relative;
+
+  span {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    font-size: 16px;
+  }
+}
+
+.num {
+  display: inline-block;
+  background: url('../../assets/imgs/examTitle.png') no-repeat 100% 100%;
+  background-size: contain;
+  height: 37px;
+  width: 37px;
+  line-height: 30px;
+  color: #fff;
+  font-size: 20px;
+  text-align: center;
+  margin-right: 15px;
+}
+
+/*选中的答案*/
+.active {
+  border: 1px solid #1f90ff !important;
+  opacity: .5;
+}
+
+/*  选中的答案且是正确答案*/
+.activeAndTrue {
+  border: 1px solid #1f90ff !important;
+  opacity: .5;
+  height: 15px;
+  width: 15px;
+  background-size: contain;
+  background: url('../../assets/imgs/true.png') no-repeat 95%;
+  position: absolute;
+  top: 0;
+  left: 0;
+}
+
+.true {
+  height: 15px;
+  width: 15px;
+  background-size: contain;
+  background: url('../../assets/imgs/true.png') no-repeat 95%;
+  position: absolute;
+  top: 0;
+  left: 0;
+}
+
+.ques-analysis {
+  padding: 30px 40px;
+  background: #f6f6f8;
+  margin-bottom: 70px;
+}

BIN
src/assets/img/bg.png


BIN
src/assets/img/examTitle.png


BIN
src/assets/img/judge.png


BIN
src/assets/img/multiple.png


BIN
src/assets/img/order.png


BIN
src/assets/img/random.png


BIN
src/assets/img/single.png


BIN
src/assets/img/true.png


+ 55 - 21
src/router/admin.js

@@ -2,46 +2,80 @@
 // 后台主页
 // ********************************************************************************************************************
 const Admin = () => import('views/admin/Admin')
-const SiteConfig = () => import('views/admin/SiteConfig')
-const Message = () => import('views/admin/Message')
-const UserList = () => import('views/admin/UserList')
-const PostList = () => import('views/admin/PostList')
-const DataSource = () => import('views/admin/DataSource')
+// const SiteConfig = () => import('views/admin/SiteConfig')
+// const Message = () => import('views/admin/Message')
+// const UserList = () => import('views/admin/UserList')
+// const PostList = () => import('views/admin/PostList')
+// const DataSource = () => import('views/admin/DataSource')
+
+const ExamDashboard = () => import('views/exam/ExamDashboard')
+const ExamIndex = () => import('views/exam/ExamIndex')
+const Exam = () => import('views/exam/Exam')
+const ExamResult = () => import('views/exam/ExamResult')
+const ExamSubject = () => import('views/exam/ExamSubject')
+const ExamQuestion = () => import('views/exam/ExamQuestion')
+const ExamPaper = () => import('views/exam/ExamPaper')
+const ExamMarker = () => import('views/exam/ExamMarker')
+const ExamResults = () => import('views/exam/ExamResults')
 
 export default {
-  path: '/admin',
-  name: 'Admin',
+  path: '/exam',
+  name: 'Exam',
   component: Admin,
   meta: { needAuth: true },
   children: [
     {
       path: '',
-      name: 'SiteConfig',
-      component: SiteConfig,
+      name: 'ExamDashboard',
+      component: ExamDashboard,
+      meta: { needAuth: true }
+    },
+    {
+      path: '/exam/paper/:examId',
+      name: 'Exam',
+      component: Exam,
+      meta: { needAuth: true }
+    },
+    {
+      path: '/exam/result/:examId',
+      name: 'ExamResult',
+      component: ExamResult,
+      meta: { needAuth: true }
+    },
+    {
+      path: '/exam/results',
+      name: 'ExamResults',
+      component: ExamResults,
+      meta: { needAuth: true }
+    },
+    {
+      path: '/exam/subject',
+      name: 'ExamSubject',
+      component: ExamSubject,
       meta: { needAuth: true }
     },
     {
-      path: '/admin/message',
-      name: 'Message',
-      component: Message,
+      path: '/exam/question',
+      name: 'ExamQuestion',
+      component: ExamQuestion,
       meta: { needAuth: true }
     },
     {
-      path: '/admin/account/user',
-      name: '用户列表',
-      component: UserList,
+      path: '/exam/paper',
+      name: 'ExamPaper',
+      component: ExamPaper,
       meta: { needAuth: true }
     },
     {
-      path: '/admin/content/video',
-      name: '稿件列表',
-      component: PostList,
+      path: '/exam/marker',
+      name: 'ExamMarker',
+      component: ExamMarker,
       meta: { needAuth: true }
     },
     {
-      path: '/admin/content/datasource',
-      name: '数据源',
-      component: DataSource,
+      path: '/exam/todo',
+      name: 'ExamIndex',
+      component: ExamIndex,
       meta: { needAuth: true }
     }
   ]

+ 27 - 1
src/utils/util.js

@@ -76,6 +76,31 @@ function delCookie(name) {
   setCookie(name, null, -1)
 }
 
+const validFormAndInvoke = (formEl, success, message = '信息有误', fail = function () {
+}) => {
+  if (!formEl) {
+    return
+  }
+  formEl.validate(valid => {
+    if (valid) {// form valid succeed
+      // do success function
+      success()
+      // reset fields
+      formEl.resetFields()
+    } else {// form valid fail
+      Vue.prototype.$notify({
+        title: 'Tips',
+        message: message,
+        type: 'error',
+        duration: 2000
+      })
+      // do something when fail
+      fail()
+      return false
+    }
+  })
+}
+
 /**
  * 导出
  **/
@@ -86,5 +111,6 @@ export {
   removeStore,
   setCookie,
   getCookie,
-  delCookie
+  delCookie,
+  validFormAndInvoke
 }

+ 27 - 28
src/views/admin/Admin.vue

@@ -4,9 +4,9 @@
       <el-col :md="2">
         <ul class="el-menu--horizontal el-menu">
           <li class="el-menu-item">
-            <a href="/admin" style="text-decoration-line: none">
+            <a href="/exam" style="text-decoration-line: none">
               <img src="@/assets/img/icon/logo.png" class="el-avatar--circle el-avatar--medium" alt="img">
-              admin
+              exam
             </a>
           </li>
         </ul>
@@ -38,35 +38,43 @@
           class="el-menu-vertical-demo"
           :unique-opened="true"
         >
-          <el-menu-item index="/admin/message">
-            <i class="el-icon-message" />
-            <span slot="title">消息</span>
-          </el-menu-item>
-          <el-submenu index="/admin/account">
+          <el-submenu index="/exam">
             <template slot="title">
               <i class="el-icon-user" />
-              <span slot="title">帐号</span>
+              <span slot="title">我的考试</span>
             </template>
             <el-menu-item-group>
-              <el-menu-item index="/admin/account/user">
+              <el-menu-item index="/exam/todo">
                 <i class="el-icon-film" />
-                <span slot="title">用户列表</span>
+                <span slot="title">考试列表</span>
+              </el-menu-item>
+              <el-menu-item index="/exam/results">
+                <i class="el-icon-film" />
+                <span slot="title">考试结果</span>
               </el-menu-item>
             </el-menu-item-group>
           </el-submenu>
-          <el-submenu index="/admin/content">
+          <el-submenu index="/exam">
             <template slot="title">
               <i class="el-icon-user" />
-              <span slot="title">内容</span>
+              <span slot="title">考试管理</span>
             </template>
             <el-menu-item-group>
-              <el-menu-item index="/admin/content/video">
-                <i class="el-icon-postcard" />
-                <span slot="title">稿件列表</span>
+              <el-menu-item index="/exam/subject">
+                <i class="el-icon-film" />
+                <span slot="title">科目管理</span>
               </el-menu-item>
-              <el-menu-item index="/admin/content/datasource">
-                <i class="el-icon-date" />
-                <span slot="title">数据源</span>
+              <el-menu-item index="/exam/question">
+                <i class="el-icon-film" />
+                <span slot="title">试题管理</span>
+              </el-menu-item>
+              <el-menu-item index="/exam/paper">
+                <i class="el-icon-film" />
+                <span slot="title">试卷管理</span>
+              </el-menu-item>
+              <el-menu-item index="/exam/marker">
+                <i class="el-icon-film" />
+                <span slot="title">阅卷管理</span>
               </el-menu-item>
             </el-menu-item-group>
           </el-submenu>
@@ -89,7 +97,7 @@ export default {
     }
   },
   created() {
-    document.title = '后台管理'
+    document.title = '考试系统'
     const userInfo = getAuthedUser()
     if (userInfo !== null) {
       this.user = userInfo
@@ -99,13 +107,4 @@ export default {
 </script>
 
 <style>
-.el-header {
-  background-color: #B3C0D1;
-  color: #333;
-  line-height: 60px;
-}
-
-.el-aside {
-  color: #333;
-}
 </style>

+ 732 - 0
src/views/exam/Exam.vue

@@ -0,0 +1,732 @@
+<template>
+  <el-container v-if="show">
+    <el-header style="margin-top: 60px">
+      <el-row>
+        <el-col :span="18" :offset="3" style="border-bottom: 1px solid #f5f5f5">
+          <span class="startExam">开始考试</span>
+          <span class="examTitle">距离考试结束还有:</span>
+          <span style="color: red;font-size: 18px;">{{ duration | timeFormat }}</span>
+          <el-button
+            type="warning"
+            round
+            style="background-color: #ffd550;float: right;color: black;font-weight: 800"
+            @click="uploadExamToAdmin"
+          >提交试卷
+          </el-button>
+        </el-col>
+      </el-row>
+    </el-header>
+
+    <el-main>
+      <el-row>
+        <el-col :span="18" :offset="3">
+          <el-col :span="16">
+            <el-card style="min-height: 500px">
+              <!--题目信息-->
+              <div>
+                <i class="num">{{ curIndex + 1 }}</i>
+                <span v-if="questionInfo[curIndex].questionType === 1">【单选题】</span>
+                <span v-else-if="questionInfo[curIndex].questionType === 2">【多选题】</span>
+                <span v-else-if="questionInfo[curIndex].questionType === 3">【判断题】</span>
+                <span v-else>【简答题】</span>
+                <span>{{ questionInfo[curIndex].questionContent }}:</span>
+              </div>
+              <!--题目中的配图-->
+              <img
+                v-for="url in questionInfo[curIndex].images"
+                :src="url"
+                title="点击查看大图"
+                alt="题目图片"
+                style="width: 100px;height: 100px;cursor: pointer"
+                @click="showBigImg(url)"
+              >
+
+              <!--单选 和 判断 的答案列表-->
+              <div
+                v-show="questionInfo[curIndex].questionType === 1 || questionInfo[curIndex].questionType === 3"
+                style="margin-top: 25px"
+              >
+                <div class="el-radio-group">
+                  <label
+                    v-for="(item,index) in questionInfo[curIndex].answer"
+                    :class="index === userAnswer[curIndex] ? 'active' : ''"
+                    @click="checkSingleAnswer(index)"
+                  >
+                    <span>{{ optionName[index] + '、' + item.answer }}</span>
+                    <img
+                      v-for="i2 in item.images"
+                      v-if="item.images !== null"
+                      style="position: absolute;left:100%;top:50%;transform: translateY(-50%);
+                  width: 40px;height: 40px;float: right;cursor: pointer;"
+                      title="点击查看大图"
+                      :src="i2"
+                      alt=""
+                      @mouseover="showBigImg(i2)"
+                    >
+                  </label>
+                </div>
+              </div>
+
+              <!--多选的答案列表-->
+              <div v-show="questionInfo[curIndex].questionType === 2" style="margin-top: 25px">
+                <div class="el-radio-group">
+                  <label
+                    v-for="(item,index) in questionInfo[curIndex].answer"
+                    :class="(userAnswer[curIndex]+'').indexOf(index+'') !== -1? 'active' : ''"
+                    @click="selectedMultipleAnswer(index)"
+                  >
+                    <span>{{ optionName[index] + '、' + item.answer }}</span>
+                    <img
+                      v-for="i2 in item.images"
+                      v-if="item.images !== null"
+                      style="position: absolute;left:100%;top:50%;transform: translateY(-50%);
+                  width: 40px;height: 40px;float: right;cursor: pointer;"
+                      title="点击查看大图"
+                      :src="i2"
+                      alt=""
+                      @mouseover="showBigImg(i2)"
+                    >
+                  </label>
+                </div>
+              </div>
+
+              <!--简答题的答案-->
+              <div v-show="questionInfo[curIndex].questionType === 4" style="margin-top: 25px">
+                <el-input
+                  v-model="userAnswer[curIndex]"
+                  type="textarea"
+                  :rows="8"
+                  placeholder="请输入答案"
+                />
+              </div>
+
+              <!--上一题 下一题-->
+              <div style="margin-top: 25px">
+                <el-button type="primary" icon="el-icon-back" :disabled="curIndex<1" @click="curIndex--">上一题</el-button>
+                <el-button
+                  type="primary"
+                  icon="el-icon-right"
+                  :disabled="curIndex>=questionInfo.length-1"
+                  @click="curIndex++"
+                >下一题
+                </el-button>
+              </div>
+            </el-card>
+          </el-col>
+
+          <el-col :span="7" :offset="1">
+            <!--答题卡卡片-->
+            <el-card>
+              <div>
+                <p style="font-size: 18px;">答题卡</p>
+                <div style="margin-top: 25px">
+                  <span
+                    style="background-color: rgb(238,238,238);padding: 5px 10px 5px 10px;margin-left: 15px"
+                  >未作答</span>
+                  <span
+                    style="background-color: rgb(87,148,247);color: white;
+                padding: 5px 10px 5px 10px;margin-left: 15px"
+                  >已作答</span>
+                </div>
+              </div>
+
+              <!--单选的答题卡-->
+              <div style="margin-top: 25px">
+                <p style="font-size: 18px;">单选题</p>
+                <el-button
+                  v-for="item in questionInfo.length"
+                  v-show="questionInfo[item-1].questionType === 1"
+                  :key="item"
+                  style="margin-top: 10px;margin-left: 15px"
+                  size="mini"
+                  :class="questionInfo[item-1].questionType === 1 && userAnswer[item-1] !== undefined ?
+                    'done' : userAnswer[item-1] === undefined ? curIndex === (item-1) ? 'orange' : 'noAnswer' : 'noAnswer'"
+                  @click="curIndex = item-1"
+                >{{ item }}
+                </el-button>
+              </div>
+
+              <!--多选的答题卡-->
+              <div style="margin-top: 25px">
+                <p style="font-size: 18px;">多选题</p>
+                <el-button
+                  v-for="item in questionInfo.length"
+                  v-show="questionInfo[item-1].questionType === 2"
+                  :key="item"
+                  style="margin-top: 10px;margin-left: 15px"
+                  size="mini"
+                  :class="questionInfo[item-1].questionType === 2 && userAnswer[item-1] !== undefined ?
+                    'done' : userAnswer[item-1] === undefined ? curIndex === (item-1) ? 'orange' : 'noAnswer' : 'noAnswer'"
+                  @click="curIndex = item-1"
+                >{{ item }}
+                </el-button>
+              </div>
+
+              <!--判断的答题卡-->
+              <div style="margin-top: 25px">
+                <p style="font-size: 18px;">判断题</p>
+                <el-button
+                  v-for="item in questionInfo.length"
+                  v-show="questionInfo[item-1].questionType === 3"
+                  :key="item"
+                  style="margin-top: 10px;margin-left: 15px"
+                  size="mini"
+                  :class="questionInfo[item-1].questionType === 3 && userAnswer[item-1] !== undefined ?
+                    'done' : userAnswer[item-1] === undefined ? curIndex === (item-1) ? 'orange' : 'noAnswer' : 'noAnswer'"
+                  @click="curIndex = item-1"
+                >{{ item }}
+                </el-button>
+              </div>
+
+              <!--简答的答题卡-->
+              <div style="margin-top: 25px">
+                <p style="font-size: 18px;">简答题</p>
+                <el-button
+                  v-for="item in questionInfo.length"
+                  v-show="questionInfo[item-1].questionType === 4"
+                  :key="item"
+                  style="margin-top: 10px;margin-left: 15px"
+                  size="mini"
+                  :class="questionInfo[item-1].questionType === 4 && userAnswer[item-1] !== undefined ?
+                    'done' : userAnswer[item-1] === undefined ? curIndex === (item-1) ? 'orange' : 'noAnswer' : 'noAnswer'"
+                  @click="curIndex = item-1"
+                >{{ item }}
+                </el-button>
+              </div>
+            </el-card>
+          </el-col>
+
+        </el-col>
+      </el-row>
+      <video
+        id="video"
+        muted="muted"
+        style="float:right;position: fixed;top: 80%;left: 85%"
+        width="200px"
+        height="200px"
+        autoplay="autoplay"
+      />
+      <canvas id="canvas" hidden width="200px" height="200px" />
+    </el-main>
+    <!--图片回显-->
+    <el-dialog :visible.sync="bigImgDialog" @close="bigImgDialog = false">
+      <img style="width: 100%" :src="bigImgUrl">
+    </el-dialog>
+  </el-container>
+
+</template>
+
+<script>
+import { submitExamPaper, getExamInfoById, getQuestionByIds } from '@/api/exam'
+
+export default {
+  name: 'ExamPage',
+  filters: {
+    ellipsis(value) {
+      if (!value) return ''
+      const max = 50
+      if (value.length > max) {
+        return value.slice(0, max) + '...'
+      }
+      return value
+    },
+    timeFormat(time) {
+      // 分钟
+      var minute = time / 60
+      var minutes = parseInt(minute)
+
+      if (minutes < 10) {
+        minutes = '0' + minutes
+      }
+
+      // 秒
+      var second = time % 60
+      var seconds = Math.round(second)
+      if (seconds < 10) {
+        seconds = '0' + seconds
+      }
+      return `${minutes}:${seconds}`
+    }
+  },
+  data() {
+    return {
+      // 当前考试的信息
+      examInfo: {},
+      // 当前的考试题目
+      questionInfo: [
+        {
+          questionType: ''
+        }
+      ],
+      // 当前题目的索引值
+      curIndex: 0,
+      // 控制大图的对话框
+      bigImgDialog: false,
+      // 当前要展示的大图的url
+      bigImgUrl: '',
+      // 用户选择的答案
+      userAnswer: [],
+      // 页面数据加载
+      loading: {},
+      // 页面绘制是否开始
+      show: false,
+      // 答案的选项名abcd数据
+      optionName: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
+      // 考试总时长
+      duration: 0,
+      // 摄像头对象
+      mediaStreamTrack: null,
+      // 诚信照片的url
+      takePhotoUrl: [],
+      // 摄像头是否开启
+      cameraOn: false
+    }
+  },
+  watch: {
+    // 监控考试的剩余时间
+    async duration(newVal) {
+      const examDuration = {
+        duration: newVal,
+        timestamp: Date.now()
+      }
+      localStorage.setItem('examDuration' + this.examInfo.examId, JSON.stringify(examDuration))
+
+      // 摄像头数据
+      const constraints = {
+        video: {
+          width: 200,
+          height: 200
+        },
+        audio: false
+      }
+      // 通过调用摄像头判断用户是否中途关闭摄像头
+      /* const promise = navigator.mediaDevices.getUserMedia(constraints)
+      promise.catch((back) => {
+        this.cameraOn = false
+      })*/
+      if (!this.cameraOn) { // 如果摄像头未开启,就再次调用开启
+        this.getCamera()
+      }
+      // 考试时间结束了提交试卷
+      if (newVal < 1) {
+        /* if (this.cameraOn) {
+          // 结束的时候拍照上传一张
+          await this.takePhoto()
+          this.closeCamera()
+        }*/
+        const data = {}
+        data.questionIds = []
+        data.userAnswers = this.userAnswer.join('-')
+        this.questionInfo.forEach((item, index) => {
+          data.questionIds.push(item.questionId)
+          // 当前数据不完整,用户回答不完整(我们自动补充空答案,防止业务出错)
+          if (index > this.userAnswer.length) {
+            data.userAnswers += ' -'
+          }
+        })
+        // 如果所有题目全部未答
+        if (data.userAnswers === '') {
+          this.questionInfo.forEach(item => {
+            data.userAnswers += ' -'
+          })
+          data.userAnswers.split(0, data.userAnswers.length - 1)
+        }
+        data.examId = parseInt(this.$route.params.examId)
+
+        data.questionIds = data.questionIds.join(',')
+        data.creditImgUrl = this.takePhotoUrl.join(',')
+        submitExamPaper(data).then((resp) => {
+          if (resp.code === 0) {
+            this.$notify({
+              title: 'Tips',
+              message: '考试时间结束,已为您自动提交 *^▽^*',
+              type: 'success',
+              duration: 2000
+            })
+            this.$router.push('/examResult/' + resp.data)
+          }
+        })
+      }
+    }
+  },
+  created() {
+    this.getExamInfo()
+    // 页面数据加载的等待状态栏
+    /* this.loading = this.$loading.service({
+      body: true,
+      lock: true,
+      text: '数据拼命加载中,(*╹▽╹*)',
+      spinner: 'el-icon-loading'
+    })*/
+    // 开启摄像头
+    window.onload = () => {
+      setTimeout(() => {
+        this.getCamera()
+      }, 1000)
+
+      // 生成3次时间点截图
+      const times = []
+      for (let i = 0; i < 2; i++) {
+        times.push(Math.ceil(Math.random() * this.duration * 1000))
+      }
+      times.push(10000)
+      // 一次考试最多3次随机的诚信截图
+      times.forEach(item => {
+        window.setTimeout(() => {
+          this.takePhoto()
+        }, item)
+      })
+    }
+  },
+  mounted() {
+    // 关闭浏览器窗口的时候移除 localstorage的时长
+    var userAgent = navigator.userAgent // 取得浏览器的userAgent字符串
+    var isOpera = userAgent.indexOf('Opera') > -1 // 判断是否Opera浏览器
+    var isIE = userAgent.indexOf('compatible') > -1 && userAgent.indexOf('MSIE') > -1 && !isOpera // 判断是否IE浏览器
+    var isIE11 = userAgent.indexOf('rv:11.0') > -1 // 判断是否是IE11浏览器
+    var isEdge = userAgent.indexOf('Edge') > -1 && !isIE // 判断是否IE的Edge浏览器
+    if (!isIE && !isEdge && !isIE11) { // 兼容chrome和firefox
+      var _beforeUnload_time = 0; var _gap_time = 0
+      var is_fireFox = navigator.userAgent.indexOf('Firefox') > -1// 是否是火狐浏览器
+      window.onunload = function() {
+        _gap_time = new Date().getTime() - _beforeUnload_time
+        if (_gap_time <= 5) {
+          localStorage.removeItem('examDuration' + this.examInfo.examId)
+        } else { // 谷歌浏览器刷新
+        }
+      }
+      window.onbeforeunload = function() {
+        _beforeUnload_time = new Date().getTime()
+        if (is_fireFox) { // 火狐关闭执行
+
+        } else { // 火狐浏览器刷新
+        }
+      }
+    }
+  },
+  methods: {
+    // 查询当前考试的信息
+    getExamInfo() {
+      getExamInfoById(this.$route.params).then((resp) => {
+        if (resp.code === 0) {
+          this.examInfo = resp.data
+          // 设置定时(秒)
+          try {
+            const examDuration = JSON.parse(localStorage.getItem('examDuration' + this.examInfo.examId) || '{}')
+            if (examDuration.duration === 0 || Date.now() >= (examDuration.timestamp || Date.now()) + (examDuration.duration * 1000 || Date.now())) {
+              localStorage.removeItem('examDuration' + this.examInfo.examId)
+            }
+            this.duration = Math.min(JSON.parse(localStorage.getItem('examDuration' + this.examInfo.examId) || '{}').duration || resp.data.examDuration * 60, resp.data.examDuration * 60)
+          } catch (e) {
+            localStorage.removeItem('examDuration' + this.examInfo.examId)
+          }
+          // 考试剩余时间定时器
+          this.timer = window.setInterval(() => {
+            if (this.duration > 0) this.duration--
+          }, 1000)
+
+          var param = resp.data.questionIds.split(',')
+          this.getQuestionInfo(param)
+        }
+      })
+    },
+    // 查询考试的题目信息
+    async getQuestionInfo(ids) {
+      await getQuestionByIds({ 'ids': ids.join(',') }).then(resp => {
+        if (resp.code === 0) {
+          this.questionInfo = resp.data || []
+          // 重置问题的顺序 单选 多选 判断 简答
+          this.questionInfo = this.questionInfo.sort(function(a, b) {
+            return a.questionType - b.questionType
+          })
+        }
+      })
+      // this.loading.close()
+      this.show = true
+    },
+    // 点击展示高清大图
+    showBigImg(url) {
+      this.bigImgUrl = url
+      this.bigImgDialog = true
+    },
+    // 检验单选题的用户选择的答案
+    checkSingleAnswer(index) {
+      this.$set(this.userAnswer, this.curIndex, index)
+    },
+    // 多选题用户的答案选中
+    selectedMultipleAnswer(index) {
+      if (this.userAnswer[this.curIndex] === undefined) { // 当前是多选的第一个答案
+        this.$set(this.userAnswer, this.curIndex, index)
+      } else if (String(this.userAnswer[this.curIndex]).split(',').includes(index + '')) { // 取消选中
+        const newArr = []
+        String(this.userAnswer[this.curIndex]).split(',').forEach(item => {
+          if (item !== '' + index) newArr.push(item)
+        })
+        if (newArr.length === 0) {
+          this.$set(this.userAnswer, this.curIndex, undefined)
+        } else {
+          this.$set(this.userAnswer, this.curIndex, newArr.join(','))
+          // 答案格式化顺序DBAC -> ABCD
+          this.userAnswer[this.curIndex] = String(this.userAnswer[this.curIndex]).split(',').sort(function(a, b) {
+            return a - b
+          }).join(',')
+        }
+      } else if (!((this.userAnswer[this.curIndex] + '').split(',').includes(index + ''))) { // 第n个答案
+        this.$set(this.userAnswer, this.curIndex, this.userAnswer[this.curIndex] += ',' + index)
+        // 答案格式化顺序DBAC -> ABCD
+        this.userAnswer[this.curIndex] = String(this.userAnswer[this.curIndex]).split(',').sort(function(a, b) {
+          return a - b
+        }).join(',')
+      }
+    },
+    // 调用摄像头
+    getCamera() {
+      const constraints = {
+        video: {
+          width: 200,
+          height: 200
+        },
+        audio: false
+      }
+      // 获得video摄像头
+      const video = document.getElementById('video')
+      /* const promise = navigator.mediaDevices.getUserMedia(constraints)
+      promise.then((mediaStream) => {
+        this.mediaStreamTrack = typeof mediaStream.stop === 'function' ? mediaStream : mediaStream.getTracks()[1]
+        video.srcObject = mediaStream
+        video.play()
+        this.cameraOn = true
+      }).catch((back) => {
+        this.$message({
+          duration: 1500,
+          message: '请开启摄像头权限o(╥﹏╥)o!',
+          type: 'error'
+        })
+      })*/
+    },
+    // 拍照
+    async takePhoto() {
+      if (this.cameraOn) { // 摄像头是否开启 开启了才执行上传信用图片
+        // 获得Canvas对象
+        const video = document.getElementById('video')
+        const canvas = document.getElementById('canvas')
+        const ctx = canvas.getContext('2d')
+        ctx.drawImage(video, 0, 0, 200, 200)
+        // toDataURL  ---  可传入'image/png'---默认, 'image/jpeg'
+        const img = document.getElementById('canvas').toDataURL()
+
+        // 构造post的form表单
+        const formData = new FormData()
+        // convertBase64UrlToBlob函数是将base64编码转换为Blob
+        formData.append('file', this.base64ToFile(img, 'examTakePhoto.png'))
+        // 上传阿里云OSS
+        await ossUtils.uploadImage(formData).then((resp) => {
+          if (resp.code === 0) this.takePhotoUrl.push(resp.data)
+        })
+      }
+    },
+    // 关闭摄像头
+    closeCamera() {
+      const stream = document.getElementById('video').srcObject
+      const tracks = stream.getTracks()
+      tracks.forEach(function(track) {
+        track.stop()
+      })
+      document.getElementById('video').srcObject = null
+    },
+    // 将摄像头截图的base64串转化为file提交后台
+    base64ToFile(urlData, fileName) {
+      const arr = urlData.split(',')
+      const mime = arr[0].match(/:(.*?);/)[1]
+      const bytes = atob(arr[1]) // 解码base64
+      let n = bytes.length
+      const ia = new Uint8Array(n)
+      while (n--) {
+        ia[n] = bytes.charCodeAt(n)
+      }
+      return new File([ia], fileName, { type: mime })
+    },
+    // 上传用户考试信息进入后台
+    async uploadExamToAdmin() {
+      if (this.cameraOn) await this.takePhoto()// 结束的时候拍照上传一张
+      // 正则
+      var reg = new RegExp('-', 'g')
+      // 去掉用户输入的非法分割符号(-),保证后端接受数据处理不报错
+      this.userAnswer.forEach((item, index) => {
+        if (this.questionInfo[index].questionType === 4) { // 简答题答案处理
+          this.userAnswer[index] = item.replace(reg, ' ')
+        }
+      })
+
+      // 标记题目是否全部做完
+      let flag = true
+      for (let i = 0; i < this.userAnswer.length; i++) { // 检测用户是否题目全部做完
+        if (this.userAnswer[i] === undefined) {
+          flag = false
+        }
+      }
+      // 如果用户所有答案的数组长度小于题目长度,这个时候也要将标志位置为false
+      if (this.userAnswer.length < this.questionInfo.length) {
+        flag = false
+      }
+      // 题目未做完
+      if (!flag) {
+        // if (this.userAnswer.some((item) => item === undefined)) {
+        this.$confirm('当前试题暂未做完, 是否继续提交o(╥﹏╥)o ?', 'Tips', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(() => {
+          const data = {}
+          data.questionIds = []
+          data.userAnswers = this.userAnswer.join('-')
+          this.questionInfo.forEach((item, index) => {
+            data.questionIds.push(item.questionId)
+            // 当前数据不完整,用户回答不完整(我们自动补充空答案,防止业务出错)
+            if (index > (this.userAnswer.length - 1)) {
+              data.userAnswers += '- '
+            }
+          })
+          // 如果所有题目全部未答
+          if (this.userAnswer.length === 0) {
+            this.questionInfo.forEach(item => {
+              data.userAnswers += ' -'
+            })
+            data.userAnswers.split(0, data.userAnswers.length - 1)
+          }
+          data.examId = parseInt(this.$route.params.examId)
+          data.questionIds = data.questionIds.join(',')
+          data.creditImgUrl = this.takePhotoUrl.join(',')
+          submitExamPaper(data).then((resp) => {
+            if (resp.code === 0) {
+              this.$notify({
+                title: 'Tips',
+                message: '考试结束 *^▽^*',
+                type: 'success',
+                duration: 2000
+              })
+              this.$router.push('/exam/result/' + resp.data)
+            }
+          })
+        }).catch(() => {
+          this.$notify({
+            title: 'Tips',
+            message: '继续加油! *^▽^*',
+            type: 'success',
+            duration: 2000
+          })
+        })
+      } else { // 当前题目做完了
+        if (this.cameraOn) {
+          // 结束的时候拍照上传一张
+          await this.takePhoto()
+          this.closeCamera()
+        }
+        const data = {}
+        data.questionIds = []
+        data.userAnswers = this.userAnswer.join('-')
+        data.examId = parseInt(this.$route.params.examId)
+        data.creditImgUrl = this.takePhotoUrl.join(',')
+        this.questionInfo.forEach((item, index) => {
+          data.questionIds.push(item.questionId)
+        })
+        data.questionIds = data.questionIds.join(',')
+        submitExamPaper(data).then((resp) => {
+          if (resp.code === 0) {
+            this.$notify({
+              title: 'Tips',
+              message: '考试结束了捏 *^▽^*',
+              type: 'success',
+              duration: 2000
+            })
+
+            this.$router.push('/exam/result/' + resp.data)
+          }
+        })
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+* {
+  font-weight: 800;
+}
+
+.el-container {
+  width: 100%;
+  height: 100%;
+}
+
+.startExam {
+  color: #160f58;
+  border-bottom: 4px solid #ffd550;
+  font-size: 18px;
+  font-weight: 700;
+  padding-bottom: 10px
+}
+
+.examTitle {
+  font-size: 18px;
+  color: #cbcacf;
+  margin-left: 20px;
+  font-weight: 700;
+}
+
+.el-radio-group label {
+  display: block;
+  width: 400px;
+  padding: 48px 20px 10px 20px;
+  border-radius: 4px;
+  border: 1px solid #dcdfe6;
+  margin-bottom: 10px;
+  cursor: pointer;
+  position: relative;
+
+  span {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    font-size: 16px;
+  }
+}
+
+.el-radio-group label:hover {
+  background-color: rgb(245, 247, 250);
+}
+
+/*当前选中的答案*/
+.active {
+  border: 1px solid #1f90ff !important;
+  opacity: .5;
+}
+
+/*做过的题目的高亮颜色*/
+.done {
+  background-color: rgb(87, 148, 247);
+}
+
+/*未做题目的高亮颜色*/
+.noAnswer {
+  background-color: rgb(238, 238, 238);
+}
+
+/*当前在做的题目高亮的颜色*/
+.orange {
+  background-color: rgb(255, 213, 80);
+}
+
+.num {
+  display: inline-block;
+  background: url('../../assets/img/examTitle.png') no-repeat 95%;
+  background-size: contain;
+  height: 37px;
+  width: 37px;
+  line-height: 30px;
+  color: #fff;
+  font-size: 20px;
+  text-align: center;
+  margin-right: 15px;
+}
+</style>

+ 23 - 0
src/views/exam/ExamDashboard.vue

@@ -0,0 +1,23 @@
+<template>
+  <el-row>
+    <span>dashboard</span>
+  </el-row>
+</template>
+
+<script>
+export default {
+  name: 'ExamDashboard',
+  data() {
+    return {
+    }
+  },
+  created() {
+    document.title = 'Dashboard'
+  },
+  methods: {
+  }
+}
+</script>
+
+<style>
+</style>

+ 234 - 0
src/views/exam/ExamIndex.vue

@@ -0,0 +1,234 @@
+<template>
+  <el-row>
+    <el-row>
+      <el-form :inline="true" :model="searchForm" class="demo-form-inline">
+        <el-form-item>
+          <el-select v-model="searchForm.type" placeholder="查询类型">
+            <el-option label="稿件标题" value="1" />
+            <el-option label="用户ID" value="2" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-input v-model="searchForm.content" placeholder="" />
+        </el-form-item>
+        <el-form-item>
+          <el-button size="mini" type="warning" @click="search">查询</el-button>
+        </el-form-item>
+      </el-form>
+      <el-table
+        :data="dataList"
+        border
+        style="width: 100%"
+      >
+        <el-table-column
+          fixed="left"
+          label="No"
+          type="index"
+        />
+        <el-table-column
+          prop="examName"
+          label="考试名称"
+          width="150"
+        />
+        <el-table-column
+          prop="type"
+          label="考试类型"
+          width="90"
+        />
+        <el-table-column
+          prop="startTime"
+          label="考试时间"
+          width="150"
+        />
+        <el-table-column
+          prop="duration"
+          label="考试时长(分钟)"
+          width="150"
+        />
+        <el-table-column
+          prop="totalScore"
+          label="考试总分"
+        />
+        <el-table-column
+          prop="passScore"
+          label="及格分数"
+        />
+        <el-table-column
+          fixed="right"
+          label="操作"
+          width="320"
+        >
+          <template slot-scope="scope">
+            <el-button
+              size="mini"
+              type="warning"
+              @click="prepareExam(scope.$index, scope.row)"
+            >去考试</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-row>
+    <el-row>
+      <el-pagination
+        background
+        :small="screenWidth <= 768"
+        layout="prev, pager, next"
+        :page-size="pageSize"
+        :current-page="currentPage"
+        :total="totalSize"
+        @current-change="handleCurrentChange"
+        @prev-click="handleCurrentChange"
+        @next-click="handleCurrentChange"
+      />
+    </el-row>
+
+    <el-dialog
+      title="考试提示"
+      :visible.sync="startExamDialog"
+      center
+      width="50%"
+    >
+      <el-alert
+        title="点击`开始考试`后将自动进入考试,请诚信考试,考试过程中可能会对用户行为、用户视频进行截图采样,请知悉!"
+        type="error"
+      />
+      <el-card style="margin-top: 25px">
+        <span>考试名称:</span>{{ currentSelectedExam.examName }}
+        <br>
+        <span>考试描述:</span>{{ currentSelectedExam.examDesc }}
+        <br>
+        <span>考试时长:</span>{{ currentSelectedExam.duration + '分钟' }}
+        <br>
+        <span>试卷总分:</span>{{ currentSelectedExam.totalScore }}分
+        <br>
+        <span>及格分数:</span>{{ currentSelectedExam.passScore }}分
+        <br>
+        <span>开放类型:</span> {{ currentSelectedExam.type === 2 ? '需要密码' : '完全公开' }}
+        <br>
+      </el-card>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="startExamDialog = false">返 回</el-button>
+        <el-button type="primary" @click="startExam(currentSelectedExam.examId)">开始考试</el-button>
+      </span>
+    </el-dialog>
+  </el-row>
+</template>
+
+<script>
+import {getExamPapers, getExams} from '@/api/exam'
+
+export default {
+  name: 'ExamIndex',
+  data() {
+    return {
+      // 屏幕宽度, 为了控制分页条的大小
+      screenWidth: document.body.clientWidth,
+      currentPage: 1,
+      pageSize: 20,
+      totalSize: 0,
+      dataList: [],
+      // **********************************************************************
+      searchForm: {
+        page: 1,
+        type: '1',
+        content: null
+      },
+      // 开始考试的提示框
+      startExamDialog: false,
+      // 当前选中的考试的信息
+      currentSelectedExam: {
+        examId: 114511
+      }
+    }
+  },
+  created() {
+    document.title = '试卷列表'
+    this.getData(this.searchForm)
+  },
+  methods: {
+    handleCurrentChange(pageNumber) {
+      this.currentPage = pageNumber
+      this.getData(this.searchForm)
+      // 回到顶部
+      scrollTo(0, 0)
+    },
+    getData(searchForm) {
+      this.dataList = []
+      getExams(this.currentPage).then(resp => {
+        if (resp.code === 0) {
+          this.dataList = resp.data.list
+          this.totalSize = resp.data.totalSize
+        } else {
+          this.$notify({
+            title: '提示',
+            message: resp.msg,
+            type: 'warning',
+            duration: 3000
+          })
+        }
+      }).catch(error => {
+        this.$notify({
+          title: '提示',
+          message: error.message,
+          type: 'error',
+          duration: 3000
+        })
+      })
+
+      getExamPapers(this.currentPage).then(resp => {
+        if (resp.code === 0) {
+          // this.dataList = resp.data.list
+          // this.totalSize = resp.data.totalSize
+        } else {
+          this.$notify({
+            title: '提示',
+            message: resp.msg,
+            type: 'warning',
+            duration: 3000
+          })
+        }
+      }).catch(error => {
+        this.$notify({
+          title: '提示',
+          message: error.message,
+          type: 'error',
+          duration: 3000
+        })
+      })
+    },
+    prepareExam(index, row) {
+      row.password = 12345678
+      row.type = 1
+      if (row.type === 2) {
+        this.$prompt('请提供考试密码', 'Tips', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消'
+        }).then(({ value }) => {
+          if (value === row.password) {
+            this.startExamDialog = true
+            this.currentSelectedExam = row
+          } else {
+            this.$message.error('密码错误o(╥﹏╥)o')
+          }
+        }).catch(() => {
+        })
+      } else {
+        this.startExamDialog = true
+        this.currentSelectedExam = row
+      }
+    },
+    startExam(paperId) {
+      const path = '/exam/paper/' + paperId
+      console.log(path)
+      this.$router.push('/exam/paper/' + paperId)
+    },
+    search() {
+      this.currentPage = 1
+      this.getData(this.searchForm)
+    }
+  }
+}
+</script>
+
+<style>
+</style>

+ 221 - 0
src/views/exam/ExamMarker.vue

@@ -0,0 +1,221 @@
+<template>
+  <el-row>
+    <el-row>
+      <el-form :inline="true" :model="searchForm" class="demo-form-inline">
+        <el-form-item>
+          <el-select v-model="searchForm.type" placeholder="查询类型">
+            <el-option label="稿件标题" value="1" />
+            <el-option label="用户ID" value="2" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-input v-model="searchForm.content" placeholder="" />
+        </el-form-item>
+        <el-form-item>
+          <el-button size="mini" type="warning" @click="search">查询</el-button>
+        </el-form-item>
+      </el-form>
+      <el-table
+        :data="dataList"
+        border
+        style="width: 100%"
+      >
+        <el-table-column
+          fixed="left"
+          label="No"
+          type="index"
+        />
+        <el-table-column
+          prop="examName"
+          label="考试名称"
+          width="150"
+        />
+        <el-table-column
+          prop="type"
+          label="考试类型"
+          width="90"
+        />
+        <el-table-column
+          prop="startTime"
+          label="考试时间"
+          width="150"
+        />
+        <el-table-column
+          prop="duration"
+          label="考试时长(分钟)"
+          width="150"
+        />
+        <el-table-column
+          prop="totalScore"
+          label="考试总分"
+        />
+        <el-table-column
+          prop="passScore"
+          label="及格分数"
+        />
+        <el-table-column
+          fixed="right"
+          label="操作"
+          width="320"
+        >
+          <template slot-scope="scope">
+            <el-button
+              size="mini"
+              type="warning"
+              @click="prepareExam(scope.$index, scope.row)"
+            >阅卷</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-row>
+    <el-row>
+      <el-pagination
+        background
+        :small="screenWidth <= 768"
+        layout="prev, pager, next"
+        :page-size="pageSize"
+        :current-page="currentPage"
+        :total="totalSize"
+        @current-change="handleCurrentChange"
+        @prev-click="handleCurrentChange"
+        @next-click="handleCurrentChange"
+      />
+    </el-row>
+
+    <el-dialog
+      title="考试提示"
+      :visible.sync="startExamDialog"
+      center
+      width="50%"
+    >
+      <el-alert
+        title="点击`开始考试`后将自动进入考试,请诚信考试,考试过程中可能会对用户行为、用户视频进行截图采样,请知悉!"
+        type="error"
+      />
+      <el-card style="margin-top: 25px">
+        <span>考试名称:</span>{{ currentSelectedExam.examName }}
+        <br>
+        <span>考试描述:</span>{{ currentSelectedExam.examDesc }}
+        <br>
+        <span>考试时长:</span>{{ currentSelectedExam.duration + '分钟' }}
+        <br>
+        <span>试卷总分:</span>{{ currentSelectedExam.totalScore }}分
+        <br>
+        <span>及格分数:</span>{{ currentSelectedExam.passScore }}分
+        <br>
+        <span>开放类型:</span> {{ currentSelectedExam.type === 2 ? '需要密码' : '完全公开' }}
+        <br>
+      </el-card>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="startExamDialog = false">返 回</el-button>
+        <el-button type="primary" @click="startExam(currentSelectedExam.examId)">开始考试</el-button>
+      </span>
+    </el-dialog>
+  </el-row>
+</template>
+
+<script>
+import {getExamPapers, getExams} from '@/api/exam'
+
+export default {
+  name: 'ExamMarker',
+  data() {
+    return {
+      // 屏幕宽度, 为了控制分页条的大小
+      screenWidth: document.body.clientWidth,
+      currentPage: 1,
+      pageSize: 20,
+      totalSize: 0,
+      dataList: [],
+      // **********************************************************************
+      searchForm: {
+        page: 1,
+        type: '1',
+        content: null
+      },
+      // 开始考试的提示框
+      startExamDialog: false,
+      // 当前选中的考试的信息
+      currentSelectedExam: {
+        examId: 114511
+      }
+    }
+  },
+  created() {
+    document.title = '阅卷管理'
+    this.getData(this.searchForm)
+  },
+  methods: {
+    handleCurrentChange(pageNumber) {
+      this.currentPage = pageNumber
+      this.getData(this.searchForm)
+      // 回到顶部
+      scrollTo(0, 0)
+    },
+    getData(searchForm) {
+      this.dataList = []
+      getExams(this.currentPage).then(resp => {
+        if (resp.code === 0) {
+          this.dataList = resp.data.list
+          this.totalSize = resp.data.totalSize
+        } else {
+          this.$notify({
+            title: '提示',
+            message: resp.msg,
+            type: 'warning',
+            duration: 3000
+          })
+        }
+      }).catch(error => {
+        this.$notify({
+          title: '提示',
+          message: error.message,
+          type: 'error',
+          duration: 3000
+        })
+      })
+
+      getExamPapers(this.currentPage).then(resp => {
+        if (resp.code === 0) {
+          // this.dataList = resp.data.list
+          // this.totalSize = resp.data.totalSize
+        } else {
+          this.$notify({
+            title: '提示',
+            message: resp.msg,
+            type: 'warning',
+            duration: 3000
+          })
+        }
+      }).catch(error => {
+        this.$notify({
+          title: '提示',
+          message: error.message,
+          type: 'error',
+          duration: 3000
+        })
+      })
+    },
+    prepareExam(index, row) {
+      this.$notify({
+        title: '提示',
+        message: '阅卷',
+        type: 'info',
+        duration: 3000
+      })
+    },
+    startExam(paperId) {
+      const path = '/exam/paper/' + paperId
+      console.log(path)
+      this.$router.push('/exam/paper/' + paperId)
+    },
+    search() {
+      this.currentPage = 1
+      this.getData(this.searchForm)
+    }
+  }
+}
+</script>
+
+<style>
+</style>

+ 234 - 0
src/views/exam/ExamPaper.vue

@@ -0,0 +1,234 @@
+<template>
+  <el-row>
+    <el-row>
+      <el-form :inline="true" :model="searchForm" class="demo-form-inline">
+        <el-form-item>
+          <el-select v-model="searchForm.type" placeholder="查询类型">
+            <el-option label="稿件标题" value="1" />
+            <el-option label="用户ID" value="2" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-input v-model="searchForm.content" placeholder="" />
+        </el-form-item>
+        <el-form-item>
+          <el-button size="mini" type="warning" @click="search">查询</el-button>
+        </el-form-item>
+      </el-form>
+      <el-table
+        :data="dataList"
+        border
+        style="width: 100%"
+      >
+        <el-table-column
+          fixed="left"
+          label="No"
+          type="index"
+        />
+        <el-table-column
+          prop="examName"
+          label="考试名称"
+          width="150"
+        />
+        <el-table-column
+          prop="type"
+          label="考试类型"
+          width="90"
+        />
+        <el-table-column
+          prop="startTime"
+          label="考试时间"
+          width="150"
+        />
+        <el-table-column
+          prop="duration"
+          label="考试时长(分钟)"
+          width="150"
+        />
+        <el-table-column
+          prop="totalScore"
+          label="考试总分"
+        />
+        <el-table-column
+          prop="passScore"
+          label="及格分数"
+        />
+        <el-table-column
+          fixed="right"
+          label="操作"
+          width="320"
+        >
+          <template slot-scope="scope">
+            <el-button
+              size="mini"
+              type="warning"
+              @click="prepareExam(scope.$index, scope.row)"
+            >去考试</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-row>
+    <el-row>
+      <el-pagination
+        background
+        :small="screenWidth <= 768"
+        layout="prev, pager, next"
+        :page-size="pageSize"
+        :current-page="currentPage"
+        :total="totalSize"
+        @current-change="handleCurrentChange"
+        @prev-click="handleCurrentChange"
+        @next-click="handleCurrentChange"
+      />
+    </el-row>
+
+    <el-dialog
+      title="考试提示"
+      :visible.sync="startExamDialog"
+      center
+      width="50%"
+    >
+      <el-alert
+        title="点击`开始考试`后将自动进入考试,请诚信考试,考试过程中可能会对用户行为、用户视频进行截图采样,请知悉!"
+        type="error"
+      />
+      <el-card style="margin-top: 25px">
+        <span>考试名称:</span>{{ currentSelectedExam.examName }}
+        <br>
+        <span>考试描述:</span>{{ currentSelectedExam.examDesc }}
+        <br>
+        <span>考试时长:</span>{{ currentSelectedExam.duration + '分钟' }}
+        <br>
+        <span>试卷总分:</span>{{ currentSelectedExam.totalScore }}分
+        <br>
+        <span>及格分数:</span>{{ currentSelectedExam.passScore }}分
+        <br>
+        <span>开放类型:</span> {{ currentSelectedExam.type === 2 ? '需要密码' : '完全公开' }}
+        <br>
+      </el-card>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="startExamDialog = false">返 回</el-button>
+        <el-button type="primary" @click="startExam(currentSelectedExam.examId)">开始考试</el-button>
+      </span>
+    </el-dialog>
+  </el-row>
+</template>
+
+<script>
+import {getExamPapers, getExams} from '@/api/exam'
+
+export default {
+  name: 'ExamPaper',
+  data() {
+    return {
+      // 屏幕宽度, 为了控制分页条的大小
+      screenWidth: document.body.clientWidth,
+      currentPage: 1,
+      pageSize: 20,
+      totalSize: 0,
+      dataList: [],
+      // **********************************************************************
+      searchForm: {
+        page: 1,
+        type: '1',
+        content: null
+      },
+      // 开始考试的提示框
+      startExamDialog: false,
+      // 当前选中的考试的信息
+      currentSelectedExam: {
+        examId: 114511
+      }
+    }
+  },
+  created() {
+    document.title = '试卷管理'
+    this.getData(this.searchForm)
+  },
+  methods: {
+    handleCurrentChange(pageNumber) {
+      this.currentPage = pageNumber
+      this.getData(this.searchForm)
+      // 回到顶部
+      scrollTo(0, 0)
+    },
+    getData(searchForm) {
+      this.dataList = []
+      getExams(this.currentPage).then(resp => {
+        if (resp.code === 0) {
+          this.dataList = resp.data.list
+          this.totalSize = resp.data.totalSize
+        } else {
+          this.$notify({
+            title: '提示',
+            message: resp.msg,
+            type: 'warning',
+            duration: 3000
+          })
+        }
+      }).catch(error => {
+        this.$notify({
+          title: '提示',
+          message: error.message,
+          type: 'error',
+          duration: 3000
+        })
+      })
+
+      getExamPapers(this.currentPage).then(resp => {
+        if (resp.code === 0) {
+          // this.dataList = resp.data.list
+          // this.totalSize = resp.data.totalSize
+        } else {
+          this.$notify({
+            title: '提示',
+            message: resp.msg,
+            type: 'warning',
+            duration: 3000
+          })
+        }
+      }).catch(error => {
+        this.$notify({
+          title: '提示',
+          message: error.message,
+          type: 'error',
+          duration: 3000
+        })
+      })
+    },
+    prepareExam(index, row) {
+      row.password = 12345678
+      row.type = 1
+      if (row.type === 2) {
+        this.$prompt('请提供考试密码', 'Tips', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消'
+        }).then(({ value }) => {
+          if (value === row.password) {
+            this.startExamDialog = true
+            this.currentSelectedExam = row
+          } else {
+            this.$message.error('密码错误o(╥﹏╥)o')
+          }
+        }).catch(() => {
+        })
+      } else {
+        this.startExamDialog = true
+        this.currentSelectedExam = row
+      }
+    },
+    startExam(paperId) {
+      const path = '/exam/paper/' + paperId
+      console.log(path)
+      this.$router.push('/exam/paper/' + paperId)
+    },
+    search() {
+      this.currentPage = 1
+      this.getData(this.searchForm)
+    }
+  }
+}
+</script>
+
+<style>
+</style>

+ 1049 - 0
src/views/exam/ExamQuestion.vue

@@ -0,0 +1,1049 @@
+<template>
+  <el-container>
+    <el-header height="220">
+      <el-row>
+        <el-select v-model="queryInfo.questionType" clearable placeholder="请选择题目类型" @change="typeChange">
+          <el-option
+            v-for="item in questionType"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          />
+        </el-select>
+
+        <el-select
+          v-model="queryInfo.questionBank"
+          clearable
+          placeholder="请选择题库"
+          style="margin-left: 5px"
+          @change="bankChange"
+        >
+          <el-option
+            v-for="item in allBank"
+            :key="item.id"
+            :label="item.bankName"
+            :value="item.bankName"
+          />
+        </el-select>
+
+        <el-input
+          v-model="queryInfo.questionContent"
+          placeholder="题目内容"
+          style="margin-left: 5px;width: 220px"
+          prefix-icon="el-icon-search"
+          @blur="contentChange"
+        />
+      </el-row>
+
+      <el-row style="margin-top: 10px">
+        <el-button type="primary" icon="el-icon-plus" @click="addQuTableVisible = true">添加</el-button>
+      </el-row>
+    </el-header>
+    <el-main>
+      <el-table
+        :data="dataList"
+        border
+        style="width: 100%"
+      >
+        <el-table-column
+          fixed="left"
+          label="No"
+          type="index"
+        />
+        <el-table-column
+          prop="examName"
+          label="题目类型"
+          width="150"
+        />
+        <el-table-column
+          prop="type"
+          label="题目内容"
+          width="90"
+        />
+        <el-table-column
+          prop="题目难度"
+          label="考试时间"
+          width="150"
+        />
+        <el-table-column
+          prop="duration"
+          label="所属科目"
+          width="150"
+        />
+        <el-table-column
+          prop="totalScore"
+          label="创建人"
+        />
+        <el-table-column
+          prop="passScore"
+          label="创建时间"
+        />
+      </el-table>
+    </el-main>
+    <el-row>
+      <el-pagination
+        background
+        :small="screenWidth <= 768"
+        layout="prev, pager, next"
+        :page-size="pageSize"
+        :current-page="currentPage"
+        :total="totalSize"
+        @current-change="handleCurrentChange"
+        @prev-click="handleCurrentChange"
+        @next-click="handleCurrentChange"
+      />
+    </el-row>
+
+    <el-dialog title="更新题目" :visible.sync="updateQuTableVisible" width="50%" center>
+      <el-card>
+
+        <el-form :model="updateQuForm" ref="updateQuForm" :rules="updateQuFormRules">
+
+          <el-form-item label="题目类型" label-width="120px" prop="questionType">
+            <el-select v-model="updateQuForm.questionType" disabled @change="updateQuForm.answer =  []"
+                       placeholder="请选择">
+              <el-option
+                v-for="item in questionType"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id">
+              </el-option>
+            </el-select>
+          </el-form-item>
+
+          <el-form-item label="难度等级" label-width="120px" prop="questionLevel">
+            <el-select v-model="updateQuForm.questionLevel" placeholder="请选择">
+              <el-option :value="parseInt(1)" label="简单"></el-option>
+              <el-option :value="parseInt(2)" label="中等"></el-option>
+              <el-option :value="parseInt(3)" label="困难"></el-option>
+            </el-select>
+          </el-form-item>
+
+
+          <el-form-item label="归属题库" label-width="120px" prop="bankId">
+            <el-select multiple v-model="updateQuForm.bankId" placeholder="请选择">
+              <el-option v-for="item in allBank" :key="item.bankId"
+                         :label="item.bankName" :value="item.bankId"></el-option>
+            </el-select>
+          </el-form-item>
+
+
+          <el-form-item label="题目内容" label-width="120px" prop="questionContent">
+            <el-input v-model="updateQuForm.questionContent" style="margin-left: 5px" type="textarea"
+                      :rows="2"></el-input>
+          </el-form-item>
+
+          <el-form-item label="题目图片" label-width="120px">
+            <el-upload
+              :action="uploadImageUrl + '/teacher/uploadQuestionImage'"
+              :on-preview="uploadPreview"
+              :on-remove="handleUpdateRemove"
+              :headers="headers"
+              :before-upload="beforeAvatarUpload"
+              list-type="picture"
+              :on-success="updateUploadImgSuccess"
+              name="file">
+              <el-button size="small" type="primary">点击上传</el-button>
+              <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过10M</div>
+            </el-upload>
+          </el-form-item>
+
+          <el-form-item label="整题解析" label-width="120px" prop="analysis">
+            <el-input v-model="updateQuForm.analysis" style="margin-left: 5px" type="textarea"
+                      :rows="2"></el-input>
+          </el-form-item>
+
+          <el-button v-if="updateQuForm.questionType!==4" type="primary" plain size="small" icon="el-icon-plus"
+                     style="margin-left: 40px" @click="addUpdateAnswer">
+            添加
+          </el-button>
+
+          <!--存放答案表单的信息-->
+          <el-form-item prop="answer" v-if="updateQuForm.questionType !== 4">
+            <el-table :data="updateQuForm.answer" border style="width: 96%;margin-left: 40px;margin-top: 10px">
+
+              <el-table-column label="是否答案" width="80" align="center">
+                <template slot-scope="scope">
+                  <el-checkbox v-model="scope.row.isTrue" @change="checked=>checkUpdateAnswer(checked,scope.row.id)">答案
+                  </el-checkbox>
+                </template>
+              </el-table-column>
+
+              <el-table-column label="答案图片">
+                <template slot-scope="scope">
+                  <el-upload id="answerUpload" :limit="1"
+                             :action="uploadImageUrl + '/teacher/uploadQuestionImage'"
+                             :on-preview="uploadPreview"
+                             :on-remove="handleUpdateAnswerRemove"
+                             :headers="headers"
+                             :before-upload="beforeAvatarUpload"
+                             list-type="picture"
+                             :on-success="(res) => { return uploadUpdateAnswerImgSuccess(res,scope.row.id)}"
+                             name="file">
+                    <el-button size="small" type="primary">点击上传</el-button>
+                  </el-upload>
+                </template>
+
+              </el-table-column>
+
+              <el-table-column label="答案内容">
+                <template slot-scope="scope">
+                  <el-input v-model="scope.row.answer" style="margin-left: 5px" type="textarea"
+                            :rows="2"></el-input>
+                </template>
+              </el-table-column>
+
+              <el-table-column label="答案解析">
+                <template slot-scope="scope">
+                  <el-input v-model="scope.row.analysis" style="margin-left: 5px" type="textarea"
+                            :rows="2"></el-input>
+                </template>
+              </el-table-column>
+
+              <el-table-column label="操作" width="80" align="center">
+                <template slot-scope="scope">
+                  <el-button type="danger" icon="el-icon-delete" circle
+                             @click="delUpdateAnswer(scope.row.id)"></el-button>
+                </template>
+              </el-table-column>
+
+            </el-table>
+          </el-form-item>
+
+        </el-form>
+
+      </el-card>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="updateQuTableVisible = false">取 消</el-button>
+        <el-button type="primary" @click="updateQuestion">确 定</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog title="新增题目" :visible.sync="addQuTableVisible" width="50%" @close="resetAddQuForm" center>
+
+      <el-card>
+
+        <el-form :model="addQuForm" ref="addQuForm" :rules="addQuFormRules">
+
+          <el-form-item label="题目类型" label-width="120px" prop="questionType">
+            <el-select v-model="addQuForm.questionType" @change="addQuForm.answer =  []" placeholder="请选择">
+              <el-option
+                v-for="item in questionType"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id">
+              </el-option>
+            </el-select>
+          </el-form-item>
+
+          <el-form-item label="难度等级" label-width="120px" prop="questionLevel">
+            <el-select v-model="addQuForm.questionLevel" placeholder="请选择">
+              <el-option :value="parseInt(1)" label="简单"></el-option>
+              <el-option :value="parseInt(2)" label="中等"></el-option>
+              <el-option :value="parseInt(3)" label="困难"></el-option>
+            </el-select>
+          </el-form-item>
+
+
+          <el-form-item label="归属题库" label-width="120px" prop="bankId">
+            <el-select multiple v-model="addQuForm.bankId" placeholder="请选择">
+              <el-option v-for="item in allBank" :key="item.bankId"
+                         :label="item.bankName" :value="item.bankId"></el-option>
+            </el-select>
+          </el-form-item>
+
+
+          <el-form-item label="题目内容" label-width="120px" prop="questionContent">
+            <el-input v-model="addQuForm.questionContent" style="margin-left: 5px" type="textarea"
+                      :rows="2"></el-input>
+          </el-form-item>
+
+          <el-form-item label="题目图片" label-width="120px" prop="image">
+            <el-upload
+              :action="uploadImageUrl + '/teacher/uploadQuestionImage'"
+              :on-preview="uploadPreview"
+              :on-remove="handleRemove"
+              :headers="headers"
+              :before-upload="beforeAvatarUpload"
+              list-type="picture"
+              :on-success="uploadImgSuccess"
+              name="file">
+              <el-button size="small" type="primary">点击上传</el-button>
+              <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过10M</div>
+            </el-upload>
+          </el-form-item>
+
+          <el-form-item label="整题解析" label-width="120px" prop="analysis">
+            <el-input v-model="addQuForm.analysis" style="margin-left: 5px" type="textarea"
+                      :rows="2"></el-input>
+          </el-form-item>
+
+          <el-button v-if="addQuForm.questionType!==4" type="primary" plain size="small" icon="el-icon-plus"
+                     style="margin-left: 40px" @click="addAnswer">
+            添加
+          </el-button>
+
+          <!--存放答案表单的信息-->
+          <el-form-item prop="answer" v-if="addQuForm.questionType!==4">
+            <el-table :data="addQuForm.answer" border style="width: 96%;margin-left: 40px;margin-top: 10px">
+
+              <el-table-column label="是否答案" width="80" align="center">
+                <template slot-scope="scope">
+                  <el-checkbox v-model="scope.row.isTrue" @change="checked=>checkAnswer(checked,scope.row.id)">答案
+                  </el-checkbox>
+                </template>
+              </el-table-column>
+
+              <el-table-column label="答案图片">
+                <template slot-scope="scope">
+                  <el-upload :limit="1"
+                             :action="uploadImageUrl + '/teacher/uploadQuestionImage'"
+                             :on-preview="uploadPreview"
+                             :on-remove="handleAnswerRemove"
+                             :headers="headers"
+                             :before-upload="beforeAvatarUpload"
+                             list-type="picture"
+                             :on-success="(res) => { return uploadAnswerImgSuccess(res,scope.row.id)}"
+                             name="file">
+                    <el-button size="small" type="primary">点击上传</el-button>
+                  </el-upload>
+                </template>
+
+              </el-table-column>
+
+              <el-table-column label="答案内容">
+                <template slot-scope="scope">
+                  <el-input v-model="scope.row.answer" style="margin-left: 5px" type="textarea"
+                            :rows="2"></el-input>
+                </template>
+              </el-table-column>
+
+              <el-table-column label="答案解析">
+                <template slot-scope="scope">
+                  <el-input v-model="scope.row.analysis" style="margin-left: 5px" type="textarea"
+                            :rows="2"></el-input>
+                </template>
+              </el-table-column>
+
+              <el-table-column label="操作" width="80" align="center">
+                <template slot-scope="scope">
+                  <el-button type="danger" icon="el-icon-delete" circle @click="delAnswer(scope.row.id)"></el-button>
+                </template>
+              </el-table-column>
+
+            </el-table>
+          </el-form-item>
+
+        </el-form>
+
+      </el-card>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="addQuTableVisible = false">取 消</el-button>
+        <el-button type="primary" @click="addQuestion">确 定</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog title="加入题库" :visible.sync="addTableVisible" width="30%" @close="resetAddForm"
+               center>
+
+      <el-form :model="addForm" :rules="addFormRules" ref="addForm">
+
+        <el-form-item label="题库名称" label-width="120px" prop="bankId">
+
+          <el-select multiple v-model="addForm.bankId" placeholder="请选择题库">
+            <el-option v-for="item in allBank" :key="item.bankId"
+                       :label="item.bankName" :value="item.bankId"></el-option>
+          </el-select>
+
+        </el-form-item>
+      </el-form>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="addTableVisible = false">取 消</el-button>
+        <el-button type="primary" @click="addBank">确 定</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog title="从题库中移除" :visible.sync="removeTableVisible" width="30%" @close="resetRemoveForm"
+               center>
+
+      <el-form :model="removeForm" :rules="removeFormRules" ref="removeForm">
+
+        <el-form-item label="题库名称" label-width="120px" prop="bankId">
+
+          <el-select multiple v-model="removeForm.bankId" placeholder="请选择题库">
+
+            <el-option v-for="item in allBank" :key="item.bankId"
+                       :label="item.bankName" :value="item.bankId"></el-option>
+          </el-select>
+
+        </el-form-item>
+      </el-form>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="removeTableVisible = false">取 消</el-button>
+        <el-button type="primary" @click="removeBank">确 定</el-button>
+      </div>
+    </el-dialog>
+
+    <!--图片回显-->
+    <el-dialog :visible.sync="backShowImgVisible" @close="backShowImgVisible = false">
+      <img style="width: 100%" :src="backShowImgUrl" alt="">
+    </el-dialog>
+  </el-container>
+</template>
+
+<script>
+import { getExamPapers, getExams } from '@/api/exam'
+import { validFormAndInvoke } from '@/utils/util'
+
+export default {
+  name: 'ExamQuestion',
+  data() {
+    return {
+      // 屏幕宽度, 为了控制分页条的大小
+      screenWidth: document.body.clientWidth,
+      currentPage: 1,
+      pageSize: 20,
+      totalSize: 0,
+      dataList: [],
+      // **********************************************************************
+      // 查询用户的参数
+      queryInfo: {
+        // 题目类型下拉款所选的内容
+        'questionType': '',
+        'questionBank': '',
+        'questionContent': '',
+        'pageNo': 1,
+        'pageSize': 10
+      },
+      // 题目类型
+      questionType: [
+        {
+          id: 1,
+          name: '单选题'
+        },
+        {
+          id: 2,
+          name: '多选题'
+        },
+        {
+          id: 3,
+          name: '不定项选择题'
+        },
+        {
+          id: 4,
+          name: '填空题'
+        },
+        {
+          id: 5,
+          name: '判断题'
+        },
+        {
+          id: 6,
+          name: '问答题'
+        },
+        {
+          id: 7,
+          name: '理解题'
+        }
+      ],
+      // 题库信息
+      allBank: [],
+      // 题目信息
+      questionInfo: [],
+      // 题目信息表格是否加载
+      loading: true,
+      // 表格被选中的所有行
+      selectionTable: [],
+      // 表格行被选中后的所有操作方式的数据
+      optionInfo: [
+        {
+          'label': '删除',
+          'desc': 'delete'
+        },
+        {
+          'label': '加入题库',
+          'desc': 'add'
+        },
+        {
+          'label': '题库中移除',
+          'desc': 'remove'
+        }
+      ],
+      // 表格行被选中后的数据
+      operation: '',
+      // 题目总数
+      total: 0,
+      // 是否显示加入题库对话框
+      addTableVisible: false,
+      // 是否显示移除题库对话框
+      removeTableVisible: false,
+      // 是否显示添加题目的对话框
+      addQuTableVisible: false,
+      // 添加题库的表单信息
+      addForm: {
+        bankId: ''
+      },
+      removeForm: {
+        bankId: ''
+      },
+      // 添加题库表单的验证
+      addFormRules: {
+        bankId: [
+          {
+            required: true,
+            message: '请选择需要添加进的题库',
+            trigger: 'blur'
+          }
+        ]
+      },
+      // 移除题库表单的验证
+      removeFormRules: {
+        bankId: [
+          {
+            required: true,
+            message: '请选择需要移除的题库',
+            trigger: 'blur'
+          }
+        ]
+      },
+      // 添加题目的表单信息
+      addQuForm: {
+        questionType: 1,
+        questionLevel: 1,
+        bankId: '',
+        questionContent: '',
+        images: [],
+        analysis: '',
+        createPerson: localStorage.getItem('username'),
+        // 答案对象
+        answer: []
+      },
+      // 添加题目表单的验证规则
+      addQuFormRules: {
+        questionType: [
+          {
+            required: true,
+            message: '请选择问题类型',
+            trigger: 'blur'
+          }
+        ],
+        questionLevel: [
+          {
+            required: true,
+            message: '请选择问题难度',
+            trigger: 'blur'
+          }
+        ],
+        bankId: [
+          {
+            required: true,
+            message: '请选择题库',
+            trigger: 'blur'
+          }
+        ],
+        questionContent: [
+          {
+            required: true,
+            message: '请输入题库内容',
+            trigger: 'blur'
+          }
+        ]
+      },
+      // 更新题目表单的验证规则
+      updateQuFormRules: {
+        questionType: [
+          {
+            required: true,
+            message: '请选择问题类型',
+            trigger: 'blur'
+          }
+        ],
+        questionLevel: [
+          {
+            required: true,
+            message: '请选择问题难度',
+            trigger: 'blur'
+          }
+        ],
+        bankId: [
+          {
+            required: true,
+            message: '请选择题库',
+            trigger: 'blur'
+          }
+        ],
+        questionContent: [
+          {
+            required: true,
+            message: '请输入题库内容',
+            trigger: 'blur'
+          }
+        ]
+      },
+      // 图片回显的样式
+      backShowImgVisible: false,
+      // 图片回显的图片地址
+      backShowImgUrl: '',
+      // 更新题目的数据信息
+      updateQuForm: {
+        questionId: '',
+        questionType: 1,
+        questionLevel: 1,
+        bankId: '',
+        questionContent: '',
+        images: [],
+        analysis: '',
+        createPerson: localStorage.getItem('username'),
+        // 答案对象
+        answer: []
+      },
+      // 是否显示更新题目的对话框
+      updateQuTableVisible: false
+    }
+  },
+  created() {
+    document.title = '题库管理'
+    this.getData(this.searchForm)
+  },
+  methods: {
+    getData(searchForm) {
+      this.dataList = []
+      getExams(this.currentPage).then(resp => {
+        if (resp.code === 0) {
+          this.dataList = resp.data.list
+          this.totalSize = resp.data.totalSize
+        } else {
+          this.$notify({
+            title: '提示',
+            message: resp.msg,
+            type: 'warning',
+            duration: 3000
+          })
+        }
+      }).catch(error => {
+        this.$notify({
+          title: '提示',
+          message: error.message,
+          type: 'error',
+          duration: 3000
+        })
+      })
+
+      getExamPapers(this.currentPage).then(resp => {
+        if (resp.code === 0) {
+          // this.dataList = resp.data.list
+          // this.totalSize = resp.data.totalSize
+        } else {
+          this.$notify({
+            title: '提示',
+            message: resp.msg,
+            type: 'warning',
+            duration: 3000
+          })
+        }
+      }).catch(error => {
+        this.$notify({
+          title: '提示',
+          message: error.message,
+          type: 'error',
+          duration: 3000
+        })
+      })
+    },
+    // 获取所有的题库信息
+    getQuestionBankInfo() {
+      questionBank.getQuestionBank().then((resp) => {
+        if (resp.code === 200) {
+          this.allBank = resp.data
+        }
+      })
+    },
+    // 题目类型变化
+    typeChange(val) {
+      this.queryInfo.questionType = val
+      this.getQuestionInfo()
+    },
+    // 题库变化
+    bankChange(val) {
+      this.queryInfo.questionBank = val
+      this.getQuestionInfo()
+    },
+    // 题目名字筛选
+    contentChange() {
+      // 发送查询题目总数的请求
+      this.getQuestionInfo()
+    },
+    // 获取题目信息
+    getQuestionInfo() {
+      getQuestion(this.queryInfo).then((resp) => {
+        if (resp.code === 200) {
+          this.questionInfo = resp.data.data
+          this.total = resp.data.total
+          this.loading = false
+        }
+      })
+    },
+    // 处理表格被选中
+    handleTableSectionChange(val) {
+      this.selectionTable = val
+    },
+    // 处理操作选择框的变化
+    operationChange(val) {
+      // 清空上一次的选择
+      this.operation = ''
+
+      const questionIds = []
+      if (val === 'delete') {
+        this.selectionTable.map(item => {
+          questionIds.push(item.id)
+        })
+        // 发起删除请求
+        deleteQuestion({ 'questionIds': questionIds.join(',') }).then(resp => {
+          if (resp.code === 200) {
+            this.$notify({
+              title: 'Tips',
+              message: '删除成功',
+              type: 'success',
+              duration: 2000
+            })
+            this.getQuestionInfo()
+          }
+        })
+      } else if (val === 'add') {
+        this.addTableVisible = true
+      } else if (val === 'remove') {
+        this.removeTableVisible = true
+      }
+    },
+    // 分页页面大小改变
+    handleSizeChange(val) {
+      this.queryInfo.pageSize = val
+      this.getQuestionInfo()
+    },
+    // 分页插件的页数
+    handleCurrentChange(val) {
+      this.queryInfo.pageNo = val
+      this.getQuestionInfo()
+    },
+    // 表单信息重置
+    resetAddForm() {
+      // 清空表格数据
+      this.$refs['addForm'].resetFields()
+    },
+    resetRemoveForm() {
+      // 清空表格数据
+      this.$refs['removeForm'].resetFields()
+    },
+    resetAddQuForm() {
+      this.$refs['addQuForm'].resetFields()
+    },
+    // 提交加入题库的表单信息
+    addBank() {
+      validFormAndInvoke(this.$refs['addForm'], () => {
+        const questionIds = []
+        const banks = this.addForm.bankId
+        // 将表格选中的数据中的问题id加入进去
+        this.selectionTable.map(item => {
+          questionIds.push(item.id)
+        })
+        questionBank.addBankQuestion({
+          'questionIds': questionIds.join(','),
+          'banks': banks.join(',')
+        }).then((resp) => {
+          if (resp.code === 200) {
+            this.getQuestionInfo()
+            this.$notify({
+              title: 'Tips',
+              message: resp.message,
+              type: 'success',
+              duration: 2000
+            })
+          }
+          this.addTableVisible = false
+        })
+      }, '请选择需要加入进的题库')
+    },
+    // 提交移除题库的表单信息
+    removeBank() {
+      validFormAndInvoke(this.$refs['removeForm'], () => {
+        const questionIds = []
+        const banks = this.removeForm.bankId
+        // 将表格选中的数据中的问题id加入进去
+        this.selectionTable.map(item => {
+          questionIds.push(item.id)
+        })
+        // 发起移除请求
+        questionBank.removeBankQuestion({
+          'questionIds': questionIds.join(','),
+          'banks': banks.join(',')
+        }).then((resp) => {
+          if (resp.code === 200) {
+            this.getQuestionInfo()
+            this.$notify({
+              title: 'Tips',
+              message: resp.message,
+              type: 'success',
+              duration: 2000
+            })
+          }
+          this.removeTableVisible = false
+        })
+      }, '请选择需要移除的题库')
+    },
+    // 新增题目上传后 点击图片的回显
+    uploadPreview(file) {
+      this.backShowImgUrl = file.response.data
+      this.backShowImgVisible = true
+    },
+    // 新增题目中的上传图片的移除
+    handleRemove(file, fileList) {
+      this.addQuForm.images.map((item, index) => { // 移除图片在表单中的数据
+        if (item === file.response.data) this.addQuForm.images.splice(index, 1)
+      })
+    },
+    // 更新题目中的上传图片的移除
+    handleUpdateRemove(file, fileList) {
+      this.updateQuForm.images.map((item, index) => { // 移除图片在表单中的数据
+        if (item === file.response.data) this.updateQuForm.images.splice(index, 1)
+      })
+    },
+    // 新增题目中的上传图片的格式大小限制
+    beforeAvatarUpload(file) {
+      const isImg = file.type === 'image/jpeg' ||
+        file.type === 'image/png' ||
+        file.type === 'image/jpg'
+      const isLt = file.size / 1024 / 1024 < 10
+
+      if (!isImg) {
+        this.$message.error('上传图片只能是 JPG/PNG 格式!')
+      }
+
+      if (!isLt) {
+        this.$message.error('上传头像图片大小不能超过 10MB!')
+      }
+      return isImg && isLt
+    },
+    // 新增题目中的上传图片成功后的钩子函数
+    uploadImgSuccess(response, file, fileList) {
+      this.addQuForm.images.push(response.data)
+    },
+    // 更新题目中的上传图片成功后的钩子函数
+    updateUploadImgSuccess(response, file, fileList) {
+      this.updateQuForm.images.push(response.data)
+    },
+    // 新增题目中的新增答案填写框
+    addAnswer() {
+      this.addQuForm.answer.push({
+        id: this.addQuForm.answer.length,
+        isTrue: false,
+        answer: '',
+        images: [],
+        analysis: ''
+      })
+    },
+    // 更新时新增题目中的新增答案填写框
+    addUpdateAnswer() {
+      this.updateQuForm.answer.push({
+        id: this.updateQuForm.answer.length,
+        isTrue: false,
+        answer: '',
+        images: [],
+        analysis: ''
+      })
+    },
+    // 新增题目中的删除答案填写框
+    delAnswer(id) { // 当前答案的id
+      this.addQuForm.answer.map((item, index) => {
+        if (item.id === id) this.addQuForm.answer.splice(index, 1)
+      })
+    },
+    // 新增题目中的删除答案填写框
+    delUpdateAnswer(id) { // 当前答案的id
+      this.updateQuForm.answer.map((item, index) => {
+        if (item.id === id) this.updateQuForm.answer.splice(index, 1)
+      })
+    },
+    // 答案上传照片了
+    uploadAnswerImgSuccess(response, id) {
+      this.addQuForm.answer[id].images.push(response.data)
+    },
+    // 更新的答案上传图片了
+    uploadUpdateAnswerImgSuccess(response, id) {
+      this.updateQuForm.answer[id].images.push(response.data)
+    },
+    // 答案上传成功后删除
+    handleAnswerRemove(file) {
+      this.addQuForm.answer.images.map((item, index) => { // 移除图片在表单中的数据
+        if (item === file.response.data) this.addQuForm.images.splice(index, 1)
+      })
+    },
+    // 更新的时候答案上传成功后删除
+    handleUpdateAnswerRemove(file) {
+      this.updateQuForm.answer.images.map((item, index) => { // 移除图片在表单中的数据
+        if (item === file.response.data) this.updateQuForm.images.splice(index, 1)
+      })
+    },
+    // 选择正确答案的按钮变化事件
+    checkAnswer(checked, id) {
+      if (checked) {
+        if (this.addQuForm.questionType === 1 || this.addQuForm.questionType === 3) { // 单选或者判断
+          // 当前已经有一个正确的选择了
+          this.addQuForm.answer.map(item => {
+            item.isTrue = false
+          })
+          this.addQuForm.answer.map(item => {
+            if (item.id === id) item.isTrue = true
+          })
+        } else { // 多选 可以有多个答案
+          this.addQuForm.answer.map(item => {
+            if (item.id === id) item.isTrue = true
+          })
+        }
+      } else {
+        this.addQuForm.answer.map(item => {
+          if (item.id === id) item.isTrue = false
+        })
+      }
+    },
+    // 更新时选择正确答案的按钮变化事件
+    checkUpdateAnswer(checked, id) {
+      if (checked) {
+        if (this.updateQuForm.questionType === 1 || this.updateQuForm.questionType === 3) { // 单选或者判断
+          // 当前已经有一个正确的选择了
+          this.updateQuForm.answer.map(item => {
+            item.isTrue = false
+          })
+          this.updateQuForm.answer.map(item => {
+            if (item.id === id) item.isTrue = true
+          })
+        } else { // 多选 可以有多个答案
+          this.updateQuForm.answer.map(item => {
+            if (item.id === id) item.isTrue = true
+          })
+        }
+      } else {
+        this.updateQuForm.answer.map(item => {
+          if (item.id === id) item.isTrue = false
+        })
+      }
+    },
+    // 新增题目
+    addQuestion() {
+      this.$refs['addQuForm'].validate((valid) => {
+        if (valid && this.addQuForm.answer.some(item => item.isTrue) && this.addQuForm.questionType !== 4) { // 单选或者多选或者判断
+          addQuestion(this.addQuForm).then((resp) => {
+            if (resp.code === 200) {
+              this.addQuTableVisible = false
+              this.getQuestionInfo()
+              this.$notify({
+                title: 'Tips',
+                message: '新增题目成功',
+                type: 'success',
+                duration: 2000
+              })
+            }
+          })
+        } else if (valid && !this.addQuForm.answer.some(item => item.isTrue) && this.addQuForm.questionType !== 4) { // 无答案
+          this.$message.error('必须有一个答案')
+          return false
+        } else if (valid && this.addQuForm.questionType === 4) { // 简答题 无标准答案直接发请求
+          // 当是简答题的时候需要清除answer
+          this.addQuForm.answer = []
+          addQuestion(this.addQuForm).then((resp) => {
+            if (resp.code === 200) {
+              this.addQuTableVisible = false
+              this.getQuestionInfo()
+              this.$notify({
+                title: 'Tips',
+                message: '新增题目成功',
+                type: 'success',
+                duration: 2000
+              })
+            }
+          })
+        } else if (!valid) {
+          this.$message.error('请填写必要信息')
+          return false
+        }
+      })
+    },
+    // 更新题目
+    updateQu(id) {
+      getQuestionById(id).then((resp) => {
+        if (resp.code === 200) {
+          if (resp.data.questionType !== 4) {
+            resp.data.answer.map(item => {
+              item.isTrue = item.isTrue === 'true'
+            })
+          }
+          this.updateQuForm = resp.data
+          // 处理图片那个参数是个数组
+          if (this.updateQuForm.images === null) this.updateQuForm.images = []
+
+          if (resp.data.questionType !== 4) {
+            this.updateQuForm.answer.map(item => {
+              if (item.images === null) {
+                item.images = []
+              }
+            })
+          }
+        }
+      })
+      this.updateQuTableVisible = true
+    },
+    // 提交更新表单
+    updateQuestion() {
+      this.$refs['updateQuForm'].validate((valid) => {
+        if (valid && this.updateQuForm.questionType !== 4 && this.updateQuForm.answer.some(item => item.isTrue)) { // 单选或者多选或者判断
+          // 保证答案的图片只有一张
+          this.updateQuForm.answer.map(item => {
+            if (item.images.length > 1) {
+              item.images.splice(0, item.images.length - 1)
+            }
+          })
+          updateQuestion(this.updateQuForm).then((resp) => {
+            if (resp.code === 200) {
+              this.updateQuTableVisible = false
+              this.getQuestionInfo()
+              this.$notify({
+                title: 'Tips',
+                message: '更新题目成功',
+                type: 'success',
+                duration: 2000
+              })
+            }
+          })
+        } else if (valid && this.updateQuForm.questionType !== 4 && !this.updateQuForm.answer.some(item => item.isTrue)) { // 无答案
+          this.$message.error('必须有一个答案')
+          return false
+        } else if (valid && this.updateQuForm.questionType === 4) { // 简答题 无标准答案直接发请求
+          // 当是简答题的时候需要清除answer
+          this.addQuForm.answer = []
+          updateQuestion(this.updateQuForm).then((resp) => {
+            if (resp.code === 200) {
+              this.updateQuTableVisible = false
+              this.getQuestionInfo()
+              this.$notify({
+                title: 'Tips',
+                message: '更新题目成功',
+                type: 'success',
+                duration: 2000
+              })
+            }
+          })
+        } else if (!valid) {
+          this.$message.error('请填写必要信息')
+          return false
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style>
+</style>

+ 321 - 0
src/views/exam/ExamResult.vue

@@ -0,0 +1,321 @@
+<template>
+  <el-container>
+    <el-header height="150">
+      <el-card style="height: 150px">
+        <span class="examName">{{ examInfo.examName }}</span>
+        <span class="examTime">{{ examRecord.examTime }}</span>
+
+        <el-row style="margin-top: 55px;">
+          <el-tooltip class="item" effect="dark" content="包括(单选,多选,判断题)" placement="top-start">
+            <span style="font-weight: 800;font-size: 17px">
+              逻辑题得分: {{ examRecord.logicScore }}分</span>
+          </el-tooltip>
+
+          <el-tooltip class="item" effect="dark" content="简答题与逻辑题" placement="top-start">
+            <span style="float: right;font-weight: 800;font-size: 17px">
+              总分: {{ examInfo.totalScore }}分</span>
+          </el-tooltip>
+        </el-row>
+
+      </el-card>
+
+    </el-header>
+
+    <el-main>
+      <el-card>
+        <!--题目信息-->
+        <div v-for="(item,index) in questionInfo" :key="index" style="margin-top: 15px">
+          <div>
+            <i class="num">{{ index + 1 }}</i>
+            <span v-if="item.questionType === 1">【单选题】</span>
+            <span v-else-if="item.questionType === 2">【多选题】</span>
+            <span v-else-if="item.questionType === 3">【判断题】</span>
+            <span v-else>【简答题】</span>
+            <span>{{ item.questionContent }}:</span>
+            <span style="color: red;font-style: italic;font-weight: 400;">&nbsp;{{
+              questionScore.get(String(item.questionId))
+            }}分</span>
+          </div>
+          <!--题目中的配图-->
+          <img
+            v-for="url in item.images"
+            :src="url"
+            title="点击查看大图"
+            alt="题目图片"
+            style="width: 100px;height: 100px;cursor: pointer"
+            @click="showBigImg(url)"
+          >
+
+          <!--单选 和 判断 的答案列表-->
+          <div
+            v-show="item.questionType === 1 || item.questionType === 3"
+            style="margin-top: 25px"
+          >
+            <div class="el-radio-group">
+              <label
+                v-for="(i2,index2) in item.answer"
+                :class="String(index2) === userAnswer[index] && i2.isTrue === 'true' ?
+                  'activeAndTrue' : String(index2) === userAnswer[index] ? 'active' :
+                    i2.isTrue === 'true' ? 'true' : ''"
+              >
+                <span>{{ optionName[index2] + '、' + i2.answer }}</span>
+                <img
+                  v-for="i3 in i2.images"
+                  v-if="i2.images !== null"
+                  style="position: absolute;left:100%;top:50%;transform: translateY(-50%);
+                  width: 40px;height: 40px;float: right;cursor: pointer;"
+                  title="点击查看大图"
+                  :src="i3"
+                  alt=""
+                  @mouseover="showBigImg(i3)"
+                >
+              </label>
+            </div>
+          </div>
+
+          <!--多选的答案列表-->
+          <div v-show="item.questionType === 2" style="margin-top: 25px">
+            <div class="el-radio-group">
+              <label
+                v-for="(i2,index2) in item.answer"
+                :class="(userAnswer[index]+'').indexOf(index2+'') !== -1 && i2.isTrue === 'true'
+                  ? 'activeAndTrue' : (userAnswer[index]+'').indexOf(index2+'') !== -1 ? 'active' :
+                    i2.isTrue === 'true' ? 'true' : ''"
+              >
+                <span>{{ optionName[index] + '、' + i2.answer }}</span>
+                <img
+                  v-for="i3 in i2.images"
+                  v-if="i2.images !== null"
+                  style="position: absolute;left:100%;top:50%;transform: translateY(-50%);
+                  width: 40px;height: 40px;float: right;cursor: pointer;"
+                  title="点击查看大图"
+                  :src="i3"
+                  alt=""
+                  @mouseover="showBigImg(i3)"
+                >
+              </label>
+            </div>
+          </div>
+
+          <!--简答题的答案-->
+          <div v-show="item.questionType === 4" style="margin-top: 25px">
+            <div class="ques-analysis">
+              <h3 style="font-weight: 400">我的回答:</h3>
+              <p style="font-weight: 400;color: orange"> {{ userAnswer[index] }} </p>
+            </div>
+          </div>
+
+        </div>
+
+      </el-card>
+    </el-main>
+
+    <!--图片回显-->
+    <el-dialog :visible.sync="bigImgDialog" @close="bigImgDialog = false">
+      <img style="width: 100%" :src="bigImgUrl">
+    </el-dialog>
+  </el-container>
+</template>
+
+<script>
+import { getExamInfoById, getExamPaperScore, getExamResult, getQuestionByIds } from '@/api/exam'
+
+export default {
+  name: 'ExamResult',
+  data() {
+    return {
+      // 考试记录信息
+      examRecord: {},
+      // 考试的信息
+      examInfo: {},
+      // 当前考试的题目
+      questionInfo: [],
+      // 页面加载
+      loading: {},
+      // 答案的选项名abcd数据
+      optionName: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
+      // 图片回显的url
+      bigImgUrl: '',
+      // 图片回显的对话框是否显示
+      bigImgDialog: false,
+      // 用户回答的答案
+      userAnswer: [],
+      // 单题的分值
+      questionScore: new Map()
+    }
+  },
+  created() {
+    document.title = '考试结果'
+    this.getExamRecord()
+    // 页面数据加载的等待状态栏
+    /* this.loading = this.$Loading.service({
+      body: true,
+      lock: true,
+      text: '数据拼命加载中,(*╹▽╹*)',
+      spinner: 'el-icon-loading'
+    })*/
+  },
+  methods: {
+    // 查询用户当时考试的信息
+    async getExamRecord() {
+      const examId = this.$route.params.examId
+      await getExamResult(examId).then((resp) => {
+        if (resp.code === 0) {
+          this.examRecord = resp.data
+          this.getExamInfoById(resp.data.examId)
+          this.userAnswer = resp.data.userAnswers.split('-')
+          // 获取单题的分值
+          this.getQuestionScore(resp.data.examId)
+          // 获取所有题目信息
+          this.getQuestionInfoByIds(resp.data.questionIds)
+          // 数据加载完毕
+          // this.loading.close()
+        }
+      })
+    },
+    // 根据考试id查询考试信息
+    getExamInfoById(examId) {
+      getExamInfoById({ 'examId': examId }).then((resp) => {
+        if (resp.code === 0) this.examInfo = resp.data
+      })
+    },
+    // 根据ids查询题目信息
+    async getQuestionInfoByIds(questionIds) {
+      await getQuestionByIds({ ids: questionIds }).then((resp) => {
+        if (resp.code === 0) {
+          this.questionInfo = resp.data || []
+          // 重置问题的顺序 单选 多选 判断 简答
+          this.questionInfo = this.questionInfo.sort(function(a, b) {
+            return a.questionType - b.questionType
+          })
+        }
+      })
+    },
+    // 点击展示高清大图
+    showBigImg(url) {
+      this.bigImgUrl = url
+      this.bigImgDialog = true
+    },
+    // 根据考试id查询考试中每一题的分数
+    async getQuestionScore(examId) {
+      await getExamPaperScore(examId).then((resp) => {
+        if (resp.code === 0) {
+          // 设置单题分值给map
+          const scores = resp.data.scores.split(',')
+          resp.data.questionIds.split(',').forEach((item, index) => {
+            // this.$set(this.questionScore, item, scores[index])
+            this.questionScore.set(item, scores[index])
+          })
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+
+* {
+  font-weight: 800;
+}
+
+.el-container {
+  width: 100%;
+  height: 100%;
+}
+
+.el-container {
+  animation: leftMoveIn .7s ease-in;
+}
+
+@keyframes leftMoveIn {
+  0% {
+    transform: translateX(-100%);
+    opacity: 0;
+  }
+  100% {
+    transform: translateX(0%);
+    opacity: 1;
+  }
+}
+
+.examName {
+  color: #160f58;
+  border-bottom: 4px solid #ffd550;
+  font-size: 18px;
+  font-weight: 700;
+  padding-bottom: 10px
+}
+
+.examTime {
+  font-size: 16px;
+  color: #cbcacf;
+  margin-left: 20px;
+  font-weight: 700;
+}
+
+.el-radio-group label {
+  display: block;
+  width: 400px;
+  padding: 48px 20px 10px 20px;
+  border-radius: 4px;
+  border: 1px solid #dcdfe6;
+  margin-bottom: 10px;
+  position: relative;
+
+  span {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    font-size: 16px;
+  }
+}
+
+.num {
+  display: inline-block;
+  background: url('../../assets/img/examTitle.png') no-repeat 100% 100%;
+  background-size: contain;
+  height: 37px;
+  width: 37px;
+  line-height: 30px;
+  color: #fff;
+  font-size: 20px;
+  text-align: center;
+  margin-right: 15px;
+}
+
+/*选中的答案*/
+.active {
+  border: 1px solid #1f90ff !important;
+  opacity: .5;
+}
+
+/*  选中的答案且是正确答案*/
+.activeAndTrue {
+  border: 1px solid #1f90ff !important;
+  opacity: .5;
+  height: 15px;
+  width: 15px;
+  background-size: contain;
+  background: url('../../assets/img/true.png') no-repeat 95%;
+  position: absolute;
+  top: 0;
+  left: 0;
+}
+
+.true {
+  height: 15px;
+  width: 15px;
+  background-size: contain;
+  background: url('../../assets/img/true.png') no-repeat 95%;
+  position: absolute;
+  top: 0;
+  left: 0;
+}
+
+.ques-analysis {
+  padding: 30px 40px;
+  background: #f6f6f8;
+  margin-bottom: 70px;
+}
+</style>

+ 212 - 0
src/views/exam/ExamResults.vue

@@ -0,0 +1,212 @@
+<template>
+  <el-row>
+    <el-row>
+      <el-form :inline="true" :model="searchForm" class="demo-form-inline">
+        <el-form-item>
+          <el-select v-model="searchForm.type" placeholder="查询类型">
+            <el-option label="稿件标题" value="1" />
+            <el-option label="用户ID" value="2" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-input v-model="searchForm.content" placeholder="" />
+        </el-form-item>
+        <el-form-item>
+          <el-button size="mini" type="warning" @click="search">查询</el-button>
+        </el-form-item>
+      </el-form>
+      <el-table
+        :data="dataList"
+        border
+        style="width: 100%"
+      >
+        <el-table-column
+          fixed="left"
+          label="No"
+          type="index"
+        />
+        <el-table-column
+          prop="examName"
+          label="科目"
+        />
+        <el-table-column
+          prop="examName"
+          label="考试名称"
+        />
+        <el-table-column
+          prop="startTime"
+          label="考试时间"
+        />
+        <el-table-column
+          prop="totalScore"
+          label="总分"
+        />
+        <el-table-column
+          prop="passScore"
+          label="得分"
+        />
+        <el-table-column
+          fixed="right"
+          label="操作"
+        >
+          <template slot-scope="scope">
+            <el-button
+              size="mini"
+              type="warning"
+              @click="prepareExam(scope.$index, scope.row)"
+            >查看</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-row>
+    <el-row>
+      <el-pagination
+        background
+        :small="screenWidth <= 768"
+        layout="prev, pager, next"
+        :page-size="pageSize"
+        :current-page="currentPage"
+        :total="totalSize"
+        @current-change="handleCurrentChange"
+        @prev-click="handleCurrentChange"
+        @next-click="handleCurrentChange"
+      />
+    </el-row>
+
+    <el-dialog
+      title="考试提示"
+      :visible.sync="startExamDialog"
+      center
+      width="50%"
+    >
+      <el-alert
+        title="点击`开始考试`后将自动进入考试,请诚信考试,考试过程中可能会对用户行为、用户视频进行截图采样,请知悉!"
+        type="error"
+      />
+      <el-card style="margin-top: 25px">
+        <span>考试名称:</span>{{ currentSelectedExam.examName }}
+        <br>
+        <span>考试描述:</span>{{ currentSelectedExam.examDesc }}
+        <br>
+        <span>考试时长:</span>{{ currentSelectedExam.duration + '分钟' }}
+        <br>
+        <span>试卷总分:</span>{{ currentSelectedExam.totalScore }}分
+        <br>
+        <span>及格分数:</span>{{ currentSelectedExam.passScore }}分
+        <br>
+        <span>开放类型:</span> {{ currentSelectedExam.type === 2 ? '需要密码' : '完全公开' }}
+        <br>
+      </el-card>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="startExamDialog = false">返 回</el-button>
+        <el-button type="primary" @click="startExam(currentSelectedExam.examId)">开始考试</el-button>
+      </span>
+    </el-dialog>
+  </el-row>
+</template>
+
+<script>
+import {getExamPapers, getExams} from '@/api/exam'
+
+export default {
+  name: 'ExamResults',
+  data() {
+    return {
+      // 屏幕宽度, 为了控制分页条的大小
+      screenWidth: document.body.clientWidth,
+      currentPage: 1,
+      pageSize: 20,
+      totalSize: 0,
+      dataList: [],
+      // **********************************************************************
+      searchForm: {
+        page: 1,
+        type: '1',
+        content: null
+      },
+      // 开始考试的提示框
+      startExamDialog: false,
+      // 当前选中的考试的信息
+      currentSelectedExam: {
+        examId: 114511
+      }
+    }
+  },
+  created() {
+    document.title = '考试结果'
+    this.getData(this.searchForm)
+  },
+  methods: {
+    handleCurrentChange(pageNumber) {
+      this.currentPage = pageNumber
+      this.getData(this.searchForm)
+      // 回到顶部
+      scrollTo(0, 0)
+    },
+    getData(searchForm) {
+      this.dataList = []
+      getExams(this.currentPage).then(resp => {
+        if (resp.code === 0) {
+          this.dataList = resp.data.list
+          this.totalSize = resp.data.totalSize
+        } else {
+          this.$notify({
+            title: '提示',
+            message: resp.msg,
+            type: 'warning',
+            duration: 3000
+          })
+        }
+      }).catch(error => {
+        this.$notify({
+          title: '提示',
+          message: error.message,
+          type: 'error',
+          duration: 3000
+        })
+      })
+
+      getExamPapers(this.currentPage).then(resp => {
+        if (resp.code === 0) {
+          // this.dataList = resp.data.list
+          // this.totalSize = resp.data.totalSize
+        } else {
+          this.$notify({
+            title: '提示',
+            message: resp.msg,
+            type: 'warning',
+            duration: 3000
+          })
+        }
+      }).catch(error => {
+        this.$notify({
+          title: '提示',
+          message: error.message,
+          type: 'error',
+          duration: 3000
+        })
+      })
+    },
+    prepareExam(index, row) {
+      this.$notify({
+        title: '提示',
+        message: '查看考试结果',
+        type: 'info',
+        duration: 3000
+      })
+    },
+    startExam(paperId) {
+      const path = '/exam/paper/' + paperId
+      console.log(path)
+      this.$router.push('/exam/paper/' + paperId)
+    },
+    search() {
+      this.currentPage = 1
+      this.getData(this.searchForm)
+    }
+  }
+}
+</script>
+
+<style>
+</style>

+ 122 - 0
src/views/exam/ExamSubject.vue

@@ -0,0 +1,122 @@
+<template>
+  <el-row>
+    <el-row>
+      <el-table
+        :data="dataList"
+        border
+        style="width: 100%"
+      >
+        <el-table-column
+          fixed="left"
+          label="No"
+          type="index"
+        />
+        <el-table-column
+          prop="examName"
+          label="科目"
+        />
+        <el-table-column
+          prop="passScore"
+          label="单选题"
+        />
+        <el-table-column
+          prop="passScore"
+          label="多选题"
+        />
+        <el-table-column
+          prop="duration"
+          label="不定项选择题"
+        />
+        <el-table-column
+          prop="totalScore"
+          label="填空题"
+        />
+        <el-table-column
+          prop="passScore"
+          label="判断题"
+        />
+        <el-table-column
+          prop="passScore"
+          label="问答题"
+        />
+        <el-table-column
+          prop="passScore"
+          label="理解题"
+        />
+        <el-table-column
+          prop="passScore"
+          label="试卷"
+        />
+      </el-table>
+    </el-row>
+    <el-row>
+      <el-pagination
+        background
+        :small="screenWidth <= 768"
+        layout="prev, pager, next"
+        :page-size="pageSize"
+        :current-page="currentPage"
+        :total="totalSize"
+        @current-change="handleCurrentChange"
+        @prev-click="handleCurrentChange"
+        @next-click="handleCurrentChange"
+      />
+    </el-row>
+  </el-row>
+</template>
+
+<script>
+import {getExams} from '@/api/exam'
+
+export default {
+  name: 'ExamSubject',
+  data() {
+    return {
+      // 屏幕宽度, 为了控制分页条的大小
+      screenWidth: document.body.clientWidth,
+      currentPage: 1,
+      pageSize: 20,
+      totalSize: 0,
+      dataList: []
+    }
+  },
+  created() {
+    document.title = '科目管理'
+    this.getData()
+  },
+  methods: {
+    handleCurrentChange(pageNumber) {
+      this.currentPage = pageNumber
+      this.getData()
+      // 回到顶部
+      scrollTo(0, 0)
+    },
+    getData() {
+      this.dataList = []
+      getExams(this.currentPage).then(resp => {
+        if (resp.code === 0) {
+          this.dataList = resp.data.list
+          this.totalSize = resp.data.totalSize
+        } else {
+          this.$notify({
+            title: '提示',
+            message: resp.msg,
+            type: 'warning',
+            duration: 3000
+          })
+        }
+      }).catch(error => {
+        this.$notify({
+          title: '提示',
+          message: error.message,
+          type: 'error',
+          duration: 3000
+        })
+      })
+    }
+  }
+}
+</script>
+
+<style>
+</style>