Ver código fonte

invigilation api md

zhangjie 4 anos atrás
pai
commit
c6c4abb939
37 arquivos alterados com 1184 adições e 793 exclusões
  1. 86 28
      src/api/invigilation.js
  2. 36 12
      src/constant/constants.js
  3. 37 28
      src/features/invigilation/ExamReport/BreachDetailDialog.vue
  4. 3 3
      src/features/invigilation/ExamReport/ExamReport.vue
  5. 9 25
      src/features/invigilation/ExamReport/ExceptionDetailDialog.vue
  6. 3 3
      src/features/invigilation/ExamReport/ReportAbsent.vue
  7. 34 5
      src/features/invigilation/ExamReport/ReportBreach.vue
  8. 39 9
      src/features/invigilation/ExamReport/ReportCancalBreach.vue
  9. 27 4
      src/features/invigilation/ExamReport/ReportException.vue
  10. 45 43
      src/features/invigilation/ExamReport/ReportOverview.vue
  11. 2 2
      src/features/invigilation/ExamReport/ReportReexam.vue
  12. 29 11
      src/features/invigilation/ExamReport/ReportStatistics.vue
  13. 14 5
      src/features/invigilation/InvigilationDetail/InvigilationDetail.vue
  14. 72 96
      src/features/invigilation/OnlinePatrol/OnlinePatrol.vue
  15. 1 33
      src/features/invigilation/ProgressDetail/ProgressDetail.vue
  16. 68 42
      src/features/invigilation/RealtimeMonitoring/RealtimeMonitoring.vue
  17. 22 19
      src/features/invigilation/RealtimeMonitoring/VideoCommunication.vue
  18. 236 160
      src/features/invigilation/RealtimeMonitoring/WarningDetail.vue
  19. 95 0
      src/features/invigilation/RealtimeMonitoring/WarningTextMessageDialog.vue
  20. 1 1
      src/features/invigilation/RealtimeMonitoring/handleRollupDialog.vue
  21. 2 42
      src/features/invigilation/ReexamApply/ReexamApply.vue
  22. 12 9
      src/features/invigilation/ReexamChecked/ReexamChecked.vue
  23. 28 55
      src/features/invigilation/ReexamPending/CheckReexamDialog.vue
  24. 8 25
      src/features/invigilation/ReexamPending/ReexamPending.vue
  25. 19 24
      src/features/invigilation/StudentLogManage/StudentLogDetailDialog.vue
  26. 37 35
      src/features/invigilation/StudentLogManage/StudentLogManage.vue
  27. 41 20
      src/features/invigilation/WarningManage/WarningManage.vue
  28. 2 2
      src/features/invigilation/common/InvigilationStudent.vue
  29. 1 1
      src/features/invigilation/common/SummaryLine.vue
  30. 6 1
      src/router/index.js
  31. 80 41
      src/router/invigilation.js
  32. 18 5
      src/store/modules/invigilation.js
  33. 25 0
      src/styles/base.scss
  34. 7 0
      src/styles/element-ui-custom.scss
  35. 35 0
      src/utils/utils.js
  36. 3 3
      src/views/Layout/Layout.vue
  37. 1 1
      src/views/Layout/components/menu.js

+ 86 - 28
src/api/invigilation.js

@@ -38,6 +38,12 @@ export function monitorCallCount(examId) {
     {}
   );
 }
+export function invigilationWarningMessage(examId) {
+  return httpApp.post(
+    "/api/admin/invigilate/warn/message?" + object2QueryString({ examId }),
+    {}
+  );
+}
 
 // online-patrol
 export function patrolList(datas) {
@@ -47,9 +53,17 @@ export function patrolList(datas) {
     {}
   );
 }
+export function patrolReportList(datas) {
+  const data = pickBy(datas, (v) => v !== "");
+  return httpApp.post(
+    "/api/admin/report/patrol?" + object2QueryString(data),
+    {}
+  );
+}
 
 // 强制/手动交卷接口
 export function invigilateFinish(datas) {
+  // type: MANUAL:手动,AUTO:自动,BREACH:违纪交卷,INTERRUPT:监考强制交卷
   const data = pickBy(datas, (v) => v !== "");
   return httpApp.post("/api/admin/invigilate/finish", data);
 }
@@ -77,15 +91,8 @@ export function examPropCount(examId) {
   return httpApp.post("/api/admin/exam/prop/count?examId=" + examId);
 }
 
