Эх сурвалжийг харах

监考端页面交互优化

zhangjie 4 жил өмнө
parent
commit
9b68b3f3b8

+ 7 - 0
src/api/invigilation.js

@@ -198,6 +198,13 @@ export function clearInvigilationUnreadWarningList({
     warningIds,
   });
 }
+export function clearInvigilationFilterUnreadWarningList(datas) {
+  const data = pickBy(datas, paramFilter);
+  return httpApp.post(
+    "/api/admin/invigilate/warn/approveStatus/list/update?" +
+      object2QueryString(data)
+  );
+}
 
 // warning-detail
 // 获取当前学生直播视频流

+ 1 - 1
src/features/invigilation/ExamInvigilation/ExamInvigilation.vue

@@ -380,7 +380,7 @@ export default {
         item.liveUrl = item.monitorLiveUrl
           ? `${domain}/live/${item.monitorLiveUrl.toLowerCase()}.flv`
           : "";
-        item.progress = item.progress ? item.progress * 100 : 0;
+        item.progress = item.progress ? Math.round(item.progress * 100) : 0;
         return item;
       });
     },

+ 11 - 1
src/features/invigilation/ExamReport/ExamReport.vue

@@ -101,6 +101,7 @@ import ReportException from "./ReportException";
 import ReportReexam from "./ReportReexam";
 import ReportBreach from "./ReportBreach";
 import ReportCancalBreach from "./ReportCancalBreach";