-export function communicationList({
-  examActivityId,
-  pageNumber = 1,
-  pageSize = 10,
-}) {
-  const data = pickBy(
-    { examActivityId, pageNumber, pageSize },
-    (v) => v !== ""
-  );
+export function communicationList(datas) {
+  const data = pickBy(datas, (v) => v !== "");
   return httpApp.post(
     "/api/admin/monitor/call/list?" + object2QueryString(data),
     {},
@@ -114,7 +121,7 @@ export function invigilationHistoryList(datas) {
   );
 }
 
-// waining-manage
+// warning-manage
 export function invigilationWarningList(datas) {
   const data = pickBy(datas, (v) => v !== "");
   return httpApp.post(
@@ -129,22 +136,15 @@ export function invigilationWarningCount(datas) {
     {}
   );
 }
-// TODO:批量处理违纪
-export function batchInvigilation(datas) {
-  const data = pickBy(datas, (v) => v !== "");
-  return Promise.resolve(data);
-  // return httpApp.post(
-  //   "/api/admin/invigilate/warn/list?" + object2QueryString(data),
-  //   {}
-  // );
-}
-// TODO:清除未阅
-export function clearInvigilationUnreadWarningList(datas) {
-  const data = pickBy(datas, (v) => v !== "");
-  return httpApp.post(
-    "/api/admin/invigilate/warn/list?" + object2QueryString(data),
-    {}
-  );
+// 清除未阅
+export function clearInvigilationUnreadWarningList({
+  approveStatus,
+  warningIds,
+}) {
+  return httpApp.post("/api/admin/invigilate/warn/save/status", {
+    approveStatus,
+    warningIds,
+  });
 }
 
 // warning-detail
@@ -171,6 +171,11 @@ export function updateBreachInfo(datas) {
 
   return httpApp.post("/api/admin/invigilate/breach", data);
 }
+// TODO:发送文字消息
+export function sendWarningMsg(datas) {
+  const data = pickBy(datas, (v) => v !== "");
+  return httpApp.post("/api/admin/invigilate/breach", data);
+}
 
 // reexam-apply
 export function reexamApplyList(datas) {
@@ -202,8 +207,13 @@ export function reexamPendingCount(datas) {
   );
 }
 export function checkReexamApply(datas) {
-  const data = pickBy(datas, (v) => v !== "");
-  return httpApp.post("/api/admin/invigilate/reexam/auditing", data);
+  return httpApp.post("/api/admin/invigilate/reexam/auditing", datas);
+}
+
+export function reexamCheckDetail(reexamId) {
+  return httpApp.post(
+    "/api/admin/invigilate/reexam/detail?reexamId=" + reexamId
+  );
 }
 
 // reexam-checked
@@ -274,3 +284,51 @@ export function reportReexamData(datas) {
     {}
   );
 }
+// report-breach
+export function reportBreachData(datas) {
+  const data = pickBy(datas, (v) => v !== "");
+  return httpApp.post(
+    "/api/admin/report/exam_breach_list?" + object2QueryString(data),
+    {}
+  );
+}
+export function reportBreachDetail(datas) {
+  const data = pickBy(datas, (v) => v !== "");
+  return httpApp.post(
+    "/api/admin/report/exam_breach_list_detail?" + object2QueryString(data),
+    {}
+  );
+}
+// report-cancel-breach
+export function reportCancelBreachData(datas) {
+  const data = pickBy(datas, (v) => v !== "");
+  return httpApp.post(
+    "/api/admin/report/exam_revoke_breach_list?" + object2QueryString(data),
+    {}
+  );
+}
+export function reportCancelBreachDetail(datas) {
+  const data = pickBy(datas, (v) => v !== "");
+  return httpApp.post(
+    "/api/admin/report/exam_revoke_breach_list_detail?" +
+      object2QueryString(data),
+    {}
+  );
+}
+
+// student-log-manage
+export function studentLogList(datas) {
+  const data = pickBy(datas, (v) => v !== "");
+  return httpApp.post(
+    "/api/admin/report/exam_student_log_list?" + object2QueryString(data),
+    {}
+  );
+}
+export function studentLogDetail(datas) {
+  const data = pickBy(datas, (v) => v !== "");
+  return httpApp.post(
+    "/api/admin/report/exam_student_log_list_detail?" +
+      object2QueryString(data),
+    {}
+  );
+}

+ 36 - 12
src/constant/constants.js

@@ -15,8 +15,8 @@ export const EXAM_STUDENT_IMPORT_TEMPLATE_DOWNLOAD_URL =
 export const STUDENT_FINISH_EXAM_TYPE = {
   MANUAL: "手动收卷",
   AUTO: "正常交卷",
-  BREACH: "强制收卷",
-  INTERRUPT: "系统清卷",
+  BREACH: "违纪交卷",
+  INTERRUPT: "监考强制交卷",
 };
 // 是 / 否
 export const BOOLEAN_TYPE = {
@@ -46,19 +46,43 @@ export const STUDENT_BEHAVIOR_STATUS = {
 };
 // 违规类型
 export const BREACH_REASON_TYPE = {
-  0: "夹带抄袭",
-  1: "左顾右盼",
-  2: "考中携带违规物品",
-  3: "他人替考",
-  4: "他人协助作答",
-  5: "考中使用违规(远程协助、直播等)软件",
-  6: "其他",
+  PLAGIARIZE: "夹带抄袭",
+  LOOK_AROUND: "左顾右盼",
+  ILLEGAL_ITEMS: "考中携带违规物品",
+  TAKER: "他人替考",
+  ASSISTANCE: "他人协助作答",
+  BLACK_LIST_SOFTWARE: "考中使用违规(远程协助、直播等)软件",
+  OTHER: "其他",
 };
 // 违规撤销原因
 export const BREACH_REPEAL_TYPE = {
-  0: "软件误操作",
-  1: "违规事实不符",
-  2: "其他",
+  SOFTWARE_MISSING: "软件误操作",
+  INCONSISTENT_FACTS: "违规事实不符",
+  CANCEL_OTHER: "其他",
+};
+// 异常状态
+export const EXCEPTION_TYPE = {
+  NET_TIME_OUT: "软件误操作",
+  MACHING_STOP: "机器硬件故障、死机",
+  NET_TIME_BREAK: "网络断开",
+  SOFTWARE_STOP: "软件故障",
+  POWER_CUT: "停电",
+  OTHER: "其他",
+};
+//
+export const VERIFY_EXCEPTION_TYPE = {
+  FACE_COUNT_ERROR: "人脸数量异常",
+  FACE_COMPARE_ERROR: "人脸比对异常",
+  EYE_CLOSE_ERROR: "闭眼检测异常",
+  LIVENESS_ACTION_ERROR: "活检动作错误",
+  NONE: "无异常",
+  REALNESS: "真实性检测异常",
+};
+// 考试状态
+export const EXAM_TYPE = {
+  FIRST_START: "初次开考",
+  RESUME_START: "恢复开考",
+  IN_PROCESS: "过程中",
 };
 // 审阅状态
 export const APPROVE_STATUS = {

+ 37 - 28
src/features/invigilation/ExamReport/BreachDetailDialog.vue

@@ -12,13 +12,16 @@
   >
     <el-table ref="TableList" :data="dataList">
       <el-table-column
-        prop="creatTime"
+        prop="status"
         label="违纪处理/撤销违纪"
       ></el-table-column>
-      <el-table-column prop="type" label="操作人"></el-table-column>
-      <el-table-column prop="type" label="异常处理结束时间"></el-table-column>
-      <el-table-column prop="content" label="类型/原因"></el-table-column>
-      <el-table-column prop="content" label="详情备注"></el-table-column>
+      <el-table-column prop="createName" label="操作人"></el-table-column>
+      <el-table-column
+        prop="endTime"
+        label="异常处理结束时间"
+      ></el-table-column>
+      <el-table-column prop="type" label="类型/原因"></el-table-column>
+      <el-table-column prop="remark" label="详情备注"></el-table-column>
     </el-table>
     <div class="part-page">
       <el-pagination
@@ -37,7 +40,10 @@
 </template>
 
 <script>
-// import { updateCourse } from "../api";
+import {
+  reportBreachDetail,
+  reportCancelBreachDetail,
+} from "@/api/invigilation";
 
 export default {
   name: "exception-detail-dialog",
@@ -46,6 +52,10 @@ export default {
       type: [String, Number],
       required: true,
     },
+    isCancel: {
+      type: Boolean,
+      default: false,
+    },
   },
   data() {
     return {
@@ -53,34 +63,33 @@ export default {
       current: 1,
       total: 0,
       size: 10,
-      dataList: [
-        {
-          id: 1,
-          creatTime: "2020-08-02 15:12:24",
-          type: "进入考试",
-          content: "MAC地址:",
-        },
-        {
-          id: 2,
-          creatTime: "2020-08-02 15:12:24",
-          type: "网络断开",
-          content: "系统自动重试2次,网络已恢复",
-        },
-        {
-          id: 3,
-          creatTime: "2020-08-02 15:12:24",
-          type: "重新登录",
-          content: "MAC地址:",
-        },
-      ],
+      dataList: [],
     };
   },
   methods: {
     visibleChange() {
+      this.toPage(1);
+    },
+    async getList() {
+      const datas = {
+        examStudentId: this.detailId,
+        pageNumber: this.current - 1,
+        pageSize: this.size,
+      };
+
+      const func = this.isCancel
+        ? reportCancelBreachDetail
+        : reportBreachDetail;
+
+      const res = await func(datas);
+
+      this.dataList = res.data.data.records;
+      this.total = res.data.data.total;
+    },
+    toPage(page) {
+      this.current = page;
       this.getList();
     },
-    getList() {},
-    toPage() {},
     cancel() {
       this.modalIsShow = false;
     },

+ 3 - 3
src/features/invigilation/ExamReport/ExamReport.vue

@@ -109,9 +109,9 @@ export default {
   data() {
     return {
       filter: {
-        examId: null,
-        roomCode: null,
-        courseCode: null,
+        examId: "",
+        roomCode: "",
+        courseCode: "",
         name: "",
       },
       examBatchs: [],

+ 9 - 25
src/features/invigilation/ExamReport/ExceptionDetailDialog.vue

@@ -12,13 +12,16 @@
   >
     <el-table ref="TableList" :data="dataList">
       <el-table-column
-        prop="creatTime"
+        prop="startTime"
         label="异常处理开始时间"
       ></el-table-column>
-      <el-table-column prop="type" label="异常处理结束时间"></el-table-column>
-      <el-table-column prop="content" label="异常原因"></el-table-column>
       <el-table-column
-        prop="content"
+        prop="endTime"
+        label="异常处理结束时间"
+      ></el-table-column>
+      <el-table-column prop="reason" label="异常原因"></el-table-column>
+      <el-table-column
+        prop="totalTime"
         label="持续时长(单位:分钟)"
       ></el-table-column>
     </el-table>
@@ -55,26 +58,7 @@ export default {
       current: 1,
       total: 0,
       size: 10,
-      dataList: [
-        {
-          id: 1,
-          creatTime: "2020-08-02 15:12:24",
-          type: "进入考试",
-          content: "MAC地址:",
-        },
-        {
-          id: 2,
-          creatTime: "2020-08-02 15:12:24",
-          type: "网络断开",
-          content: "系统自动重试2次,网络已恢复",
-        },
-        {
-          id: 3,
-          creatTime: "2020-08-02 15:12:24",
-          type: "重新登录",
-          content: "MAC地址:",
-        },
-      ],
+      dataList: [],
     };
   },
   methods: {
@@ -91,7 +75,7 @@ export default {
       const res = await reportExceptionDetail(datas);
 
       this.dataList = res.data.data.records;
-      this.total = res.data.data.records.total;
+      this.total = res.data.data.total;
     },
     toPage(page) {
       this.current = page;

+ 3 - 3
src/features/invigilation/ExamReport/ReportAbsent.vue

@@ -3,8 +3,8 @@
     <el-table ref="TableList" :data="dataList">
       <el-table-column type="index" label="排序"></el-table-column>
       <el-table-column prop="examName" label="批次名称(ID)"></el-table-column>
-      <el-table-column prop="examActivity" label="场次ID"></el-table-column>
-      <el-table-column prop="examroom" label="考场名称(代码)">
+      <el-table-column prop="examActivityId" label="场次ID"></el-table-column>
+      <el-table-column prop="roomName" label="考场名称(代码)">
         <span slot-scope="scope"
           >{{ scope.row.roomName }}({{ scope.row.roomCode }})</span
         >
@@ -67,7 +67,7 @@ export default {
       const res = await reportAbsentData(datas);
 
       this.dataList = res.data.data.records;
-      this.total = res.data.data.records.total;
+      this.total = res.data.data.total;
     },
     toPage(page) {
       this.current = page;

+ 34 - 5
src/features/invigilation/ExamReport/ReportBreach.vue

@@ -3,7 +3,7 @@
     <el-table ref="TableList" :data="dataList">
       <el-table-column type="index" label="排序"></el-table-column>
       <el-table-column prop="examName" label="批次名称(ID)"></el-table-column>
-      <el-table-column prop="examActivity" label="场次ID"></el-table-column>
+      <el-table-column prop="examActivityId" label="场次ID"></el-table-column>
       <el-table-column prop="examroom" label="考场名称(代码)">
         <span slot-scope="scope"
           >{{ scope.row.roomName }}({{ scope.row.roomCode }})</span
@@ -16,7 +16,24 @@
           >{{ scope.row.courseName }}({{ scope.row.courseCode }})</span
         ></el-table-column
       >
-      <el-table-column prop="name" label="违纪/正常"></el-table-column>
+      <el-table-column prop="breachStatus" label="违纪/正常">
+        <template slot-scope="scope">
+          <span :class="{ 'color-danger': scope.row.breachStatus }">
+            {{ scope.row.breachStatus ? "违纪" : "正常" }}
+          </span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作">
+        <template slot-scope="scope">
+          <el-button
+            class="btn-table-icon"
+            type="primary"
+            icon="icon icon-view"
+            @click="toDetail(scope.row)"
+            >详情</el-button
+          >
+        </template>
+      </el-table-column>
     </el-table>
     <div class="part-page">
       <el-pagination
@@ -30,14 +47,21 @@
       >
       </el-pagination>
     </div>
+
+    <breach-detail-dialog
+      :detail-id="detailId"
+      ref="BreachDetailDialog"
+    ></breach-detail-dialog>
   </div>
 </template>
 
 <script>
-import { reexamCheckedList } from "@/api/invigilation";
+import { reportBreachData } from "@/api/invigilation";
+import BreachDetailDialog from "./BreachDetailDialog";
 
 export default {
   name: "report-breach",
+  components: { BreachDetailDialog },
   props: {
     filter: {
       type: Object,
@@ -52,6 +76,7 @@ export default {
       total: 0,
       size: 10,
       dataList: [],
+      detailId: "",
     };
   },
   mounted() {
@@ -65,10 +90,10 @@ export default {
         pageSize: this.size,
       };
 
-      const res = await reexamCheckedList(datas);
+      const res = await reportBreachData(datas);
 
       this.dataList = res.data.data.records;
-      this.total = res.data.data.records.total;
+      this.total = res.data.data.total;
     },
     toPage(page) {
       this.current = page;
@@ -77,6 +102,10 @@ export default {
     getData() {
       this.toPage(1);
     },
+    toDetail(row) {
+      this.detailId = row.examStudentId;
+      this.$refs.BreachDetailDialog.open();
+    },
   },
 };
 </script>

+ 39 - 9
src/features/invigilation/ExamReport/ReportCancalBreach.vue

@@ -3,7 +3,7 @@
     <el-table ref="TableList" :data="dataList">
       <el-table-column type="index" label="排序"></el-table-column>
       <el-table-column prop="examName" label="批次名称(ID)"></el-table-column>
-      <el-table-column prop="examActivity" label="场次ID"></el-table-column>
+      <el-table-column prop="examActivityId" label="场次ID"></el-table-column>
       <el-table-column prop="examroom" label="考场名称(代码)">
         <span slot-scope="scope"
           >{{ scope.row.roomName }}({{ scope.row.roomCode }})</span
@@ -12,11 +12,28 @@
       <el-table-column prop="identity" label="证件号"></el-table-column>
       <el-table-column prop="name" label="姓名"></el-table-column>
       <el-table-column prop="courseCode" label="科目名称(代码)">
-        <span slot-scope="scope"
-          >{{ scope.row.courseName }}({{ scope.row.courseCode }})</span
-        ></el-table-column
-      >
-      <el-table-column prop="name" label="违纪/正常"></el-table-column>
+        <span slot-scope="scope">
+          {{ scope.row.courseName }}({{ scope.row.courseCode }})
+        </span>
+      </el-table-column>
+      <el-table-column prop="breachStatus" label="违纪/正常">
+        <template slot-scope="scope">
+          <span :class="{ 'color-danger': scope.row.breachStatus }">
+            {{ scope.row.breachStatus ? "违纪" : "正常" }}
+          </span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作">
+        <template slot-scope="scope">
+          <el-button
+            class="btn-table-icon"
+            type="primary"
+            icon="icon icon-view"
+            @click="toDetail(scope.row)"
+            >详情</el-button
+          >
+        </template>
+      </el-table-column>
     </el-table>
     <div class="part-page">
       <el-pagination
@@ -30,14 +47,22 @@
       >
       </el-pagination>
     </div>
+
+    <breach-detail-dialog
+      :detail-id="detailId"
+      is-cancel
+      ref="BreachDetailDialog"
+    ></breach-detail-dialog>
   </div>
 </template>
 
 <script>
-import { reexamCheckedList } from "@/api/invigilation";
+import { reportCancelBreachData } from "@/api/invigilation";
+import BreachDetailDialog from "./BreachDetailDialog";
 
 export default {
   name: "report-cancel-breach",
+  components: { BreachDetailDialog },
   props: {
     filter: {
       type: Object,
@@ -55,6 +80,7 @@ export default {
       total: 0,
       size: 10,
       dataList: [],
+      detailId: "",
     };
   },
   methods: {
@@ -65,10 +91,10 @@ export default {
         pageSize: this.size,
       };
 
-      const res = await reexamCheckedList(datas);
+      const res = await reportCancelBreachData(datas);
 
       this.dataList = res.data.data.records;
-      this.total = res.data.data.records.total;
+      this.total = res.data.data.total;
     },
     toPage(page) {
       this.current = page;
@@ -77,6 +103,10 @@ export default {
     getData() {
       this.toPage(1);
     },
+    toDetail(row) {
+      this.detailId = row.examStudentId;
+      this.$refs.BreachDetailDialog.open();
+    },
   },
 };
 </script>

+ 27 - 4
src/features/invigilation/ExamReport/ReportException.vue

@@ -3,7 +3,7 @@
     <el-table ref="TableList" :data="dataList">
       <el-table-column type="index" label="排序"></el-table-column>
       <el-table-column prop="examName" label="批次名称(ID)"></el-table-column>
-      <el-table-column prop="examActivity" label="场次ID"></el-table-column>
+      <el-table-column prop="examActivityId" label="场次ID"></el-table-column>
       <el-table-column prop="examroom" label="考场名称(代码)">
         <span slot-scope="scope"
           >{{ scope.row.roomName }}({{ scope.row.roomCode }})</span
@@ -16,11 +16,22 @@
           >{{ scope.row.courseName }}({{ scope.row.courseCode }})</span
         ></el-table-column
       >
-      <el-table-column prop="name" label="次数"></el-table-column>
+      <el-table-column prop="exceptionCount" label="次数"></el-table-column>
       <el-table-column
-        prop="name"
+        prop="exceptionTotalTime"
         label="累积持续时长(单位:分钟)"
       ></el-table-column>
+      <el-table-column label="操作">
+        <template slot-scope="scope">
+          <el-button
+            class="btn-table-icon"
+            type="primary"
+            icon="icon icon-view"
+            @click="toDetail(scope.row)"
+            >详情</el-button
+          >
+        </template>
+      </el-table-column>
     </el-table>
     <div class="part-page">
       <el-pagination
@@ -34,14 +45,21 @@
       >
       </el-pagination>
     </div>
+
+    <exception-detail-dialog
+      :detail-id="detailId"
+      ref="ExceptionDetailDialog"
+    ></exception-detail-dialog>
   </div>
 </template>
 
 <script>
 import { reportExceptionData } from "@/api/invigilation";
+import ExceptionDetailDialog from "./ExceptionDetailDialog";
 
 export default {
   name: "report-exception",
+  components: { ExceptionDetailDialog },
   props: {
     filter: {
       type: Object,
@@ -56,6 +74,7 @@ export default {
       total: 0,
       size: 10,
       dataList: [],
+      detailId: "",
     };
   },
   mounted() {
@@ -72,7 +91,7 @@ export default {
       const res = await reportExceptionData(datas);
 
       this.dataList = res.data.data.records;
-      this.total = res.data.data.records.total;
+      this.total = res.data.data.total;
     },
     toPage(page) {
       this.current = page;
@@ -81,6 +100,10 @@ export default {
     getData() {
       this.toPage(1);
     },
+    toDetail(row) {
+      this.detailId = row.examStudentId;
+      this.$refs.ExceptionDetailDialog.open();
+    },
   },
 };
 </script>

+ 45 - 43
src/features/invigilation/ExamReport/ReportOverview.vue

@@ -5,27 +5,21 @@
         <div class="overview-yk part-box">
           <div class="overview-yk-info">
             <h5>应考</h5>
-            <p>8000</p>
+            <p>{{ examTotal }}</p>
             <p>(单位:科次)</p>
           </div>
         </div>
       </el-col>
       <el-col :span="5">
         <div class="overview-detail">
-          <div class="overview-detail-item part-box">
-            <i class="overview-detail-line line-blue"></i>
-            <h5>实考 (科次)</h5>
-            <p>4852</p>
-          </div>
-          <div class="overview-detail-item part-box">
-            <i class="overview-detail-line line-yellow"></i>
-            <h5>缺考 (科次)</h5>
-            <p>745</p>
-          </div>
-          <div class="overview-detail-item part-box">
-            <i class="overview-detail-line line-cyan"></i>
-            <h5>未完成 (科次)</h5>
-            <p>2450</p>
+          <div
+            class="overview-detail-item part-box"
+            v-for="(item, index) in progressData"
+            :key="index"
+          >
+            <i :class="['overview-detail-line', `line-${item.color}`]"></i>
+            <h5>{{ item.name }} (科次)</h5>
+            <p>{{ item.count }}</p>
           </div>
         </div>
       </el-col>
@@ -88,20 +82,23 @@ export default {
   },
   data() {
     return {
-      infos: {},
-      chartDataReady: true,
+      chartDataReady: false,
+      examTotal: "0",
       progressData: [
         {
           name: "实考",
-          count: 60,
+          count: 0,
+          color: "blue",
         },
         {
           name: "缺考",
-          count: 10,
+          count: 0,
+          color: "yellow",
         },
         {
           name: "未完成",
-          count: 40,
+          count: 0,
+          color: "cyan",
         },
       ],
       distributionData: [
@@ -109,31 +106,11 @@ export default {
           name: "6月1号",
           count: 13,
         },
-        {
-          name: "6月2号",
-          count: 16,
-        },
-        {
-          name: "6月3号",
-          count: 20,
-        },
-        {
-          name: "6月4号",
-          count: 16,
-        },
-        {
-          name: "6月5号",
-          count: 10,
-        },
-        {
-          name: "6月6号",
-          count: 16,
-        },
       ],
     };
   },
   mounted() {
-    // this.getData();
+    this.getData();
   },
   methods: {
     async getData() {
@@ -142,8 +119,33 @@ export default {
       };
 
       const res = await reportOverviewData(datas);
-
-      this.infos = res.data.data;
+      const resData = res.data.data;
+      this.examTotal = resData.examTotal;
+      this.progressData = [
+        {
+          name: "实考",
+          count: resData.actualExamTotal,
+          color: "blue",
+        },
+        {
+          name: "缺考",
+          count: resData.deficiencyExamTotal,
+          color: "yellow",
+        },
+        {
+          name: "未完成",
+          count: resData.completeOffExamTotal,
+          color: "cyan",
+        },
+      ];
+      this.distributionData = resData.examTotalList.map((item) => {
+        const days = item.day.split("-");
+        return {
+          ...item,
+          name: `${days[1] * 1}月${days[2] * 1}日`,
+        };
+      });
+      this.chartDataReady = true;
     },
   },
 };

+ 2 - 2
src/features/invigilation/ExamReport/ReportReexam.vue

@@ -3,7 +3,7 @@
     <el-table ref="TableList" :data="dataList">
       <el-table-column type="index" label="排序"></el-table-column>
       <el-table-column prop="examName" label="批次名称(ID)"></el-table-column>
-      <el-table-column prop="examActivity" label="场次ID"></el-table-column>
+      <el-table-column prop="examActivityId" label="场次ID"></el-table-column>
       <el-table-column prop="examroom" label="考场名称(代码)">
         <span slot-scope="scope"
           >{{ scope.row.roomName }}({{ scope.row.roomCode }})</span
@@ -67,7 +67,7 @@ export default {
       const res = await reportReexamData(datas);
 
       this.dataList = res.data.data.records;
-      this.total = res.data.data.records.total;
+      this.total = res.data.data.total;
     },
     toPage(page) {
       this.current = page;

+ 29 - 11
src/features/invigilation/ExamReport/ReportStatistics.vue

@@ -3,31 +3,34 @@
     <el-table ref="TableList" :data="dataList">
       <el-table-column type="index" label="排序"></el-table-column>
       <el-table-column prop="examName" label="批次名称(ID)"></el-table-column>
-      <el-table-column prop="examActivity" label="场次ID"></el-table-column>
-      <el-table-column prop="examroom" label="考场名称(代码)">
+      <el-table-column prop="examActivityId" label="场次ID"></el-table-column>
+      <el-table-column prop="roomName" label="考场名称(代码)">
         <span slot-scope="scope"
           >{{ scope.row.roomName }}({{ scope.row.roomCode }})</span
         >
       </el-table-column>
-      <el-table-column prop="name" label="应考(科次)">
+      <el-table-column prop="examTotal" label="应考(科次)">
         <template slot-scope="scope">
-          <span class="color-primary">{{ scope.row.name }}</span>
+          <span class="color-primary">{{ scope.row.examTotal }}</span>
         </template>
       </el-table-column>
-      <el-table-column prop="courseCode" label="实考(科次)">
+      <el-table-column prop="actualExamTotal" label="实考(科次)">
         <template slot-scope="scope">
-          <span class="color-success">{{ scope.row.name }}</span>
+          <span class="color-success">{{ scope.row.actualExamTotal }}</span>
         </template>
       </el-table-column>
-      <el-table-column prop="reasom" label="缺考(科次)">
+      <el-table-column prop="deficiencyExamTotal" label="缺考(科次)">
         <template slot-scope="scope">
-          <span class="color-danger">{{ scope.row.name }}</span>
+          <span class="color-danger">{{ scope.row.deficiencyExamTotal }}</span>
         </template>
       </el-table-column>
     </el-table>
     <div class="part-page">
       <div class="page-info stat-page-info">
-        共计<span>应考8000</span>科次,<span>实考748分</span>科次,<span>缺考7854</span>科次
+        共计<span>应考{{ examTotal }}</span
+        >科次,<span>实考{{ actualExamTotal }}分</span>科次,<span
+          >缺考{{ deficiencyExamTotal }}</span
+        >科次
       </div>
       <el-pagination
         background
@@ -44,7 +47,7 @@
 </template>
 
 <script>
-import { reportStatisticsData } from "@/api/invigilation";
+import { reportOverviewData, reportStatisticsData } from "@/api/invigilation";
 
 export default {
   name: "report-statistics",
@@ -61,6 +64,9 @@ export default {
       current: 1,
       total: 0,
       size: 10,
+      examTotal: 0,
+      actualExamTotal: 0,
+      deficiencyExamTotal: 0,
       dataList: [],
     };
   },
@@ -78,14 +84,26 @@ export default {
       const res = await reportStatisticsData(datas);
 
       this.dataList = res.data.data.records;
-      this.total = res.data.data.records.total;
+      this.total = res.data.data.total;
     },
     toPage(page) {
       this.current = page;
       this.getList();
     },
+    async getTotalCount() {
+      const datas = {
+        ...this.filter,
+      };
+
+      const res = await reportOverviewData(datas);
+      const resData = res.data.data;
+      this.examTotal = resData.examTotal;
+      this.actualExamTotal = resData.actualExamTotal;
+      this.deficiencyExamTotal = resData.deficiencyExamTotal;
+    },
     getData() {
       this.toPage(1);
+      this.getTotalCount();
     },
   },
 };

+ 14 - 5
src/features/invigilation/InvigilationDetail/InvigilationDetail.vue

@@ -159,7 +159,7 @@
             ></el-input-number>
           </el-form-item>
           <el-form-item>
-            <el-button type="primary" @click="toPage(1)">查询</el-button>
+            <el-button type="primary" @click="toSearch">查询</el-button>
             <el-button type="primary" @click="changeFilter">{{
               showAdvancedFilter ? "隐藏高级查询" : "高级查询"
             }}</el-button>
@@ -184,7 +184,7 @@
       <el-table-column prop="identity" label="证件号"></el-table-column>
       <el-table-column prop="name" label="姓名"></el-table-column>
       <el-table-column prop="mobileNumber" label="联系电话"></el-table-column>
-      <el-table-column prop="courseNameCode" label="科目(代码)">
+      <el-table-column prop="courseCode" label="科目(代码)">
       </el-table-column>
       <el-table-column prop="status" label="状态"></el-table-column>
       <el-table-column prop="finishType" label="交卷方式">
@@ -244,6 +244,7 @@ import {
   STUDENT_BEHAVIOR_STATUS,
 } from "@/constant/constants";
 import SummaryLine from "../common/SummaryLine";
+import { mapActions } from "vuex";
 
 export default {
   name: "invigilation-detail",
@@ -283,10 +284,11 @@ export default {
     this.initData();
   },
   methods: {
+    ...mapActions("invigilation", ["updateDetailIds"]),
     async initData() {
       await this.getExamBatchList();
       this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
-      this.toPage(1);
+      this.toSearch();
       this.getExamActivityRoomList();
     },
     async getList() {
@@ -299,12 +301,19 @@ export default {
       const res = await invigilationHistoryList(datas);
 
       this.dataList = res.data.data.records;
-      this.total = res.data.data.records.total;
+      this.total = res.data.data.total;
     },
     toPage(page) {
       this.current = page;
       this.getList();
     },
+    toSearch() {
+      this.updateDetailIds({
+        filterData: this.filter,
+        fetchFunc: invigilationHistoryList,
+      });
+      this.toPage(1);
+    },
     async getExamBatchList() {
       const user = this.$store.state.user;
       const userId =
@@ -342,7 +351,7 @@ export default {
     batchAction() {},
     toDetail(row) {
       this.$router.push({
-        name: "WainingDetail",
+        name: "WarningDetail",
         params: { recordId: row.examRecordId },
       });
     },

+ 72 - 96
src/features/invigilation/OnlinePatrol/OnlinePatrol.vue

@@ -125,7 +125,7 @@
             ></el-input-number>
           </el-form-item>
           <el-form-item>
-            <el-button type="primary" @click="toPage(1)">查询</el-button>
+            <el-button type="primary" @click="toSearch">查询</el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -152,10 +152,20 @@
           >{{ scope.row.courseName }}({{ scope.row.courseCode }})</span
         >
       </el-table-column>
-      <el-table-column prop="subjectCode" label="剩余时间"></el-table-column>
-      <el-table-column prop="subjectCode" label="进度"></el-table-column>
-      <el-table-column prop="subjectCode" label="通讯"></el-table-column>
-      <el-table-column prop="updateTime" label="更新时间"></el-table-column>
+      <el-table-column prop="remainTime" label="剩余时间"></el-table-column>
+      <el-table-column prop="progress" label="进度"></el-table-column>
+      <el-table-column prop="clientWebsocketStatus" label="通讯">
+        <template slot-scope="scope">
+          <right-or-wrong
+            :status="CLIENT_WEBSOCKET_STATUS[scope.row.clientWebsocketStatus]"
+          ></right-or-wrong>
+        </template>
+      </el-table-column>
+      <el-table-column prop="updateTime" label="更新时间">
+        <span slot-scope="scope">
+          {{ scope.row.updateTime | datetimeFilter }}
+        </span>
+      </el-table-column>
       <el-table-column prop="exceptionCount" label="异常处理"></el-table-column>
       <el-table-column
         prop="multipleFaceCount"
@@ -221,16 +231,22 @@
 <script>
 import {
   patrolList,
+  patrolReportList,
   examBatchList,
   examActivityRoomList,
 } from "@/api/invigilation";
-import { BOOLEAN_TYPE, STUDENT_ONLINE_STATUS } from "@/constant/constants";
+import {
+  BOOLEAN_TYPE,
+  STUDENT_ONLINE_STATUS,
+  CLIENT_WEBSOCKET_STATUS,
+} from "@/constant/constants";
 import EchartRender from "../common/EchartRender";
 import SummaryLine from "../common/SummaryLine";
+import RightOrWrong from "../common/RightOrWrong";
 
 export default {
   name: "online-patrol",
-  components: { EchartRender, SummaryLine },
+  components: { EchartRender, SummaryLine, RightOrWrong },
   data() {
     return {
       filter: {
@@ -249,23 +265,38 @@ export default {
       },
       BOOLEAN_TYPE,
       STUDENT_ONLINE_STATUS,
+      CLIENT_WEBSOCKET_STATUS,
       current: 1,
-      total: 30,
+      total: 0,
       size: 10,
       examBatchs: [],
       examActivities: [],
       examRooms: [],
       examCourses: [],
-      dataList: [],
-      userId: this.$store.state.user.id,
-      examPropData: {},
-      statDataReady: true,
-      statInfo: [
+      dataList: [
         {
-          name: "已登录",
-          order: "none",
-          key: "login",
+          breachStatus: 0,
+          examActivityCode: "",
+          examActivityId: 0,
+          examId: 0,
+          examName: "",
+          examRecordId: 0,
+          examStudentId: 0,
+          exceptionCount: 0,
+          identity: "",
+          multipleFaceCount: 0,
+          name: "",
+          roomCode: "",
+          roomName: "",
+          seq: 0,
+          status: "",
+          statusCode: "",
+          updateTime: "",
+          warningCount: 0,
         },
+      ],
+      statDataReady: false,
+      statInfo: [
         {
           name: "通讯故障",
           order: "none",
@@ -277,84 +308,11 @@ export default {
           key: "waiting",
         },
       ],
-      statData: [
-        {
-          name: "苏州市第一中学",
-          login: 0.6,
-          waiting: 0.25,
-          error: 0.15,
-        },
-        {
-          name: "中山纪念中学",
-          login: 0.5,
-          waiting: 0.35,
-          error: 0.15,
-        },
-        {
-          name: "东华高级中学",
-          login: 0.4,
-          waiting: 0.25,
-          error: 0.35,
-        },
-        {
-          name: "重庆市开州中学",
-          login: 0.8,
-          waiting: 0.1,
-          error: 0.1,
-        },
-        {
-          name: "重庆市铜梁中学校",
-          login: 0.55,
-          waiting: 0.25,
-          error: 0.2,
-        },
-        {
-          name: "北京市鼎石学校",
-          login: 0.8,
-          waiting: 0.05,
-          error: 0.15,
-        },
-        {
-          name: "重庆市第八中学",
-          login: 0.7,
-          waiting: 0.15,
-          error: 0.15,
-        },
-        {
-          name: "柯桥中学",
-          login: 0.75,
-          waiting: 0.05,
-          error: 0.2,
-        },
-        {
-          name: "伊川县第一高中",
-          login: 0.6,
-          waiting: 0.2,
-          error: 0.2,
-        },
-        {
-          name: "汝南高中",
-          login: 0.6,
-          waiting: 0.05,
-          error: 0.35,
-        },
-        {
-          name: "博罗榕城中学",
-          login: 0.7,
-          waiting: 0.25,
-          error: 0.05,
-        },
-        {
-          name: "海亮高级中学",
-          login: 0.7,
-          waiting: 0.1,
-          error: 0.2,
-        },
-      ],
+      statData: [],
     };
   },
   mounted() {
-    this.initData();
+    // this.initData();
   },
   methods: {
     async initData() {
@@ -372,14 +330,17 @@ export default {
       };
 
       const res = await patrolList(datas);
-
       this.dataList = res.data.data.records;
-      this.total = res.data.data.records.total;
+      this.total = res.data.data.total;
     },
     toPage(page) {
       this.current = page;
       this.getList();
     },
+    toSearch() {
+      this.toPage(1);
+      this.getPatrolReportList();
+    },
     async getExamBatchList() {
       const user = this.$store.state.user;
       const userId =
@@ -403,11 +364,26 @@ export default {
       this.getExamActivityRoomList();
     },
     toDetail(row) {
-      console.log(row);
       this.$router.push({
-        name: "WainingDetail",
-        params: { recordId: row.examRecordId },
+        name: "RealtimeMonitoring",
+        params: { examId: row.examId },
+      });
+    },
+    async getPatrolReportList() {
+      const res = await patrolReportList(this.filter);
+      this.statData = res.data.data.map((item) => {
+        return {
+          ...item,
+          name: item.roomName,
+          warning: item.warningCount,
+          error: item.clientWebsocketStatusCount,
+        };
+      });
+      // 默认预警数倒叙排列
+      this.statInfo.map((elem) => {
+        elem.order = "none";
       });
+      this.orderChange(this.statInfo[1]);
     },
     orderChange(statItem) {
       const orderInfo = {

+ 1 - 33
src/features/invigilation/ProgressDetail/ProgressDetail.vue

@@ -66,34 +66,6 @@
           data-type="progress"
           :exam-id="filter.examId"
         ></summary-line>
-        <!-- <div class="part-filter-info-main summary-line">
-          <p class="summary-line-item">
-            <i class="icon icon-users"></i>
-            <span class="line-name">全部应考</span>
-            <span>50人</span>
-          </p>
-          <p class="summary-line-item">
-            <i class="icon icon-rate"></i>
-            <span class="line-name">完成率</span><span>20人</span>
-          </p>
-          <p class="summary-line-item">
-            <i class="line-point line-point-success"></i>
-            <el-popover
-              placement="bottom-start"
-              width="200"
-              trigger="hover"
-              content="已待考:已进入待考界面等待开考的考生"
-            >
-              <span class="line-name" slot="reference">已待考</span>
-            </el-popover>
-            <span>5人</span>
-          </p>
-          <p class="summary-line-item">
-            <i class="line-point line-point-danger"></i>
-            <span class="line-name">未完成</span>
-            <span>3人</span>
-          </p>
-        </div> -->
         <div class="part-filter-info-sub">
           <el-button type="primary" icon="icon icon-upload" @click="toExport"
             >导出查询结果</el-button
@@ -192,7 +164,7 @@ export default {
       const res = await progressDetailList(datas);
 
       this.dataList = res.data.data.records;
-      this.total = res.data.data.records.total;
+      this.total = res.data.data.total;
     },
     toPage(page) {
       this.current = page;
@@ -220,10 +192,6 @@ export default {
       this.filter.courseCode = null;
       this.getExamActivityRoomList();
     },
-    toDetail(row) {
-      console.log(row);
-      this.$router.push({ name: "WainingDetail", params: { id: row.id } });
-    },
     toExport() {
       // TODO:
     },

+ 68 - 42
src/features/invigilation/RealtimeMonitoring/RealtimeMonitoring.vue

@@ -128,7 +128,7 @@
             ></el-input>
           </el-form-item>
           <el-form-item>
-            <el-button type="primary" @click="toPage(1)">查询</el-button>
+            <el-button type="primary" @click="toSearch">查询</el-button>
           </el-form-item>
         </el-form>
 
@@ -208,7 +208,13 @@
         </span>
       </el-table-column>
       <el-table-column prop="warningCount" label="预警数"></el-table-column>
-      <el-table-column prop="breachStatus" label="违纪"></el-table-column>
+      <el-table-column prop="breachStatus" label="违纪">
+        <template slot-scope="scope">
+          <span :class="{ 'color-danger': scope.row.breachStatus }">
+            {{ scope.row.breachStatus ? "违纪" : "正常" }}
+          </span>
+        </template>
+      </el-table-column>
       <el-table-column label="操作" width="100">
         <template slot-scope="scope">
           <el-button
@@ -263,6 +269,7 @@ import {
   invigilateVideoList,
   invigilateExamFinish,
   monitorCallCount,
+  invigilationWarningMessage,
 } from "@/api/invigilation";
 import ExamBatchDialog from "./ExamBatchDialog";
 import RightOrWrong from "../common/RightOrWrong";
@@ -276,6 +283,7 @@ import {
   CLIENT_WEBSOCKET_STATUS,
   MONITOR_STATUS_SOURCE,
 } from "@/constant/constants";
+import { mapActions } from "vuex";
 
 export default {
   name: "realtime-monitoring",
@@ -306,6 +314,8 @@ export default {
       communicationCount: 0,
       curExamBatch: {},
       curViewingAngle: {},
+      noticeCaches: {},
+      setT: null,
       current: 1,
       total: 0,
       size: 10,
@@ -316,22 +326,7 @@ export default {
       subjects: [],
       pageType: "0",
       dataList: [],
-      videoList: [
-        {
-          name: "刘西西",
-          identity: "000000000000000008",
-          progress: "52%",
-          warning: false,
-          netbreak: true,
-        },
-        {
-          name: "刘西西",
-          identity: "000000000000000008",
-          progress: "52%",
-          warning: true,
-          netbreak: false,
-        },
-      ],
+      videoList: [],
       viewingAngles: [
         {
           code: "1",
@@ -349,25 +344,12 @@ export default {
     };
   },
   mounted() {
-    // window.inviligateWaining = (id) => {
-    //   console.log(id);
-    // };
-    // this.$notify({
-    //   duration: 5,
-    //   dangerouslyUseHTMLString: true,
-    //   customClass: "msg-monitor-magbox",
-    //   position: "bottom-right",
-    //   offset: 50,
-    //   message: `
-    //     <div class="msg-monitor">
-    //       <span class="msg-monitor-icon"><i class="icon icon-warning"></i></span>
-    //       <span>注意:<b>张三意识</b>发现违纪,</span>
-    //       <span class="msg-monitor-action" onclick="window.inviligateWaining(12)">立即处理</span>
-    //     </div>
-    //   `,
-    // });
+    window.inviligateWarning = (id) => {
+      this.toDetail({ examRecordId: id });
+    };
   },
   methods: {
+    ...mapActions("invigilation", ["updateDetailIds"]),
     async getList() {
       const datas = {
         ...this.filter,
@@ -387,12 +369,19 @@ export default {
         this.videoList = res.data.data.records;
       }
 
-      this.total = res.data.data.records.total;
+      this.total = res.data.data.total;
     },
     toPage(page) {
       this.current = page;
       this.getList();
     },
+    toSearch() {
+      this.updateDetailIds({
+        filterData: this.filter,
+        fetchFunc: invigilateList,
+      });
+      this.toPage(1);
+    },
     async getMonitorCallCount() {
       if (!this.filter.examId) return;
       const res = await monitorCallCount(this.filter.examId);
@@ -402,8 +391,9 @@ export default {
       if (!examBatch) return;
       this.filter.examId = examBatch.id;
       this.curExamBatch = examBatch;
-      this.toPage(1);
+      this.toSearch();
       this.getMonitorCallCount();
+      this.fetchWarningNotice();
     },
     pageTypeChange(pageType) {
       this.pageType = pageType;
@@ -416,6 +406,7 @@ export default {
     },
     viewingAngleChange(data) {
       this.curViewingAngle = data;
+      // TODO:视角切换
     },
     async finishInvigilation() {
       if (!this.multipleSelection.length) {
@@ -442,7 +433,7 @@ export default {
 
       if (!result) return;
 
-      await invigilateExamFinish();
+      await invigilateExamFinish(this.filter.examId);
       this.toPage(1);
       this.$message({
         type: "success",
@@ -450,18 +441,53 @@ export default {
       });
     },
     toCommunication() {
-      this.$router.push({ name: "VideoCommunication" });
+      this.$router.push({
+        name: "VideoCommunication",
+        params: {
+          examId: this.filter.examId,
+        },
+      });
+    },
+    async fetchWarningNotice() {
+      if (this.setT) clearTimeout(this.setT);
+      if (!this.filter.examId) return;
+
+      const res = await invigilationWarningMessage(this.filter.examId);
+      res.data.data.forEach((item) => {
+        const stdKey = item.examRecordId;
+        if (!this.noticeCaches[stdKey]) {
+          this.noticeCaches[stdKey] = item;
+          this.$notify({
+            duration: 5 * 1000,
+            dangerouslyUseHTMLString: true,
+            customClass: "msg-monitor-magbox",
+            position: "bottom-right",
+            offset: 50,
+            message: `
+              <div class="msg-monitor">
+                <span class="msg-monitor-icon"><i class="icon icon-warning"></i></span>
+                <span>注意:<b>${item.name}</b>发现违纪,</span>
+                <span class="msg-monitor-action" onclick="window.inviligateWarning(${item.examRecordId})">立即处理</span>
+              </div>
+            `,
+          });
+        }
+      });
+
+      this.setT = setTimeout(() => {
+        this.fetchWarningNotice();
+      }, 10 * 1000);
     },
     toDetail(row) {
-      console.log(row);
       this.$router.push({
-        name: "WainingDetail",
+        name: "WarningDetail",
         params: { recordId: row.examRecordId },
       });
     },
   },
   beforeDestroy() {
-    delete window.inviligateWaining;
+    if (this.setT) clearTimeout(this.setT);
+    delete window.inviligateWarning;
   },
 };
 </script>

+ 22 - 19
src/features/invigilation/RealtimeMonitoring/VideoCommunication.vue

@@ -3,9 +3,13 @@
     <div class="part-box-head">
       <div class="part-box-head-left"><h1>通话申请列表</h1></div>
       <div class="part-box-head-right">
-        <el-radio-group size="small" v-model="pageType">
-          <el-radio-button label="0">待处理</el-radio-button>
-          <el-radio-button label="1">已处理</el-radio-button>
+        <el-radio-group
+          size="small"
+          v-model="callStatus"
+          @change="getCommunicationList"
+        >
+          <el-radio-button label="START">待处理</el-radio-button>
+          <el-radio-button label="STOP">已处理</el-radio-button>
         </el-radio-group>
       </div>
     </div>
@@ -87,8 +91,8 @@ export default {
   name: "video-communication",
   data() {
     return {
-      examActivityId: this.$route.params.examActivityId,
-      pageType: "0",
+      examId: this.$route.params.examId,
+      callStatus: "START",
       dialogVisible: false,
       students: [
         // {
@@ -117,22 +121,21 @@ export default {
       if (this.setT) clearTimeout(this.setT);
 
       const res = await communicationList({
-        // examActivityId: 1,
+        examId: this.examId,
+        callStatus: this.callStatus,
         pageNumber: this.current,
         pageSize: this.size,
       }).catch(() => {});
-      this.students = res.data.data.records
-        .filter((item) => item.callStatus === "START")
-        .map((item, index) => {
-          // TODO:用户头像临时处理方法
-          const lindex = index % 3;
-          return {
-            ...item,
-            stdAvatar: stdAvatars[lindex],
-            stdName: "",
-          };
-        });
-      this.total = res.data.data.records.total;
+      this.students = res.data.data.records.map((item, index) => {
+        // TODO:用户头像临时处理方法
+        const lindex = index % 3;
+        return {
+          ...item,
+          stdAvatar: stdAvatars[lindex],
+          stdName: "",
+        };
+      });
+      this.total = res.data.data.total;
       // 当前页没有数据,同时当前页不是第一页,则自动跳到前一页。
       if (!this.students.length && this.current > 1) {
         this.current--;
@@ -140,7 +143,7 @@ export default {
       } else {
         this.setT = setTimeout(() => {
           this.getCommunicationList();
-        }, 3000);
+        }, 5000);
       }
     },
     toPage(page) {

+ 236 - 160
src/features/invigilation/RealtimeMonitoring/WainingDetail.vue → src/features/invigilation/RealtimeMonitoring/WarningDetail.vue

@@ -38,20 +38,35 @@
           </div>
           <div class="student-head-right">
             <el-button
+              class="el-icon-btn"
               size="mini"
               type="primary"
               icon="el-icon-arrow-left"
+              title="查看上一个"
+              @click="changeStudent(0)"
+              :disabled="holding"
             ></el-button>
             <el-button
+              class="el-icon-btn"
               size="mini"
               type="primary"
               icon="el-icon-arrow-right"
+              title="查看下一个"
+              @click="changeStudent(1)"
+              :disabled="holding"
             ></el-button>
           </div>
         </div>
         <div class="student-views">
           <div class="student-avatar">
-            <img :src="detailInfo.stdAvatar" alt="" />
+            <img
+              :src="detailInfo.examStudentAvatar"
+              alt=""
+              v-if="detailInfo.examStudentAvatar"
+            />
+            <div class="avatar-default" v-else>
+              <i class="el-icon-user-solid"></i>
+            </div>
           </div>
           <div class="student-video">
             <div class="student-video-item">
@@ -78,10 +93,15 @@
         </div>
         <div class="student-exception">
           <ul>
-            <li v-for="(item, index) in detailInfo.exceptionInfos" :key="index">
+            <li v-for="(log, index) in exceptionSummary" :key="index">
               <i>{{ index + 1 }}</i>
-              <h4>{{ item.remark }}</h4>
-              <p>{{ item.info }}</p>
+              <h4>{{ log.info }}</h4>
+              <p>
+                时间段:
+                <span v-if="log.startTime">{{ log.startTime }} ~ </span>
+                <span>{{ log.endTime }}</span>
+              </p>
+              <p v-if="log.durationTime">持续时长约:{{ log.durationTime }}</p>
             </li>
           </ul>
         </div>
@@ -92,9 +112,24 @@
       <div class="warning-body-head clear-float">
         <div class="warning-body-head-action">
           <h3>考试轨迹</h3>
-          <el-button type="primary" icon="icon icon-view"></el-button>
-          <el-button type="primary" icon="icon icon-text"></el-button>
-          <el-button type="primary" icon="icon icon-audio"></el-button>
+          <!-- <el-button
+            class="el-icon-btn"
+            type="primary"
+            icon="icon icon-view"
+          ></el-button> -->
+          <el-button
+            class="el-icon-btn"
+            type="primary"
+            icon="icon icon-text"
+            @click="toSendTextMsg"
+            title="发送文字提醒"
+          ></el-button>
+          <!-- todo: -->
+          <!-- <el-button
+            class="el-icon-btn"
+            type="primary"
+            icon="icon icon-audio"
+          ></el-button> -->
           <el-popover
             class="warning-body-head-call"
             placement="bottom-start"
@@ -107,22 +142,28 @@
         </div>
         <div class="warning-body-head-info summary-line">
           <p class="summary-line-item">
-            <i class="line-point line-point-danger"></i
-            ><span class="line-name">系统预警</span
-            ><span>{{ detailInfo.warningCount }}次</span>
+            <i class="line-point line-point-danger"></i>
+            <span class="line-name">系统预警</span>
+            <span>{{ detailInfo.warningCount }}次</span>
           </p>
           <p class="summary-line-item">
-            <i class="line-point line-point-danger"></i
-            ><span class="line-name">陌生人脸</span
-            ><span>{{ detailInfo.multipleFaceCount }}次</span>
+            <i class="line-point line-point-danger"></i>
+            <span class="line-name">陌生人脸</span>
+            <span>{{ detailInfo.multipleFaceCount }}次</span>
           </p>
           <p class="summary-line-item">
-            <i class="line-point line-point-danger"></i
-            ><span class="line-name">异常处理</span
-            ><span>{{ detailInfo.exceptionCount }}次</span>
+            <i class="line-point line-point-danger"></i>
+            <span class="line-name">异常处理</span>
+            <span>{{ detailInfo.exceptionCount }}次</span>
           </p>
           <p class="summary-line-item">
-            <span></span><span>违纪状态:{{ detailInfo.breachStatus }}</span>
+            <span></span>
+            <span>
+              <b>违纪状态:</b>
+              <b :class="{ 'color-danger': detailInfo.breachStatus }">
+                {{ detailInfo.breachStatus ? "违纪" : "正常" }}
+              </b>
+            </span>
           </p>
           <el-button type="danger" icon="icon icon-stop" @click="toBreach"
             >违纪处理</el-button
@@ -133,71 +174,42 @@
         </div>
       </div>
       <div class="warning-body-main">
-        <div class="warning-history">
-          <div class="warning-history-info">
-            <h3>进入考试</h3>
-            <p>时间段:08:00:01</p>
-          </div>
-          <div class="warning-history-type type-common">
-            <i class="icon icon-current-step"></i>
-          </div>
-          <div class="warning-history-media">
-            <ul class="media-list">
-              <li></li>
-            </ul>
-          </div>
-        </div>
-        <div class="warning-history">
-          <div class="warning-history-info">
-            <h3>违纪预警</h3>
-            <p>时间段:09:10:20 ~ 09:20:36</p>
-            <p>持续时长约:10分钟16秒</p>
-          </div>
-          <div class="warning-history-type type-exception">
-            <i class="icon icon-warning-act"></i>
-          </div>
-          <div class="warning-history-media">
-            <ul class="media-list">
-              <li></li>
-              <li></li>
-              <li></li>
-              <li></li>
-              <li></li>
-              <li></li>
-              <li></li>
-              <li></li>
-            </ul>
-          </div>
-        </div>
-        <div class="warning-history">
-          <div class="warning-history-info">
-            <h3>异常处理</h3>
-            <h5>类型:网络故障</h5>
-            <p>时间段:09:10:20 ~ 09:20:36</p>
-            <p>持续时长约:10分钟16秒</p>
-          </div>
-          <div class="warning-history-type type-exception">
-            <i class="icon icon-net-break"></i>
-          </div>
-          <div class="warning-history-media">
-            <ul class="media-list">
-              <li></li>
-            </ul>
-          </div>
-        </div>
-        <div class="warning-history">
+        <div
+          class="warning-history"
+          v-for="log in detailInfo.examStudentLogList"
+          :key="log.id"
+        >
           <div class="warning-history-info">
-            <h3>异常处理</h3>
-            <h5>类型:网络故障</h5>
-            <p>时间段:09:10:20 ~ 09:20:36</p>
-            <p>持续时长约:10分钟16秒</p>
+            <h3>{{ log.info }}</h3>
+            <p>
+              时间段:
+              <span v-if="log.startTime">{{ log.startTime }} ~ </span>
+              <span>{{ log.endTime }}</span>
+            </p>
+            <p v-if="log.durationTime">持续时长约:{{ log.durationTime }}</p>
           </div>
-          <div class="warning-history-type type-exception">
-            <i class="icon icon-net-break"></i>
+          <div
+            :class="[
+              'warning-history-type',
+              log.viewType === 'common' ? 'type-common' : 'type-exception',
+            ]"
+          >
+            <i
+              :class="[
+                'icon',
+                {
+                  'icon-current-step': log.viewType === 'common',
+                  'icon-warning-act': log.viewType === 'warning',
+                  'icon-net-break': log.viewType === 'exception',
+                },
+              ]"
+            ></i>
           </div>
           <div class="warning-history-media">
-            <ul class="media-list">
-              <li></li>
+            <ul class="media-list" v-if="log.photos">
+              <li v-for="(photo, pindex) in log.photos" :key="pindex">
+                <img :src="photo" />
+              </li>
             </ul>
           </div>
         </div>
@@ -208,6 +220,11 @@
       :instance="curDetail"
       ref="StudentBreachDialog"
     ></student-breach-dialog>
+    <!-- warning-text-message-dialog -->
+    <warning-text-message-dialog
+      :record-id="recordId"
+      ref="WarningTextMessageDialog"
+    ></warning-text-message-dialog>
 
     <!-- 通话弹出层 -->
     <el-dialog
@@ -234,9 +251,11 @@
       <div class="communication-wait" v-show="isWaiting">
         <p class="communication-wait-tips">等待接听…</p>
         <div class="communication-wait-avatar">
-          <img :src="curStudent.stdAvatar" :alt="curStudent.stdName" />
+          <img :src="detailInfo.stdAvatar" :alt="detailInfo.examStudentName" />
         </div>
-        <p class="communication-wait-username">{{ curStudent.stdName }}</p>
+        <p class="communication-wait-username">
+          {{ detailInfo.examStudentName }}
+        </p>
         <div class="communication-wait-action">
           <el-button round type="danger" @click="hangup">取消通话</el-button>
         </div>
@@ -256,82 +275,20 @@ import {
   getUserMonitorKey,
 } from "@/api/invigilation";
 import StudentBreachDialog from "./StudentBreachDialog";
-
-const test = {
-  breachStatus: 0,
-  courseNameCode: "大学语文(1001)",
-  examActivityId: 0,
-  examId: 0,
-  examRecordId: 11111,
-  examStudentId: 0,
-  examStudentName: "张一三",
-  exceptionCount: 0,
-  exceptionInfos: [
-    {
-      createTime: "",
-      info: "系统发现该考生身份识别多次不通过,请监考老师关注!",
-      remark: "身份验证不通过",
-      type: "",
-    },
-    {
-      createTime: "",
-      info:
-        "系统发现该考生至少有多次以上持续1分钟以上的违规动作,且违规动作的持续时间已超出合理范围,请监考老师关注!",
-      remark: "疑似:有违规动作",
-      type: "",
-    },
-    {
-      createTime: "",
-      info: "系统检测到考生可能使用了虚拟摄像头,请监考老师关注!",
-      remark: "疑似:启用虚拟摄像头",
-      type: "",
-    },
-  ],
-  stdAvatar: "/img/avatars/2.jpg",
-  identity: "000000000000000008",
-  multipleFaceCount: 0,
-  roomCode: "111",
-  roomName: "第一考场",
-  status: "正常",
-  statusCode: "0124",
-  studentLogs: [
-    {
-      createTime: "",
-      info: "",
-      remark: "",
-      type: "",
-    },
-  ],
-  warningCount: 0,
-  warningInfos: [
-    {
-      approveStatus: 0,
-      createTime: "",
-      info: "",
-      level: "",
-      remark: "",
-      type: "",
-    },
-  ],
-};
+import WarningTextMessageDialog from "./WarningTextMessageDialog";
+import { formatDate, timeNumberToText } from "@/utils/utils";
+import { mapState } from "vuex";
 
 export default {
   name: "warning-detail",
-  components: { FlvMedia, StudentBreachDialog },
+  components: { FlvMedia, StudentBreachDialog, WarningTextMessageDialog },
   data() {
     return {
-      // recordId: "32145907927482368",
       recordId: this.$route.params.recordId,
-      detailInfo: test,
+      detailInfo: {},
       curDetail: {},
-      curStudent: {
-        stdAvatar: "/img/avatars/2.jpg",
-        stdCardNo: "000000000000000008",
-        stdName: "张龙龙",
-        subjectName: "大学英语",
-        subjectCode: "10006",
-        roomId: "6",
-      },
+      serialIds: [],
+      exceptionSummary: [],
       firstViewVideo: {
         id: "111",
         src: "",
@@ -344,6 +301,7 @@ export default {
       },
       firstViewVideoReady: false,
       secondViewVideoReady: false,
+      holding: false,
       // communication
       durationTime: "00:10:23",
       appId: "1400411036",
@@ -355,12 +313,27 @@ export default {
       setT: null,
     };
   },
+  computed: {
+    ...mapState("invigilation", ["detailIds"]),
+  },
+  watch: {
+    $route: {
+      handler() {
+        this.initData();
+      },
+    },
+  },
   mounted() {
     this.initData();
   },
   methods: {
     async initData() {
-      this.getInvigilateDetail();
+      this.recordId = this.$route.params.recordId;
+      await this.getInvigilateDetail().catch(() => {});
+      await this.getStudentVideo().catch(() => {});
+      this.holding = false;
+    },
+    async getStudentVideo() {
       const res = await warningStudentDetail({ recordId: this.recordId });
       const records = res.data.data.map((item) => {
         item.src = `http://live.qmth.com.cn/live/${item.liveUrl.toLowerCase()}.flv`;
@@ -376,6 +349,100 @@ export default {
     async getInvigilateDetail() {
       const res = await invigilateDetail(this.recordId);
       this.detailInfo = res.data.data;
+      this.detailInfo.examStudentLogList = this.parseStudentLogs(
+        this.detailInfo.examStudentLogList
+      );
+      this.exceptionSummary = this.detailInfo.examStudentLogList
+        .filter((item) => item.viewType === "warning")
+        .slice(0, 3);
+    },
+    parseStudentLogs(examStudentLogList) {
+      const statusTypes = {
+        common: [
+          "FIRST_START",
+          "RESUME_START",
+          "IN_PROCESS",
+          "PREPARE",
+          "ANSWERING",
+          "BREAK_OFF",
+          "RESUME_PREPARE",
+          "FINISHED",
+          "FIRST_PREPARE",
+        ],
+        warning: [
+          "FACE_COUNT_ERROR",
+          "FACE_COMPARE_ERROR",
+          "EYE_CLOSE_ERROR",
+          "LIVENESS_ACTION_ERROR",
+          "REALNESS",
+        ],
+        exception: [
+          "NET_TIME_OUT",
+          "MACHING_STOP",
+          "NET_TIME_BREAK",
+          "SOFTWARE_STOP",
+          "POWER_CUT",
+        ],
+      };
+      let statusTypeMap = {};
+      Object.keys(statusTypes).map((k) => {
+        statusTypes[k].map((item) => {
+          statusTypeMap[item] = k;
+        });
+      });
+
+      const logs = examStudentLogList.map((item) => {
+        let info = { ...item };
+        info.endTime = formatDate("HH:mm:ss", new Date(info.createTime));
+        info.viewType = statusTypeMap[info.type] || "common";
+        if (info.remark && info.remark.includes('{"')) {
+          info.remark = JSON.parse(info.remark);
+          if (info.remark["MIN_CREATE_TIME"]) {
+            info.startTime = formatDate(
+              "HH:mm:ss",
+              new Date(info.remark["MIN_CREATE_TIME"])
+            );
+            info.durationTime = timeNumberToText(
+              info.createTime - info.remark["MIN_CREATE_TIME"]
+            );
+          }
+          let facePhoto = info.remark["FACE_VERIFY_PHOTO"]
+            ? [info.remark["FACE_VERIFY_PHOTO"]]
+            : "";
+          info.photos = info.remark["PHOTOS"] || facePhoto;
+        }
+        return info;
+      });
+      return logs;
+    },
+    changeStudent(type) {
+      let index = this.detailIds.indexOf(this.recordId);
+      if (type) {
+        if (index >= this.detailIds.length - 1) {
+          this.$message.error("当前没有下一个学生了");
+          return;
+        }
+        index++;
+      } else {
+        if (index <= 0) {
+          this.$message.error("当前没有上一个学生了");
+          return;
+        }
+        index--;
+      }
+
+      if (this.holding) return;
+      this.holding = true;
+
+      this.closeSubscribeVideo();
+      console.log(this.detailIds[index]);
+
+      this.$router.replace({
+        name: "WarningDetail",
+        params: {
+          recordId: this.detailIds[index],
+        },
+      });
     },
     toBreach() {
       this.curDetail = {
@@ -404,12 +471,15 @@ export default {
       if (!result) return;
 
       await invigilateFinish({
-        examRecordId: this.detailInfo.examRecordId,
-        type: true,
+        examRecordId: [this.detailInfo.examRecordId],
+        type: "INTERRUPT",
       });
       this.$message.success("强制收卷成功!");
       this.goBack();
     },
+    toSendTextMsg() {
+      this.$refs.WarningTextMessageDialog.open();
+    },
     // video relative
     initSubscribeVideo() {
       if (this.firstViewVideo.id) this.firstViewVideoReady = true;
@@ -668,8 +738,7 @@ export default {
   }
 }
 .student-exception {
-  padding-top: 30px;
-  overflow: auto;
+  margin-top: 30px;
   ul,
   li {
     margin: 0;
@@ -684,8 +753,8 @@ export default {
     vertical-align: top;
     font-size: 14px;
     position: relative;
-    padding-left: 38px;
-    width: 400px;
+    padding: 0 15px 0 38px;
+    width: 33.33%;
 
     > i {
       display: block;
@@ -733,7 +802,6 @@ export default {
     }
     p {
       margin: 0;
-      width: 304px;
       font-weight: 400;
       color: #626a82;
       line-height: 20px;
@@ -760,7 +828,7 @@ export default {
       margin: 0 30px 0 0;
     }
 
-    i.icon {
+    .el-button >>> i.icon {
       margin: 0;
     }
   }
@@ -786,7 +854,7 @@ export default {
 }
 .warning-history {
   display: flex;
-  min-height: 130px;
+  min-height: 100px;
   align-items: center;
   &-info {
     padding: 15px 30px 15px 0;
@@ -844,6 +912,7 @@ export default {
   &-media {
     padding: 10px 0 10px 20px;
     position: relative;
+    min-height: 100px;
     z-index: 8;
 
     &::before {
@@ -868,7 +937,14 @@ export default {
       height: 100px;
       margin: 5px 10px;
       border-radius: 6px;
-      background-color: #626a82;
+      background-color: #e8edf3;
+      overflow: hidden;
+
+      > img {
+        width: 100%;
+        height: 100%;
+        object-fit: contain;
+      }
     }
   }
 }

+ 95 - 0
src/features/invigilation/RealtimeMonitoring/WarningTextMessageDialog.vue

@@ -0,0 +1,95 @@
+<template>
+  <el-dialog
+    class="warning-text-message-dialog"
+    :visible.sync="dialogVisible"
+    width="540px"
+    title="文字提醒"
+    :close-on-press-escape="false"
+    :close-on-click-modal="false"
+    append-to-body
+    @open="opened"
+  >
+    <el-form
+      ref="modalFormComp"
+      :model="modalForm"
+      :rules="rules"
+      label-width="0"
+    >
+      <el-form-item prop="content">
+        <el-input
+          type="textarea"
+          v-model.trim="modalForm.content"
+          placeholder="请输入"
+          :maxlength="200"
+          :rows="5"
+          show-word-limit
+          clearable
+        ></el-input>
+      </el-form-item>
+    </el-form>
+    <div slot="footer" class="dialog-footer">
+      <el-button @click="cancel" plain>取消</el-button>
+      <el-button type="primary" :disabled="isSubmit" @click="confirm"
+        >确认</el-button
+      >
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { sendWarningMsg } from "@/api/invigilation";
+
+export default {
+  name: "warning-text-message-dialog",
+  props: {
+    recordId: {
+      type: String,
+    },
+  },
+  data() {
+    return {
+      dialogVisible: false,
+      isSubmit: false,
+      modalForm: { content: "" },
+      rules: {
+        content: [
+          {
+            required: true,
+            message: "请输入文字提醒内容",
+            trigger: "change",
+          },
+        ],
+      },
+    };
+  },
+  mounted() {},
+  methods: {
+    opened() {
+      this.modalForm.content = "";
+    },
+    cancel() {
+      this.dialogVisible = false;
+    },
+    open() {
+      this.dialogVisible = true;
+    },
+    async confirm() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const result = await sendWarningMsg({
+        content: this.modalForm.content,
+        type: "text",
+        examRecordId: this.recordId,
+      }).catch(() => {});
+
+      this.isSubmit = false;
+      if (!result) return;
+      this.$emit("modified");
+      this.cancel();
+    },
+  },
+};
+</script>

+ 1 - 1
src/features/invigilation/RealtimeMonitoring/handleRollupDialog.vue

@@ -110,7 +110,7 @@ export default {
       this.isSubmit = true;
       const result = await invigilateFinish({
         examRecordId: this.modalForm.ids,
-        type: true,
+        type: "MANUAL",
       }).catch(() => {});
 
       this.isSubmit = false;

+ 2 - 42
src/features/invigilation/ReexamApply/ReexamApply.vue

@@ -145,47 +145,7 @@ export default {
       examActivities: [],
       examRooms: [],
       examCourses: [],
-      dataList: [
-        {
-          courseCode: "F001",
-          courseName: "数学",
-          examActivityId: 11,
-          examId: 111,
-          examRecordId: 1111,
-          examStudentId: 11111,
-          identity: "000000000015",
-          name: "张示框",
-          reexamId: 1,
-          roomCode: "111",
-          roomName: "第一考场",
-        },
-        {
-          courseCode: "F001",
-          courseName: "数学",
-          examActivityId: 22,
-          examId: 222,
-          examRecordId: 2222,
-          examStudentId: 22222,
-          identity: "000000000016",
-          name: "张设置",
-          reexamId: 2,
-          roomCode: "111",
-          roomName: "第一考场",
-        },
-        {
-          courseCode: "F001",
-          courseName: "数学",
-          examActivityId: 33,
-          examId: 333,
-          examRecordId: 3333,
-          examStudentId: 33333,
-          identity: "000000000017",
-          name: "张看得",
-          reexamId: 3,
-          roomCode: "111",
-          roomName: "第一考场",
-        },
-      ],
+      dataList: [],
       selectStudents: [],
       multipleSelection: [],
     };
@@ -210,7 +170,7 @@ export default {
       const res = await reexamApplyList(datas);
 
       this.dataList = res.data.data.records;
-      this.total = res.data.data.records.total;
+      this.total = res.data.data.total;
     },
     toPage(page) {
       this.current = page;

+ 12 - 9
src/features/invigilation/ReexamChecked/ReexamChecked.vue

@@ -99,7 +99,7 @@
     </div>
 
     <el-table ref="TableList" :data="dataList">
-      <el-table-column prop="examroom" label="考场(代码)">
+      <el-table-column prop="roomName" label="考场(代码)">
         <span slot-scope="scope"
           >{{ scope.row.roomName }}({{ scope.row.roomCode }})</span
         >
@@ -108,14 +108,17 @@
       <el-table-column prop="courseName" label="科目名称"></el-table-column>
       <el-table-column prop="name" label="姓名"></el-table-column>
       <el-table-column prop="courseCode" label="科目代码"></el-table-column>
-      <el-table-column prop="orgExamName" label="原批次"></el-table-column>
-      <el-table-column prop="orgRoomName" label="原场次"></el-table-column>
-      <el-table-column prop="reasom" label="申请理由"></el-table-column>
-      <el-table-column prop="reasonTime" label="申请时间"></el-table-column>
-      <el-table-column prop="reasonUser" label="申请人"></el-table-column>
+      <el-table-column prop="examName" label="原批次"></el-table-column>
+      <el-table-column prop="examActivityCode" label="原场次"></el-table-column>
+      <el-table-column prop="reason" label="申请理由"></el-table-column>
+      <el-table-column prop="applyTime" label="申请时间"></el-table-column>
+      <el-table-column prop="applyName" label="申请人"></el-table-column>
       <el-table-column prop="auditingTime" label="审核时间"></el-table-column>
-      <el-table-column prop="status" label="审核结果"></el-table-column>
-      <el-table-column prop="auditingId" label="审核人"></el-table-column>
+      <el-table-column
+        prop="auditingStatusStr"
+        label="审核结果"
+      ></el-table-column>
+      <el-table-column prop="auditingName" label="审核人"></el-table-column>
       <el-table-column label="操作">
         <template slot-scope="scope">
           <el-button
@@ -206,7 +209,7 @@ export default {
       const res = await reexamCheckedList(datas);
 
       this.dataList = res.data.data.records;
-      this.total = res.data.data.records.total;
+      this.total = res.data.data.total;
     },
     toPage(page) {
       this.current = page;

+ 28 - 55
src/features/invigilation/ReexamPending/CheckReexamDialog.vue

@@ -9,30 +9,22 @@
     append-to-body
     @open="visibleChange"
   >
-    <el-form
-      ref="modalFormComp"
-      :model="modalForm"
-      :rules="rules"
-      label-width="120px"
-    >
-      <el-form-item prop="reexamId" label="证件号/姓名:">
-        <el-input v-model="modalForm.name" readonly></el-input>
+    <el-form ref="modalFormComp" :model="modalForm" label-width="120px">
+      <el-form-item label="证件号/姓名:">
+        <el-input v-model="modalForm.studentInfo" readonly></el-input>
       </el-form-item>
-      <el-form-item prop="reason" label="重考原因:">
-        <el-select
-          v-model="modalForm.reason"
-          placeholder="请选择"
-          :disabled="!isEdit"
-          clearable
-        >
+      <el-form-item label="重考原因:">
+        <!-- <el-select v-model="modalForm.reason" placeholder="请选择" disabled>
           <el-option
             v-for="(val, key) in REEXAM_REASON"
             :key="key"
             :value="key"
             :label="val"
           ></el-option>
-        </el-select>
+        </el-select> -->
+        <el-input v-model="modalForm.reasonDesc" readonly></el-input>
       </el-form-item>
+
       <el-form-item label="备注详情:">
         <el-input
           type="textarea"
@@ -61,27 +53,17 @@
       </el-form-item>
     </el-form>
 
-    <div slot="footer" class="dialog-footer">
-      <el-button type="danger" @click="confirm(0)">不同意</el-button>
-      <el-button type="primary" @click="confirm(1)">同意</el-button>
+    <div slot="footer" class="dialog-footer" v-if="isEdit">
+      <el-button type="danger" @click="confirm(1)">不同意</el-button>
+      <el-button type="primary" @click="confirm(0)">同意</el-button>
     </div>
   </el-dialog>
 </template>
 
 <script>
-import { checkReexamApply } from "@/api/invigilation";
+import { reexamCheckDetail, checkReexamApply } from "@/api/invigilation";
 import { REEXAM_REASON } from "@/constant/constants";
 
-const initModalForm = {
-  reexamId: "",
-  auditingStatus: 0,
-  auditingSuggest: "",
-  reason: "",
-  remark: "",
-  label: "",
-  status: 0,
-};
-
 export default {
   name: "reexam-check-dialog",
   props: {
@@ -94,7 +76,7 @@ export default {
   },
   computed: {
     isEdit() {
-      return !this.instance.status;
+      return this.modalForm.status === 1;
     },
     title() {
       return this.isEdit ? "重考审核" : "查看详情";
@@ -104,29 +86,19 @@ export default {
     return {
       dialogVisible: false,
       isSubmit: false,
-      modalForm: { ...initModalForm },
       REEXAM_REASON,
-      rules: {
-        reexamId: [
-          {
-            required: true,
-            message: "请选择审核记录",
-            trigger: "change",
-          },
-        ],
-        reason: [
-          {
-            required: true,
-            message: "请选择重考原因",
-            trigger: "change",
-          },
-        ],
-      },
+      modalForm: {},
     };
   },
   methods: {
     visibleChange() {
-      this.modalForm = Object.assign({}, initModalForm, this.instance);
+      this.getDetail();
+    },
+    async getDetail() {
+      const res = await reexamCheckDetail(this.instance.reexamId);
+      this.modalForm = res.data.data;
+      this.modalForm.reasonDesc = REEXAM_REASON[this.modalForm.reason];
+      this.modalForm.studentInfo = `${this.instance.identity}  ${this.instance.courseName}(${this.instance.courseCode}) ${this.instance.name}`;
     },
     cancel() {
       this.dialogVisible = false;
@@ -140,14 +112,15 @@ export default {
 
       if (this.isSubmit) return;
       this.isSubmit = true;
-      this.modalForm.auditingStatus = auditingStatus;
-      const data = await checkReexamApply(this.modalForm).catch(() => {
-        this.isSubmit = false;
-      });
+      const datas = {
+        auditingStatus,
+        auditingSuggest: this.modalForm.auditingSuggest,
+        reexamId: [this.modalForm.id],
+      };
+      const data = await checkReexamApply(datas).catch(() => {});
+      this.isSubmit = false;
 
       if (!data) return;
-
-      this.isSubmit = false;
       this.$message.success("操作成功!");
       this.$emit("modified");
       this.cancel();

+ 8 - 25
src/features/invigilation/ReexamPending/ReexamPending.vue

@@ -107,7 +107,7 @@
 
     <el-table ref="TableList" :data="dataList">
       <!-- <el-table-column type="selection" width="55"> </el-table-column> -->
-      <el-table-column prop="examroom" label="考场(代码)">
+      <el-table-column prop="roomName" label="考场(代码)">
         <span slot-scope="scope"
           >{{ scope.row.roomName }}({{ scope.row.roomCode }})</span
         >
@@ -116,11 +116,11 @@
       <el-table-column prop="courseName" label="科目名称"></el-table-column>
       <el-table-column prop="name" label="姓名"></el-table-column>
       <el-table-column prop="courseCode" label="科目代码"></el-table-column>
-      <el-table-column prop="orgExamName" label="原批次"></el-table-column>
-      <el-table-column prop="orgRoomName" label="原场次"></el-table-column>
-      <el-table-column prop="reasom" label="申请理由"></el-table-column>
-      <el-table-column prop="reasonTime" label="申请时间"></el-table-column>
-      <el-table-column prop="reasonUser" label="申请人"></el-table-column>
+      <el-table-column prop="examName" label="原批次"></el-table-column>
+      <el-table-column prop="examActivityCode" label="原场次"></el-table-column>
+      <el-table-column prop="reason" label="申请理由"></el-table-column>
+      <el-table-column prop="applyTime" label="申请时间"></el-table-column>
+      <el-table-column prop="applyName" label="申请人"></el-table-column>
       <el-table-column label="操作">
         <template slot-scope="scope">
           <el-button
@@ -186,23 +186,7 @@ export default {
       examRooms: [],
       examCourses: [],
       applyTime: "",
-      dataList: [
-        {
-          courseCode: "F001",
-          courseName: "数学",
-          examActivityId: 11,
-          examId: 111,
-          examRecordId: 1111,
-          examStudentId: 11111,
-          identity: "000000000015",
-          name: "张示框",
-          reexamId: 1,
-          roomCode: "111",
-          roomName: "第一考场",
-          remark: "这是remark",
-          status: 1,
-        },
-      ],
+      dataList: [],
       curReexam: {},
       multipleSelection: [],
     };
@@ -227,7 +211,7 @@ export default {
       const res = await reexamPendingList(datas);
 
       this.dataList = res.data.data.records;
-      this.total = res.data.data.records.total;
+      this.total = res.data.data.total;
     },
     toPage(page) {
       this.current = page;
@@ -258,7 +242,6 @@ export default {
       this.multipleSelection = val;
     },
     toDetail(row) {
-      console.log(row);
       this.curReexam = { ...row };
       this.$refs.CheckReexamDialog.open();
     },

+ 19 - 24
src/features/invigilation/StudentLogManage/StudentLogDetailDialog.vue

@@ -13,7 +13,7 @@
     <el-table ref="TableList" :data="dataList">
       <el-table-column prop="creatTime" label="发生时间"></el-table-column>
       <el-table-column prop="type" label="事件类型"></el-table-column>
-      <el-table-column prop="content" label="详情"></el-table-column>
+      <el-table-column prop="remark" label="详情"></el-table-column>
     </el-table>
     <div class="part-page">
       <el-pagination
@@ -32,7 +32,7 @@
 </template>
 
 <script>
-// import { updateCourse } from "../api";
+import { studentLogDetail } from "@/api/invigilation";
 
 export default {
   name: "student-log-detail-dialog",
@@ -48,34 +48,29 @@ export default {
       current: 1,
       total: 0,
       size: 10,
-      dataList: [
-        {
-          id: 1,
-          creatTime: "2020-08-02 15:12:24",
-          type: "进入考试",
-          content: "MAC地址:",
-        },
-        {
-          id: 2,
-          creatTime: "2020-08-02 15:12:24",
-          type: "网络断开",
-          content: "系统自动重试2次,网络已恢复",
-        },
-        {
-          id: 3,
-          creatTime: "2020-08-02 15:12:24",
-          type: "重新登录",
-          content: "MAC地址:",
-        },
-      ],
+      dataList: [],
     };
   },
   methods: {
     visibleChange() {
+      this.toPage(1);
+    },
+    async getList() {
+      const datas = {
+        examStudentId: this.detailId,
+        pageNumber: this.current - 1,
+        pageSize: this.size,
+      };
+
+      const res = await studentLogDetail(datas);
+
+      this.dataList = res.data.data.records;
+      this.total = res.data.data.total;
+    },
+    toPage(page) {
+      this.current = page;
       this.getList();
     },
-    getList() {},
-    toPage() {},
     cancel() {
       this.modalIsShow = false;
     },

+ 37 - 35
src/features/invigilation/StudentLogManage/StudentLogManage.vue

@@ -79,19 +79,20 @@
     </div>
 
     <el-table ref="TableList" :data="dataList">
-      <el-table-column prop="batchName" label="排序"></el-table-column>
-      <el-table-column prop="batchName" label="批次名称(ID)"></el-table-column>
-      <el-table-column prop="examName" label="场次ID"></el-table-column>
-      <el-table-column
-        prop="examName"
-        label="考场名称(代码)"
-      ></el-table-column>
-      <el-table-column prop="stdCardNo" label="证件号"></el-table-column>
-      <el-table-column prop="stdName" label="姓名"></el-table-column>
-      <el-table-column prop="subjectCode" label="科目(代码)">
-        <template slot-scope="scope">
-          <span>{{ scope.row.subjectName }}({{ scope.row.subjectCode }})</span>
-        </template>
+      <el-table-column type="index" label="排序"></el-table-column>
+      <el-table-column prop="examName" label="批次名称(ID)"></el-table-column>
+      <el-table-column prop="examActivityId" label="场次ID"></el-table-column>
+      <el-table-column prop="examroom" label="考场名称(代码)">
+        <span slot-scope="scope"
+          >{{ scope.row.roomName }}({{ scope.row.roomCode }})</span
+        >
+      </el-table-column>
+      <el-table-column prop="identity" label="证件号"></el-table-column>
+      <el-table-column prop="name" label="姓名"></el-table-column>
+      <el-table-column prop="courseCode" label="科目名称(代码)">
+        <span slot-scope="scope">
+          {{ scope.row.courseName }}({{ scope.row.courseCode }})
+        </span>
       </el-table-column>
       <el-table-column label="操作">
         <template slot-scope="scope">
@@ -126,7 +127,11 @@
 </template>
 
 <script>
-import { examBatchList, examActivityRoomList } from "@/api/invigilation";
+import {
+  studentLogList,
+  examBatchList,
+  examActivityRoomList,
+} from "@/api/invigilation";
 import StudentLogDetailDialog from "./StudentLogDetailDialog";
 
 export default {
@@ -139,7 +144,7 @@ export default {
         examId: null,
         roomCode: null,
         courseCode: null,
-        content: "",
+        name: "",
       },
       current: 1,
       total: 0,
@@ -149,30 +154,27 @@ export default {
       examActivities: [],
       examRooms: [],
       examCourses: [],
-      dataList: [
-        {
-          id: 1,
-          batchName: "第一批次",
-          examName: "第一场次",
-          examroom: "第一考场",
-          examId: "123456",
-          stdCardNo: "000000000000000008",
-          stdName: "张龙龙",
-          subjectName: "大学英语",
-          subjectCode: "10006",
-          strangeNumber: "0",
-          exceptionNumber: "2",
-          warningNumber: "2",
-          isDiscipline: "",
-          auditStatus: "未阅",
-        },
-      ],
+      dataList: [],
       curLogId: "",
     };
   },
   methods: {
-    getList() {},
-    toPage() {},
+    async getList() {
+      const datas = {
+        ...this.filter,
+        pageNumber: this.current - 1,
+        pageSize: this.size,
+      };
+
+      const res = await studentLogList(datas);
+
+      this.dataList = res.data.data.records;
+      this.total = res.data.data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
     async getExamBatchList() {
       const res = await examBatchList();
       this.examBatchs = res.data.data;

+ 41 - 20
src/features/invigilation/WainingManage/WainingManage.vue → src/features/invigilation/WarningManage/WarningManage.vue

@@ -1,9 +1,9 @@
 <template>
-  <div class="WainingManage">
+  <div class="warning-manage">
     <div class="part-box-head">
       <div class="part-box-head-left"><h1>预警提醒</h1></div>
-      <div class="part-box-head-right">
-        <span>考场名称:哈尔滨工业大学第十一考场</span>
+      <div class="part-box-head-right" v-if="curExamRoomName">
+        <span>考场名称:{{ curExamRoomName }}</span>
       </div>
     </div>
     <div class="part-filter">
@@ -27,6 +27,7 @@
             <el-select
               v-model="filter.roomCode"
               placeholder="请选择考场"
+              @change="roomChange"
               clearable
             >
               <el-option
@@ -118,7 +119,7 @@
             ></el-input-number>
           </el-form-item>
           <el-form-item>
-            <el-button type="primary" @click="toPage(1)">查询</el-button>
+            <el-button type="primary" @click="toSearch">查询</el-button>
           </el-form-item>
         </el-form>
         <div class="part-filter-form-action">
@@ -198,13 +199,13 @@ import {
   examBatchList,
   examActivityRoomList,
   invigilationWarningList,
-  batchInvigilation,
   clearInvigilationUnreadWarningList,
 } from "@/api/invigilation";
+import { mapActions } from "vuex";
 
 import { APPROVE_STATUS } from "@/constant/constants";
 export default {
-  name: "WainingManage",
+  name: "warning-manage",
   data() {
     return {
       filter: {
@@ -220,6 +221,7 @@ export default {
         maxWarningCount: undefined,
         minWarningCount: undefined,
       },
+      curExamRoomName: "",
       APPROVE_STATUS,
       multipleSelection: [],
       current: 1,
@@ -236,10 +238,11 @@ export default {
     this.initData();
   },
   methods: {
+    ...mapActions("invigilation", ["updateDetailIds"]),
     async initData() {
       await this.getExamBatchList();
       this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
-      this.toPage(1);
+      this.toSearch();
       this.getExamActivityRoomList();
     },
     async getList() {
@@ -252,12 +255,19 @@ export default {
       const res = await invigilationWarningList(datas);
 
       this.dataList = res.data.data.records;
-      this.total = res.data.data.records.total;
+      this.total = res.data.data.total;
     },
     toPage(page) {
       this.current = page;
       this.getList();
     },
+    toSearch() {
+      this.updateDetailIds({
+        filterData: this.filter,
+        fetchFunc: invigilationWarningList,
+      });
+      this.toPage(1);
+    },
     async getExamBatchList() {
       const user = this.$store.state.user;
       const userId =
@@ -280,27 +290,38 @@ export default {
       this.filter.courseCode = null;
       this.getExamActivityRoomList();
     },
+    roomChange() {
+      if (!this.filter.roomCode) {
+        this.curExamRoomName = "";
+        return;
+      }
+      const room = this.examRooms.find(
+        (item) => item.roomCode === this.filter.roomCode
+      );
+      this.curExamRoomName = room.roomName;
+    },
     handleSelectionChange(val) {
       this.multipleSelection = val;
     },
     async cleanUnread() {
-      await clearInvigilationUnreadWarningList();
+      await clearInvigilationUnreadWarningList({
+        approveStatus: 1,
+        warningIds: this.dataList
+          .filter((item) => !item.approveStatus)
+          .map((item) => item.warningId),
+      });
       this.$message.success("操作成功!");
       this.getList();
     },
-    async batchAction() {
-      if (!this.multipleSelection.length) {
-        this.$message.error("请先选择数据!");
-        return;
+    async toDetail(row) {
+      if (!row.approveStatus) {
+        await clearInvigilationUnreadWarningList({
+          approveStatus: 1,
+          warningIds: [row.warningId],
+        });
       }
-
-      await batchInvigilation();
-      this.$message.success("操作成功!");
-      this.getList();
-    },
-    toDetail(row) {
       this.$router.push({
-        name: "WainingDetail",
+        name: "WarningDetail",
         params: { recordId: row.examRecordId },
       });
     },

+ 2 - 2
src/features/invigilation/common/InvigilationStudent.vue

@@ -16,11 +16,11 @@
         <span>证件号:</span><span>{{ data.identity }}</span>
       </p>
       <p>
-        <span>答题进度:</span><span>{{ data.progress }}</span>
+        <span>答题进度:</span><span>{{ data.progress }}%</span>
       </p>
       <div class="student-time">
         <i class="icon icon-alarm-clock"></i>
-        <span>50:32:15</span>
+        <span>{{ data.remainTime }}</span>
       </div>
     </div>
   </div>

+ 1 - 1
src/features/invigilation/common/SummaryLine.vue

@@ -122,7 +122,7 @@ export default {
     async initData() {
       if (!this.examId) return;
       const res = await examPropCount(this.examId).catch(() => {});
-      this.examPropData = res.data.data || {};
+      this.examPropData = (res && res.data && res.data.data) || {};
       this.paramList = types[this.dataType].map((item) => {
         let info = paramInfo[item];
         return {

+ 6 - 1
src/router/index.js

@@ -48,7 +48,6 @@ const routes = [
         name: "Home",
         component: Home,
       },
-      ...invigilation,
     ],
   },
   {
@@ -167,6 +166,12 @@ const routes = [
       },
     ],
   },
+  {
+    path: "/invigilation",
+    name: "Invigilation",
+    component: Layout,
+    children: [...invigilation],
+  },
   {
     path: "/login",
     name: "Login",

+ 80 - 41
src/router/invigilation.js

@@ -1,88 +1,127 @@
-import ExamInvigilation from "../features/invigilation/ExamInvigilation/ExamInvigilation";
-import OnlinePatrol from "../features/invigilation/OnlinePatrol/OnlinePatrol";
-import RealtimeMonitoring from "../features/invigilation/RealtimeMonitoring/RealtimeMonitoring";
-import VideoCommunication from "../features/invigilation/RealtimeMonitoring/VideoCommunication";
-import WainingDetail from "../features/invigilation/RealtimeMonitoring/WainingDetail";
-import InvigilationDetail from "../features/invigilation/InvigilationDetail/InvigilationDetail";
-import WainingManage from "../features/invigilation/WainingManage/WainingManage";
-import ReexamApply from "../features/invigilation/ReexamApply/ReexamApply";
-import ProgressDetail from "../features/invigilation/ProgressDetail/ProgressDetail";
-import ReexamPending from "../features/invigilation/ReexamPending/ReexamPending";
-import ReexamChecked from "../features/invigilation/ReexamChecked/ReexamChecked";
-import ExamReport from "../features/invigilation/ExamReport/ExamReport";
-import StudentLogManage from "../features/invigilation/StudentLogManage/StudentLogManage";
+// import ExamInvigilation from "../features/invigilation/ExamInvigilation/ExamInvigilation";
+// import OnlinePatrol from "../features/invigilation/OnlinePatrol/OnlinePatrol";
+// import RealtimeMonitoring from "../features/invigilation/RealtimeMonitoring/RealtimeMonitoring";
+// import VideoCommunication from "../features/invigilation/RealtimeMonitoring/VideoCommunication";
+// import WarningDetail from "../features/invigilation/RealtimeMonitoring/WarningDetail";
+// import InvigilationDetail from "../features/invigilation/InvigilationDetail/InvigilationDetail";
+// import WarningManage from "../features/invigilation/WarningManage/WarningManage";
+// import ReexamApply from "../features/invigilation/ReexamApply/ReexamApply";
+// import ProgressDetail from "../features/invigilation/ProgressDetail/ProgressDetail";
+// import ReexamPending from "../features/invigilation/ReexamPending/ReexamPending";
+// import ReexamChecked from "../features/invigilation/ReexamChecked/ReexamChecked";
+// import ExamReport from "../features/invigilation/ExamReport/ExamReport";
+// import StudentLogManage from "../features/invigilation/StudentLogManage/StudentLogManage";
 
 const routes = [
   {
-    path: "/invigilation/exam-invigilation",
+    path: "exam-invigilation",
     name: "ExamInvigilation",
-    component: ExamInvigilation,
+    component: () =>
+      import(
+        /* webpackChunkName: "inspection" */ "../features/invigilation/ExamInvigilation/ExamInvigilation"
+      ),
   },
   {
-    path: "/invigilation/online-patrol",
+    path: "online-patrol",
     name: "OnlinePatrol",
-    component: OnlinePatrol,
+    component: () =>
+      import(
+        /* webpackChunkName: "inspection" */ "../features/invigilation/OnlinePatrol/OnlinePatrol"
+      ),
   },
   {
-    path: "/invigilation/realtime-monitoring",
+    path: "realtime-monitoring/:examId?",
     name: "RealtimeMonitoring",
-    component: RealtimeMonitoring,
+    component: () =>
+      import(
+        /* webpackChunkName: "monitor" */ "../features/invigilation/RealtimeMonitoring/RealtimeMonitoring"
+      ),
   },
   {
-    path: "/invigilation/video-communication",
+    path: "video-communication/:examId",
     name: "VideoCommunication",
-    component: VideoCommunication,
+    component: () =>
+      import(
+        /* webpackChunkName: "monitor" */ "../features/invigilation/RealtimeMonitoring/VideoCommunication"
+      ),
     meta: {
       relate: "RealtimeMonitoring",
     },
   },
   {
-    path: "/invigilation/waining-detail/:recordId",
-    name: "WainingDetail",
-    component: WainingDetail,
+    path: "warning-detail/:recordId",
+    name: "WarningDetail",
+    component: () =>
+      import(
+        /* webpackChunkName: "monitor" */ "../features/invigilation/RealtimeMonitoring/WarningDetail"
+      ),
     meta: {
       relate: "RealtimeMonitoring",
     },
   },
   {
-    path: "/invigilation/invigilation-detail",
+    path: "invigilation-detail",
     name: "InvigilationDetail",
-    component: InvigilationDetail,
+    component: () =>
+      import(
+        /* webpackChunkName: "invigilation" */ "../features/invigilation/InvigilationDetail/InvigilationDetail"
+      ),
   },
   {
-    path: "/invigilation/waining-manage",
-    name: "WainingManage",
-    component: WainingManage,
+    path: "warning-manage",
+    name: "WarningManage",
+    component: () =>
+      import(
+        /* webpackChunkName: "invigilation" */ "../features/invigilation/WarningManage/WarningManage"
+      ),
   },
   {
-    path: "/invigilation/reexam-apply",
+    path: "reexam-apply",
     name: "ReexamApply",
-    component: ReexamApply,
+    component: () =>
+      import(
+        /* webpackChunkName: "invigilation" */ "../features/invigilation/ReexamApply/ReexamApply"
+      ),
   },
   {
-    path: "/invigilation/progress-detail",
+    path: "progress-detail",
     name: "ProgressDetail",
-    component: ProgressDetail,
+    component: () =>
+      import(
+        /* webpackChunkName: "invigilation" */ "../features/invigilation/ProgressDetail/ProgressDetail"
+      ),
   },
   {
-    path: "/invigilation/reexam-pending",
+    path: "reexam-pending",
     name: "ReexamPending",
-    component: ReexamPending,
+    component: () =>
+      import(
+        /* webpackChunkName: "invigilation" */ "../features/invigilation/ReexamPending/ReexamPending"
+      ),
   },
   {
-    path: "/invigilation/reexam-checked",
+    path: "reexam-checked",
     name: "ReexamChecked",
-    component: ReexamChecked,
+    component: () =>
+      import(
+        /* webpackChunkName: "invigilation" */ "../features/invigilation/ReexamChecked/ReexamChecked"
+      ),
   },
   {
-    path: "/invigilation/exam-report",
+    path: "exam-report",
     name: "ExamReport",
-    component: ExamReport,
+    component: () =>
+      import(
+        /* webpackChunkName: "invigilation" */ "../features/invigilation/ExamReport/ExamReport"
+      ),
   },
   {
-    path: "/invigilation/student-log-manage",
+    path: "student-log-manage",
     name: "StudentLogManage",
-    component: StudentLogManage,
+    component: () =>
+      import(
+        /* webpackChunkName: "invigilation" */ "../features/invigilation/StudentLogManage/StudentLogManage"
+      ),
   },
 ];
 

+ 18 - 5
src/store/modules/invigilation.js

@@ -10,7 +10,7 @@ const state = {
       val: "",
       classes: "nav-item-tips-popover",
     },
-    WainingManage: {
+    WarningManage: {
       val: "",
       classes: "nav-item-tips-num",
     },
@@ -19,18 +19,22 @@ const state = {
       classes: "nav-item-tips-num",
     },
   },
+  detailIds: [],
 };
 
 const mutations = {
   setRealtimeMonitoring(state, val) {
     state.navTips.RealtimeMonitoring.val = val ? "考试" : null;
   },
-  setWainingManage(state, val) {
-    state.navTips.WainingManage.val = val;
+  setWarningManage(state, val) {
+    state.navTips.WarningManage.val = val;
   },
   setReexamPending(state, val) {
     state.navTips.ReexamPending.val = val;
   },
+  setDetailIds(state, detailIds) {
+    state.detailIds = detailIds;
+  },
 };
 
 const actions = {
@@ -38,14 +42,23 @@ const actions = {
     const res = await invigilateCount(datas);
     commit("setRealtimeMonitoring", res.data.data.count);
   },
-  async fetchWainingManageCount({ commit }, datas) {
+  async fetchWarningManageCount({ commit }, datas) {
     const res = await invigilationWarningCount(datas);
-    commit("setWainingManage", res.data.data.count);
+    commit("setWarningManage", res.data.data.count);
   },
   async fetchReexamPendingCount({ commit }, datas) {
     const res = await reexamPendingCount(datas);
     commit("setReexamPending", res.data.data.count);
   },
+  async updateDetailIds({ commit }, { filterData, fetchFunc }) {
+    const res = await fetchFunc({
+      ...filterData,
+      pageNumber: 0,
+      pageSize: 1000,
+    });
+    const ids = res.data.data.records.map((item) => item.examRecordId);
+    commit("setDetailIds", [...new Set(ids)]);
+  },
 };
 
 export default { namespaced: true, state, mutations, actions };

+ 25 - 0
src/styles/base.scss

@@ -168,6 +168,31 @@ body {
   height: 100% !important;
   width: 100% !important;
 }
+.el-icon-btn {
+  &.el-button {
+    padding: 8px;
+  }
+  i.icon {
+    margin: 0;
+  }
+}
+// avarar
+.avatar-default {
+  width: 100%;
+  height: 100%;
+  background-color: #e8edf3;
+  position: relative;
+
+  > i {
+    display: block;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    font-size: 80px;
+    color: #8c94ac;
+  }
+}
 // tips-info
 .tips-info {
   position: relative;

+ 7 - 0
src/styles/element-ui-custom.scss

@@ -26,6 +26,13 @@
     }
   }
 }
+// el-textarea
+.el-textarea {
+  .el-textarea__inner {
+    border-color: #e8edf3;
+    background-color: #f0f4f9;
+  }
+}
 
 // datepicker
 .el-date-editor.el-input__inner {

+ 35 - 0
src/utils/utils.js

@@ -108,6 +108,41 @@ export function formatDate(format = "YYYY/MM/DD HH:mm:ss", date = new Date()) {
   return format;
 }
 
+/**
+ *  获取时间长度文字
+ * @param {Number} timeNumber 时间数值,单位:毫秒
+ */
+export function timeNumberToText(timeNumber) {
+  const DAY_TIME = 24 * 60 * 60 * 1000;
+  const HOUR_TIME = 60 * 60 * 1000;
+  const MINUTE_TIME = 60 * 1000;
+  const SECOND_TIME = 1000;
+  let [day, hour, minute, second] = [0, 0, 0, 0];
+  let residueTime = timeNumber;
+
+  if (residueTime >= DAY_TIME) {
+    day = Math.floor(residueTime / DAY_TIME);
+    residueTime -= day * DAY_TIME;
+    day += "天";
+  }
+  if (residueTime >= HOUR_TIME) {
+    hour = Math.floor(residueTime / HOUR_TIME);
+    residueTime -= hour * HOUR_TIME;
+    hour += "小时";
+  }
+  if (residueTime >= MINUTE_TIME) {
+    minute = Math.floor(residueTime / MINUTE_TIME);
+    residueTime -= minute * MINUTE_TIME;
+    minute += "分钟";
+  }
+  if (residueTime >= SECOND_TIME) {
+    second = Math.round(residueTime / SECOND_TIME);
+    second += "秒";
+  }
+
+  return [day, hour, minute, second].filter((item) => !!item).join("");
+}
+
 export function deepCopy(obj) {
   return JSON.parse(JSON.stringify(obj));
 }

+ 3 - 3
src/views/Layout/Layout.vue

@@ -40,7 +40,7 @@ export default {
   methods: {
     ...mapMutations("inviligation", [
       "fetchRealtimeMonitoringCount",
-      "fetchWainingManageCount",
+      "fetchWarningManageCount",
       "fetchReexamPendingCount",
     ]),
     navChange(name) {
@@ -121,9 +121,9 @@ export default {
           valid: false,
           func: this.fetchRealtimeMonitoringCount,
         },
-        WainingManage: {
+        WarningManage: {
           valid: false,
-          func: this.fetchWainingManageCount,
+          func: this.fetchWarningManageCount,
         },
         ReexamPending: {
           valid: false,

+ 1 - 1
src/views/Layout/components/menu.js

@@ -107,7 +107,7 @@ const invigilationMenuConfig = [
       },
       {
         title: "预警提醒",
-        name: "WainingManage",
+        name: "WarningManage",
       },
       {
         title: "重考申请",