+import { mapState, mapActions, mapMutations } from "vuex";
 
 export default {
   name: "exam-report",
@@ -160,6 +161,7 @@ export default {
     };
   },
   computed: {
+    ...mapState("invigilation", ["selectedExamId"]),
     curComponent() {
       return this.curTab.key ? `report-${this.curTab.key}` : null;
     },
@@ -168,13 +170,21 @@ export default {
     this.initData();
   },
   methods: {
+    ...mapActions("invigilation", ["updateSelectedExamId"]),
+    ...mapMutations("invigilation", ["setSelectedExamId"]),
     async initData() {
       await this.getExamBatchList();
-      this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
+      this.updateSelectedExamId({
+        exams: this.examBatchs,
+        selectedExamId: this.selectedExamId,
+      });
+      this.filter.examId = this.selectedExamId;
+      // this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
       this.getExamActivityRoomList();
       this.curTab = { ...this.tabs[0] };
     },
     search() {
+      this.setSelectedExamId(this.filter.examId);
       this.$refs.ReportDetail.getData();
     },
     selectTab(tab) {

+ 11 - 4
src/features/invigilation/InvigilationDetail/InvigilationDetail.vue

@@ -251,7 +251,7 @@ import {
   STUDENT_BEHAVIOR_STATUS,
 } from "@/constant/constants";
 import SummaryLine from "../common/SummaryLine";
-import { mapActions, mapMutations } from "vuex";
+import { mapState, mapActions, mapMutations } from "vuex";
 
 export default {
   name: "InvigilationDetail",
@@ -288,6 +288,7 @@ export default {
     };
   },
   computed: {
+    ...mapState("invigilation", ["selectedExamId"]),
     user() {
       return this.$store.state.user;
     },
@@ -299,11 +300,16 @@ export default {
     this.initData();
   },
   methods: {
-    ...mapActions("invigilation", ["updateDetailIds"]),
-    ...mapMutations("invigilation", ["setDetailIds"]),
+    ...mapActions("invigilation", ["updateDetailIds", "updateSelectedExamId"]),
+    ...mapMutations("invigilation", ["setDetailIds", "setSelectedExamId"]),
     async initData() {
       await this.getExamBatchList();
-      this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
+      this.updateSelectedExamId({
+        exams: this.examBatchs,
+        selectedExamId: this.selectedExamId,
+      });
+      this.filter.examId = this.selectedExamId;
+      // this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
       this.toSearch();
       this.getExamActivityRoomList();
     },
@@ -324,6 +330,7 @@ export default {
       this.getList();
     },
     async toSearch() {
+      this.setSelectedExamId(this.filter.examId);
       this.current = 1;
       await this.getList();
 

+ 1 - 1
src/features/invigilation/OnlinePatrol/OnlinePatrol.vue

@@ -319,7 +319,7 @@ export default {
 
       const res = await patrolList(datas);
       this.dataList = res.data.data.records.map((item) => {
-        item.progress = item.progress ? item.progress * 100 : 0;
+        item.progress = item.progress ? Math.round(item.progress * 100) : 0;
         return item;
       });
       this.total = res.data.data.total;

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

@@ -136,6 +136,7 @@ import {
 } from "@/api/invigilation";
 import SummaryLine from "../common/SummaryLine";
 import { downloadBlob } from "@/utils/utils";
+import { mapState, mapActions, mapMutations } from "vuex";
 
 export default {
   name: "progress-detail",
@@ -160,13 +161,23 @@ export default {
       isDownload: false,
     };
   },
+  computed: {
+    ...mapState("invigilation", ["selectedExamId"]),
+  },
   mounted() {
     this.initData();
   },
   methods: {
+    ...mapActions("invigilation", ["updateSelectedExamId"]),
+    ...mapMutations("invigilation", ["setSelectedExamId"]),
     async initData() {
       await this.getExamBatchList();
-      this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
+      this.updateSelectedExamId({
+        exams: this.examBatchs,
+        selectedExamId: this.selectedExamId,
+      });
+      this.filter.examId = this.selectedExamId;
+      // this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
       this.toPage(1);
       this.getExamActivityRoomList();
     },
@@ -183,6 +194,7 @@ export default {
       this.total = res.data.data.total;
     },
     toPage(page) {
+      this.setSelectedExamId(this.filter.examId);
       this.current = page;
       this.getList();
     },

+ 12 - 1
src/features/invigilation/RealtimeMonitoring/ExamBatchDialog.vue

@@ -23,6 +23,7 @@
 <script>
 import { examMonitorBatchList } from "@/api/invigilation";
 import { dateFormatForAPI } from "@/utils/utils";
+import { mapState, mapMutations } from "vuex";
 
 export default {
   name: "exam-batch-dialog",
@@ -37,10 +38,14 @@ export default {
       userId: this.$store.state.user.id,
     };
   },
+  computed: {
+    ...mapState("invigilation", ["selectedExamId"]),
+  },
   mounted() {
     this.getExamList();
   },
   methods: {
+    ...mapMutations("invigilation", ["setSelectedExamId"]),
     async getExamList() {
       const res = await examMonitorBatchList({
         userId: this.userId,
@@ -57,12 +62,16 @@ export default {
           isExaming: dnow > item.startTime && dnow < item.endTime,
         };
       });
-
+      // 选择优先级:initExamid > selectedExamId > 第一个正在考试的考试 > 列表第一个考试
       let selectedExam = null;
       if (this.initExamid) {
         selectedExam = this.examBatchs.find(
           (item) => item.id === this.initExamid
         );
+      } else if (this.selectedExamId) {
+        selectedExam = this.examBatchs.find(
+          (item) => item.id === this.selectedExamId
+        );
       } else {
         // 优先取当前第一个正在考试的考试
         selectedExam = this.examBatchs.find((item) => item.isExaming);
@@ -71,6 +80,7 @@ export default {
       selectedExam = selectedExam || this.examBatchs[0];
 
       this.examBatchId = selectedExam && selectedExam.id;
+      this.setSelectedExamId(this.examBatchId);
 
       this.$emit("confirm", selectedExam);
     },
@@ -85,6 +95,7 @@ export default {
         this.$message.error("请选择考试批次!");
         return;
       }
+      this.setSelectedExamId(this.examBatchId);
       const exam = this.examBatchs.find((item) => item.id === this.examBatchId);
       const dnow = Date.now();
       exam.isExaming = dnow > exam.startTime && dnow < exam.endTime;

+ 26 - 17
src/features/invigilation/RealtimeMonitoring/RealtimeMonitoring.vue

@@ -486,7 +486,7 @@ export default {
         item.liveUrl = item.monitorLiveUrl
           ? `${domain}/live/${item.monitorLiveUrl.toLowerCase()}.flv`
           : "";
-        item.progress = item.progress ? item.progress * 100 : 0;
+        item.progress = item.progress ? Math.round(item.progress * 100) : 0;
         return item;
       });
 
@@ -522,28 +522,37 @@ export default {
     async fetchWarningNotice() {
       if (!this.filter.examId) return;
       let noticeCaches = {};
+      const showAlert = async (item) => {
+        return new Promise((resolve) => {
+          setTimeout(() => {
+            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>
+              `,
+            });
+            resolve();
+          }, 200);
+        });
+      };
 
       const res = await invigilationWarningMessage(this.filter.examId);
-      res.data.data.forEach((item) => {
+      for (let i = 0, len = res.data.data.length; i < len; i++) {
+        const item = res.data.data[i];
         const stdKey = item.examRecordId;
         if (!noticeCaches[stdKey]) {
           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>
-            `,
-          });
+          await showAlert(item);
         }
-      });
+      }
     },
     handleSelectionChange(val) {
       console.log(val);

+ 13 - 1
src/features/invigilation/ReexamApply/ReexamApply.vue

@@ -133,6 +133,7 @@ import {
   reexamApplyList,
 } from "@/api/invigilation";
 import ApplyReexamDialog from "./ApplyReexamDialog";
+import { mapState, mapActions, mapMutations } from "vuex";
 
 export default {
   name: "ReexamApply",
@@ -158,13 +159,23 @@ export default {
       multipleSelection: [],
     };
   },
+  computed: {
+    ...mapState("invigilation", ["selectedExamId"]),
+  },
   mounted() {
     this.initData();
   },
   methods: {
+    ...mapActions("invigilation", ["updateSelectedExamId"]),
+    ...mapMutations("invigilation", ["setSelectedExamId"]),
     async initData() {
       await this.getExamBatchList();
-      this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
+      // this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
+      this.updateSelectedExamId({
+        exams: this.examBatchs,
+        selectedExamId: this.selectedExamId,
+      });
+      this.filter.examId = this.selectedExamId;
       this.toPage(1);
       this.getExamActivityRoomList();
     },
@@ -181,6 +192,7 @@ export default {
       this.total = res.data.data.total;
     },
     toPage(page) {
+      this.setSelectedExamId(this.filter.examId);
       this.current = page;
       this.getList();
     },

+ 13 - 2
src/features/invigilation/ReexamChecked/ReexamChecked.vue

@@ -167,6 +167,7 @@ import {
 } from "@/api/invigilation";
 import { REEXAM_REASON } from "@/constant/constants";
 import CheckReexamDialog from "../ReexamPending/CheckReexamDialog";
+import { mapState, mapActions, mapMutations } from "vuex";
 
 export default {
   name: "reexam-pending",
@@ -197,13 +198,23 @@ export default {
       multipleSelection: [],
     };
   },
+  computed: {
+    ...mapState("invigilation", ["selectedExamId"]),
+  },
   mounted() {
     this.initData();
   },
   methods: {
+    ...mapActions("invigilation", ["updateSelectedExamId"]),
+    ...mapMutations("invigilation", ["setSelectedExamId"]),
     async initData() {
       await this.getExamBatchList();
-      this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
+      this.updateSelectedExamId({
+        exams: this.examBatchs,
+        selectedExamId: this.selectedExamId,
+      });
+      this.filter.examId = this.selectedExamId;
+      // this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
       this.toPage(1);
       this.getExamActivityRoomList();
     },
@@ -220,6 +231,7 @@ export default {
       this.total = res.data.data.total;
     },
     toPage(page) {
+      this.setSelectedExamId(this.filter.examId);
       this.current = page;
       this.getList();
     },
@@ -246,7 +258,6 @@ export default {
       this.filter.reasonEndTime = dvals[1] || null;
     },
     toDetail(row) {
-      console.log(row);
       this.curReexam = { ...row };
       this.$refs.CheckReexamDialog.open();
     },

+ 17 - 3
src/features/invigilation/ReexamPending/ReexamPending.vue

@@ -169,7 +169,7 @@ import {
 } from "@/api/invigilation";
 import { REEXAM_REASON } from "@/constant/constants";
 import CheckReexamDialog from "./CheckReexamDialog";
-import { mapActions } from "vuex";
+import { mapState, mapActions, mapMutations } from "vuex";
 
 export default {
   name: "reexam-pending",
@@ -200,14 +200,27 @@ export default {
       multipleSelection: [],
     };
   },
+  computed: {
+    ...mapState("invigilation", ["selectedExamId"]),
+  },
   mounted() {
     this.initData();
   },
   methods: {
-    ...mapActions("invigilation", ["fetchReexamPendingCount"]),
+    ...mapActions("invigilation", [
+      "fetchReexamPendingCount",
+      "updateSelectedExamId",
+    ]),
+    ...mapMutations("invigilation", ["setSelectedExamId"]),
+
     async initData() {
       await this.getExamBatchList();
-      this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
+      this.updateSelectedExamId({
+        exams: this.examBatchs,
+        selectedExamId: this.selectedExamId,
+      });
+      this.filter.examId = this.selectedExamId;
+      // this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
       this.toPage(1);
       this.getExamActivityRoomList();
     },
@@ -224,6 +237,7 @@ export default {
       this.total = res.data.data.total;
     },
     toPage(page) {
+      this.setSelectedExamId(this.filter.examId);
       this.current = page;
       this.getList();
     },

+ 13 - 1
src/features/invigilation/StudentLogManage/StudentLogManage.vue

@@ -126,6 +126,7 @@ import {
   examActivityRoomList,
 } from "@/api/invigilation";
 import StudentLogDetailDialog from "./StudentLogDetailDialog";
+import { mapState, mapActions, mapMutations } from "vuex";
 
 export default {
   name: "StudentLogManage",
@@ -151,13 +152,23 @@ export default {
       curLogId: "",
     };
   },
+  computed: {
+    ...mapState("invigilation", ["selectedExamId"]),
+  },
   mounted() {
     this.initData();
   },
   methods: {
+    ...mapActions("invigilation", ["updateSelectedExamId"]),
+    ...mapMutations("invigilation", ["setSelectedExamId"]),
     async initData() {
       await this.getExamBatchList();
-      this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
+      // this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
+      this.updateSelectedExamId({
+        exams: this.examBatchs,
+        selectedExamId: this.selectedExamId,
+      });
+      this.filter.examId = this.selectedExamId;
       this.getExamActivityRoomList();
       this.toPage(1);
     },
@@ -174,6 +185,7 @@ export default {
       this.total = res.data.data.total;
     },
     toPage(page) {
+      this.setSelectedExamId(this.filter.examId);
       this.current = page;
       this.getList();
     },

+ 25 - 13
src/features/invigilation/WarningManage/WarningManage.vue

@@ -145,7 +145,7 @@
       :data="dataList"
       @selection-change="handleSelectionChange"
     >
-      <el-table-column type="selection" width="55" align="center" />
+      <!-- <el-table-column type="selection" width="55" align="center" /> -->
       <el-table-column prop="examName" label="批次"></el-table-column>
       <el-table-column prop="examActivityCode" label="场次"></el-table-column>
       <el-table-column prop="roomName" label="考场"> </el-table-column>
@@ -206,11 +206,11 @@ import {
   examBatchList,
   examActivityRoomList,
   invigilationWarningList,
-  clearInvigilationUnreadWarningList,
+  clearInvigilationFilterUnreadWarningList,
 } from "@/api/invigilation";
-import { mapActions, mapMutations } from "vuex";
-
 import { APPROVE_STATUS } from "@/constant/constants";
+import { mapState, mapActions, mapMutations } from "vuex";
+
 export default {
   name: "WarningManage",
   data() {
@@ -243,6 +243,7 @@ export default {
     };
   },
   computed: {
+    ...mapState("invigilation", ["selectedExamId"]),
     user() {
       return this.$store.state.user;
     },
@@ -254,11 +255,20 @@ export default {
     this.initData();
   },
   methods: {
-    ...mapActions("invigilation", ["updateDetailIds"]),
-    ...mapMutations("invigilation", ["setDetailIds"]),
+    ...mapActions("invigilation", [
+      "updateDetailIds",
+      "updateSelectedExamId",
+      "fetchWarningManageCount",
+    ]),
+    ...mapMutations("invigilation", ["setDetailIds", "setSelectedExamId"]),
     async initData() {
       await this.getExamBatchList();
-      this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
+      this.updateSelectedExamId({
+        exams: this.examBatchs,
+        selectedExamId: this.selectedExamId,
+      });
+      this.filter.examId = this.selectedExamId;
+      // this.filter.examId = this.examBatchs[0] && this.examBatchs[0].id;
       this.toSearch();
       this.getExamActivityRoomList();
     },
@@ -279,6 +289,7 @@ export default {
       this.getList();
     },
     async toSearch() {
+      this.setSelectedExamId(this.filter.examId);
       this.current = 1;
       await this.getList();
       if (this.total > this.size) {
@@ -323,14 +334,15 @@ export default {
       this.multipleSelection = val;
     },
     async cleanUnread() {
-      await clearInvigilationUnreadWarningList({
-        approveStatus: 1,
-        warningIds: this.dataList
-          .filter((item) => !item.approveStatus)
-          .map((item) => item.warningId),
-      });
+      await clearInvigilationFilterUnreadWarningList(this.filter);
       this.$message.success("操作成功!");
       this.getList();
+
+      const user = this.$store.state.user;
+      const datas = user.roleCodes.includes("INVIGILATE")
+        ? { userId: user.id }
+        : {};
+      this.fetchWarningManageCount(datas);
     },
     toDetail(row) {
       const router = this.IS_INVIGILATE

+ 3 - 2
src/features/invigilation/common/SummaryLine.vue

@@ -123,8 +123,9 @@ export default {
       const res = await examPropCount(this.examId).catch(() => {});
       this.examPropData = (res && res.data && res.data.data) || {};
       if (this.examPropData["completionRate"])
-        this.examPropData["completionRate"] =
-          this.examPropData["completionRate"] * 100;
+        this.examPropData["completionRate"] = Math.round(
+          this.examPropData["completionRate"] * 100
+        );
       this.paramList = types[this.dataType].map((item) => {
         let info = paramInfo[item];
         return {

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

@@ -22,6 +22,7 @@ const state = {
   },
   detailIds: [],
   liveDomains: [],
+  selectedExamId: null,
 };
 
 const mutations = {
@@ -40,6 +41,9 @@ const mutations = {
   setLiveDomains(state, liveDomains) {
     state.liveDomains = liveDomains;
   },
+  setSelectedExamId(state, selectedExamId) {
+    state.selectedExamId = selectedExamId;
+  },
 };
 
 const actions = {
@@ -68,6 +72,20 @@ const actions = {
     const res = await getLiveDomains();
     commit("setLiveDomains", res.data.data || []);
   },
+  updateSelectedExamId(
+    { commit },
+    { exams, selectedExamId, defaultExamId = null }
+  ) {
+    if (!exams.length) return;
+    const firstExamId = exams[0].id;
+    const selectedExam = exams.find((item) => item.id === selectedExamId);
+    const defaultExam = exams.find((item) => item.id === defaultExamId);
+    const newSelectedExamId =
+      (defaultExam && defaultExam.id) ||
+      (selectedExam && selectedExam.id) ||
+      firstExamId;
+    commit("setSelectedExamId", newSelectedExamId);
+  },
 };
 
 export default { namespaced: true, state, mutations, actions };

+ 47 - 7
src/views/Layout/Layout.vue

@@ -31,7 +31,11 @@ export default {
     return {
       curMenus: [],
       navs: [],
+      curNavName: "",
       validRoutes: [],
+      loopSetTs: [],
+      loopFuncList: [],
+      loopRunning: false,
     };
   },
   created() {
@@ -44,10 +48,20 @@ export default {
       "fetchReexamPendingCount",
     ]),
     navChange(name) {
+      if (this.curNavName === name) return;
+
+      this.curNavName = name;
       const nav = this.navs.find((item) => item.name === name);
       this.curMenus = nav.children || [];
 
-      if (name === "Invigilation") this.initNavTips();
+      if (name === "Invigilation") {
+        this.initNavTips();
+        this.loopRunning = true;
+        this.timerUpdatePage();
+      } else {
+        this.loopRunning = false;
+        this.clearLoopSetTs();
+      }
     },
     async getMenu() {
       const res = await sysMenu();
@@ -119,6 +133,31 @@ export default {
       });
       return navs;
     },
+    clearLoopSetTs() {
+      if (!this.loopSetTs.length) return;
+      this.loopSetTs.forEach((sett) => {
+        clearTimeout(sett);
+      });
+      this.loopSetTs = [];
+    },
+    async timerUpdatePage() {
+      this.clearLoopSetTs();
+      if (!this.loopRunning) return;
+
+      const user = this.$store.state.user;
+      const datas = user.roleCodes.includes("INVIGILATE")
+        ? { userId: user.id }
+        : {};
+
+      const fetchAll = this.loopFuncList.map((item) => item.call(this, datas));
+      await Promise.all(fetchAll).catch(() => {});
+
+      this.loopSetTs.push(
+        setTimeout(() => {
+          this.timerUpdatePage();
+        }, 10 * 1000)
+      );
+    },
     initNavTips() {
       let validNav = {
         RealtimeMonitoring: {
@@ -143,16 +182,17 @@ export default {
         });
       });
 
-      const user = this.$store.state.user;
-      const datas = user.roleCodes.includes("INVIGILATE")
-        ? { userId: user.id }
-        : {};
-
+      let loopFuncList = [];
       validNavNames.forEach((item) => {
-        if (validNav[item].valid) validNav[item].func.call(this, datas);
+        if (validNav[item].valid) loopFuncList.push(validNav[item].func);
       });
+      this.loopFuncList = loopFuncList;
     },
   },
+  beforeDestroy() {
+    this.loopRunning = false;
+    this.clearLoopSetTs();
+  },
 };
 </script>
 

+ 1 - 0
src/views/Layout/components/NavBar.vue

@@ -124,6 +124,7 @@ export default {
 
       if (!result) return;
 
+      this.$store.commit("invigilation/setSelectedExamId", null);
       this.logout("?cause=点击退出");
     },
     checkDocIsFullscreen() {