Pārlūkot izejas kodu

大屏调试完毕

zhangjie 2 gadi atpakaļ
vecāks
revīzija
1b75719eca

+ 184 - 591
src/features/invigilation/ExamInvigilation/ExamInvigilation.vue

@@ -1,629 +1,222 @@
 <template>
-  <div class="exam-invigilation">
-    <div class="invigilation-analysis">
-      <div class="part-box-head">
-        <div class="part-box-head-left"><h1>考情监控</h1></div>
-        <div class="part-box-head-right" @click="exitFullscreen">
-          <i class="icon icon-full-screen"></i>退出全屏
+  <div class="exam-invigilation invi-container">
+    <div class="invi-body">
+      <h1 class="invi-body-title">考情监控</h1>
+      <div class="invi-summary">
+        <div class="invi-summary-item">
+          <h4>在线考试人数</h4>
+          <p>
+            <animate-number :value="summary.onlineCount"></animate-number>
+          </p>
         </div>
-      </div>
-      <div class="invigilation-summary">
-        <div class="invigilation-summary-item">
-          <div class="part-box">
-            <h5>
-              <span>在线(人)</span>
-              <el-popover
-                placement="top-start"
-                popper-class="warning-popover"
-                width="200"
-                trigger="hover"
-                content="当前在线考试人数。"
-              >
-                <i class="el-icon-question" slot="reference"></i>
-              </el-popover>
-            </h5>
-            <p>{{ monitorCount.onlineCount }}</p>
-          </div>
+        <div class="invi-summary-item">
+          <h4>考试中人数</h4>
+          <p>
+            <animate-number :value="summary.examingCount"></animate-number>
+          </p>
         </div>
-        <div class="invigilation-summary-item">
-          <div class="part-box">
-            <h5>
-              <span>待考(人)</span>
-              <el-popover
-                placement="top-start"
-                popper-class="warning-popover"
-                width="200"
-                trigger="hover"
-                content="已进入待考界面等待开考的考生。"
-              >
-                <i class="el-icon-question" slot="reference"></i>
-              </el-popover>
-            </h5>
-            <p>{{ monitorCount.waitingCount }}</p>
-          </div>
+        <div class="invi-summary-item">
+          <h4>待考人数</h4>
+          <p>
+            <animate-number :value="summary.waitingCount"></animate-number>
+          </p>
         </div>
-        <div class="invigilation-summary-item">
-          <div class="part-box">
-            <h5>
-              <span>考试中(人)</span>
-              <el-popover
-                placement="top-start"
-                popper-class="warning-popover"
-                width="200"
-                trigger="hover"
-                content="正在答题的考生。"
-              >
-                <i class="el-icon-question" slot="reference"></i>
-              </el-popover>
-            </h5>
-            <p>{{ monitorCount.examingCount }}</p>
-          </div>
+        <div class="invi-summary-item">
+          <h4>预警总数</h4>
+          <p><animate-number :value="summary.warnCount"></animate-number></p>
         </div>
-        <div class="invigilation-summary-item">
-          <div class="part-box">
-            <h5>
-              <span>通讯故障</span>
-              <el-popover
-                placement="top-start"
-                popper-class="warning-popover"
-                width="200"
-                trigger="hover"
-                content="考生端出现断网、断电、软硬件故障等异常导致考生端与监考端无法正常连接的考生。"
-              >
-                <i class="el-icon-question" slot="reference"></i>
-              </el-popover>
-            </h5>
-            <p
-              :class="{
-                'color-danger': monitorCount.exceptionCountChange > 0,
-                'color-success': monitorCount.exceptionCountChange < 0,
-              }"
-            >
-              {{ monitorCount.exceptionCount }}
-              <i
-                :class="[
-                  'icon',
-                  {
-                    'icon-arrows-up': monitorCount.exceptionCountChange > 0,
-                    'icon-arrows-down': monitorCount.exceptionCountChange < 0,
-                  },
-                ]"
-              ></i>
-            </p>
-          </div>
+        <div class="invi-summary-item">
+          <h4>违纪总数</h4>
+          <p>
+            <animate-number :value="summary.breachCount"></animate-number>
+          </p>
         </div>
-        <div class="invigilation-summary-item">
-          <div class="part-box">
-            <h5>
-              <span>预警</span>
-              <el-popover
-                placement="top-start"
-                popper-class="warning-popover"
-                width="200"
-                trigger="hover"
-                content="实时预警数量及升跌趋势(后1分钟增量与前1分钟增量对比,大于为升势,小于为跌势)。"
-              >
-                <i class="el-icon-question" slot="reference"></i>
-              </el-popover>
-            </h5>
-            <p
-              :class="{
-                'color-danger': monitorCount.warnCountChange > 0,
-                'color-success': monitorCount.warnCountChange < 0,
-              }"
-            >
-              {{ monitorCount.warnCount }}
-              <i
-                :class="[
-                  'icon',
-                  {
-                    'icon-arrows-up': monitorCount.warnCountChange > 0,
-                    'icon-arrows-down': monitorCount.warnCountChange < 0,
-                  },
-                ]"
-              ></i>
-            </p>
+      </div>
+      <div class="invi-analysis">
+        <div class="invi-analysis-item">
+          <div class="invi-analysis-box invi-time">
+            <h2 class="invi-analysis-title">考生在线/预警时间趋势</h2>
+            <div class="invi-time-chart invi-chart">
+              <vue-charts
+                v-if="inviTimeChartOption"
+                :options="inviTimeChartOption"
+                autoresize
+              ></vue-charts>
+            </div>
           </div>
         </div>
       </div>
-      <div class="invigilation-online part-box">
-        <h3 class="invigilation-part-title">
-          <span>各机构在线考试人数分布</span>
-          <el-popover
-            placement="top-start"
-            popper-class="warning-popover"
-            width="200"
-            trigger="hover"
-            content="各二级子机构当前在线考试人数分布。"
-          >
-            <i class="el-icon-question" slot="reference"></i>
-          </el-popover>
-        </h3>
-        <echart-render
-          :chart-data="onlineData"
-          chart-type="bar"
-        ></echart-render>
-      </div>
-      <div class="invigilation-warning">
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <div class="part-box">
-              <h3 class="invigilation-part-title">
-                <span>机构预警分布</span>
-                <el-popover
-                  placement="top-start"
-                  popper-class="warning-popover"
-                  width="200"
-                  trigger="hover"
-                  content="各二级子机构当前预警数量分布。"
-                >
-                  <i class="el-icon-question" slot="reference"></i>
-                </el-popover>
-              </h3>
-              <echart-render
-                :chart-data="orgWarningData"
-                chart-type="pieAnnulus"
-              ></echart-render>
+      <div class="invi-analysis invi-analysis-2">
+        <div class="invi-analysis-item">
+          <div class="invi-analysis-box invi-types">
+            <h2 class="invi-analysis-title">预警类型排行</h2>
+            <div class="invi-types-table">
+              <table class="invi-table">
+                <colgroup>
+                  <col width="70" />
+                  <col width="120" />
+                  <col width="20" />
+                </colgroup>
+                <tr v-for="item in inviTypesList" :key="item.id">
+                  <td>{{ item.type }}</td>
+                  <td>
+                    <el-progress
+                      :stroke-width="6"
+                      :show-text="false"
+                      :percentage="item.percentage"
+                    ></el-progress>
+                  </td>
+                  <td>{{ item.warnCount }}</td>
+                </tr>
+              </table>
             </div>
-          </el-col>
-          <el-col :span="12">
-            <div class="part-box">
-              <h3 class="invigilation-part-title">
-                <span>预警类型分布</span>
-                <el-popover
-                  placement="top-start"
-                  popper-class="warning-popover"
-                  width="200"
-                  trigger="hover"
-                  content="当前考试累计预警的类型分析。"
-                >
-                  <i class="el-icon-question" slot="reference"></i>
-                </el-popover>
-              </h3>
-              <echart-render
-                :chart-data="typeWarningData"
-                chart-type="pieAnnulus"
-              ></echart-render>
+            <div class="invi-types-chart invi-chart">
+              <div v-if="inviTypesChartOption" class="invi-chart-title">
+                预警类型占比
+              </div>
+              <vue-charts
+                v-if="inviTypesChartOption"
+                :options="inviTypesChartOption"
+                autoresize
+              ></vue-charts>
             </div>
-          </el-col>
-        </el-row>
-      </div>
-      <div class="invigilation-trend part-box">
-        <h3 class="invigilation-part-title">
-          <span>预警时间趋势</span>
-          <el-popover
-            placement="top-start"
-            popper-class="warning-popover"
-            width="200"
-            trigger="hover"
-            content="按时间维度呈现预警数量的升跌走势图。"
-          >
-            <i class="el-icon-question" slot="reference"></i>
-          </el-popover>
-        </h3>
-        <echart-render
-          :chart-data="warningTrendData"
-          chart-type="line"
-        ></echart-render>
+          </div>
+        </div>
+        <div class="invi-analysis-item">
+          <div class="invi-analysis-box invi-area">
+            <h2 class="invi-analysis-title">预警地区排行</h2>
+            <div class="invi-area-table">
+              <table class="invi-table">
+                <colgroup>
+                  <col width="40" />
+                  <col width="150" />
+                  <col width="30" />
+                </colgroup>
+                <tr v-for="item in inviAreaList" :key="item.id">
+                  <td>{{ item.province }}</td>
+                  <td>
+                    <el-progress
+                      :stroke-width="6"
+                      :show-text="false"
+                      :percentage="item.percentage"
+                    ></el-progress>
+                  </td>
+                  <td>{{ item.warnCount }}</td>
+                </tr>
+              </table>
+            </div>
+            <div class="invi-area-chart invi-chart">
+              <div v-if="inviAreaChartOption" class="invi-chart-title">
+                考生分布
+              </div>
+              <vue-charts
+                v-if="inviAreaChartOption"
+                :options="inviAreaChartOption"
+                autoresize
+              ></vue-charts>
+            </div>
+          </div>
+        </div>
       </div>
-      <div class="invigilation-message part-box">
-        <h3 class="invigilation-part-title">
-          <span>预警消息</span>
-          <el-popover
-            placement="top-start"
-            popper-class="warning-popover"
-            width="200"
-            trigger="hover"
-            content="系统实时预警提示滚屏。"
-          >
-            <i class="el-icon-question" slot="reference"></i>
-          </el-popover>
-        </h3>
-        <div class="message-list">
-          <div
-            class="message-item"
-            v-for="(item, index) in messages"
-            :key="index"
-          >
-            <span><i class="el-icon-warning"></i></span>
-            <span>{{ item.time | datetimeFilter }}</span>
-            <span>{{ item.content }}</span>
+      <div class="invi-analysis">
+        <div class="invi-analysis-item">
+          <div class="invi-analysis-box invi-student">
+            <h2 class="invi-analysis-title">考试动态预警</h2>
+            <div v-if="inviStudentWainList.length" class="invi-student-table">
+              <table class="invi-table">
+                <colgroup>
+                  <col width="60" />
+                  <col width="60" />
+                  <col width="140" />
+                  <col width="100" />
+                  <col width="70" />
+                </colgroup>
+                <tbody>
+                  <tr>
+                    <th>时间</th>
+                    <th>姓名</th>
+                    <th>证件号</th>
+                    <th>预警类型</th>
+                    <th>处理状态</th>
+                  </tr>
+                  <tr v-for="item in inviStudentWainList" :key="item.id">
+                    <td>{{ item.createTime | timeFilter }}</td>
+                    <td>
+                      <div class="td-cont" style="width: 60px;">
+                        {{ item.name }}
+                      </div>
+                    </td>
+                    <td>{{ item.identity }}</td>
+                    <td>
+                      <div
+                        class="td-cont"
+                        style="width: 140px;"
+                        :title="item.info"
+                      >
+                        {{ item.info }}
+                      </div>
+                    </td>
+                    <td>{{ item.approveStatus }}</td>
+                  </tr>
+                </tbody>
+              </table>
+            </div>
           </div>
         </div>
       </div>
     </div>
-    <div class="invigilation-list">
-      <h3>实时监控台</h3>
-      <invigilation-student
-        ref="InvigilationStudent"
-        v-for="item in students"
+    <div class="invi-monitor">
+      <h3 class="invi-monitor-title">实时监控台</h3>
+      <div
+        v-for="item in studentVideoList"
         :key="item.examStudentId"
-        :data="item"
-        show-type="patrol"
-        :max-retry-count="1"
-        @muted-change="videoAllMuted"
-      ></invigilation-student>
+        class="invi-monitor-item"
+      >
+        <invigilation-student
+          ref="InvigilationStudent"
+          :data="item"
+          show-type="patrol"
+          :max-retry-count="1"
+          detail-name="详情"
+          @muted-change="videoAllMuted"
+        ></invigilation-student>
+      </div>
     </div>
   </div>
 </template>
 
 <script>
-import EchartRender from "../common/EchartRender";
 import InvigilationStudent from "../common/InvigilationStudent";
-import {
-  examInvigilationCount,
-  examInvigilationWarnDistribution,
-  examInvigilationWarnTrend,
-  examInvigilationVideoRandomList,
-  examInvigilationWarnMessage,
-} from "@/api/invigilation";
-import { mapState } from "vuex";
-import timeMixin from "@/mixins/timeMixin";
+import VueCharts from "@/plugins/VueCharts";
+import AnimateNumber from "@/components/AnimateNumber";
+import examInviMixins from "./examInviMixins";
 
 export default {
   name: "exam-invigilation",
-  components: { EchartRender, InvigilationStudent },
-  mixins: [timeMixin],
+  components: { InvigilationStudent, VueCharts, AnimateNumber },
+  mixins: [examInviMixins],
   data() {
     return {
-      monitorCount: {
-        onlineCount: 0,
-        waitingCount: 0,
-        examingCount: 0,
-        exceptionCount: 0,
-        warnCount: 0,
-        exceptionCountChange: false,
-        warnCountChange: false,
-      },
-      onlineData: {
-        dataList: [],
-        type: "light", // light or dark
-      },
-      orgWarningData: {
-        dataList: [],
-        type: "light",
-      },
-      typeWarningData: {
-        dataList: [],
-        type: "light",
-      },
-      warningTrendData: {
-        dataList: [],
-        type: "light",
-      },
-      loopRunning: false,
-      students: [],
-      messages: [],
+      colorType: "white",
     };
   },
-  computed: {
-    ...mapState("invigilation", ["liveDomains"]),
-    isFullScreen() {
-      return this.$store.state.isFullScreen;
-    },
-  },
   watch: {
-    isFullScreen(val) {
-      this.fullScreenChange(val);
+    isFullScreen: {
+      immediate: true,
+      handler(val, oldVal) {
+        if (val !== oldVal && val) {
+          this.$router.replace({ name: "ExamInvigilationFull" });
+        }
+      },
     },
   },
-  mounted() {
-    this.loopRunning = true;
-    this.initData();
-  },
   methods: {
-    async initData() {
-      if (!this.loopRunning) return;
-      this.clearSetTs();
-
-      const fetchAll = [
-        this.getCount(),
-        this.getWarnDistribution(),
-        this.getWarnTrend(),
-      ];
-      await Promise.all(fetchAll).catch(() => {});
-
-      this.fullScreenChange(this.checkDocIsFullscreen());
-
-      await this.getVideoList();
-      await this.getMessageList();
-
-      this.addSetTime(() => {
-        this.initData();
-      }, 10 * 1000);
-    },
-    async getCount() {
-      const res = await examInvigilationCount();
-      const data = res.data.data;
-      this.monitorCount = {
-        onlineCount: data.onlineCount,
-        waitingCount: data.waitingCount,
-        examingCount: data.examingCount,
-        exceptionCount: data.exceptionCount,
-        warnCount: data.warnCount,
-        exceptionCountChange:
-          data.exceptionCount - this.monitorCount.exceptionCount,
-        warnCountChange: data.warnCount - this.monitorCount.warnCount,
-      };
-      this.onlineData.dataList = data.orgExamingCount.map((item) => {
-        return {
-          name: item.orgName,
-          count: item.count * 1,
-        };
-      });
-    },
-    async getWarnDistribution() {
-      const res = await examInvigilationWarnDistribution();
-      const data = res.data.data;
-      this.orgWarningData.dataList = data.orgDistribution.map((item) => {
-        return {
-          name: item.orgName,
-          count: item.count,
-        };
-      });
-      this.typeWarningData.dataList = data.typeDistribution.map((item) => {
-        return {
-          name: item.type,
-          count: item.count * 1,
-        };
-      });
-    },
-    async getWarnTrend() {
-      const res = await examInvigilationWarnTrend();
-      this.warningTrendData.dataList = res.data.data.map((item) => {
-        return {
-          name: item.hour,
-          count: item.count * 1,
-        };
-      });
-    },
-    async getVideoList() {
-      const res = await examInvigilationVideoRandomList({ randomNum: 4 });
-      const domainLen = this.liveDomains.length;
-      this.students = res.data.data.map((item, index) => {
-        const domain = domainLen ? this.liveDomains[index % domainLen] : "";
-        item.liveUrl = item.monitorLiveUrl
-          ? `${domain}/live/${item.monitorLiveUrl.toLowerCase()}.flv`
-          : "";
-        item.progress = item.progress ? Math.round(item.progress * 100) : 0;
-        return item;
-      });
+    initData() {
+      this.intervalSummary();
+      this.intervalCommonData();
+      this.intervalStudentWain();
+      this.intervalTimeTrend();
     },
-    async getMessageList() {
-      const res = await examInvigilationWarnMessage();
-      this.messages = res.data.data;
-    },
-    // fullscreen
-    exitFullscreen() {
-      const exitFullscreen =
-        document.exitFullscreen ||
-        document.mozCancelFullScreen ||
-        document.webkitCancelFullScreen;
-
-      exitFullscreen.call(document);
-    },
-    checkDocIsFullscreen() {
-      return (
-        document.fullscreenElement ||
-        document.msFullscreenElement ||
-        document.mozFullscreenElement ||
-        document.webkitFullscreenElement
-      );
-    },
-    fullScreenChange(isFullScreen) {
-      const colorType = isFullScreen ? "dark" : "light";
-      this.onlineData.type = colorType;
-      this.orgWarningData.type = colorType;
-      this.typeWarningData.type = colorType;
-      this.warningTrendData.type = colorType;
-    },
-    videoAllMuted() {
-      this.$refs.InvigilationStudent.forEach((refInst) => {
-        refInst.mutedPlayer(true);
-      });
-    },
-  },
-  beforeDestroy() {
-    this.loopRunning = false;
-    this.clearSetTs();
   },
 };
 </script>
-
-<style lang="scss" scoped>
-.exam-invigilation {
-  position: relative;
-  padding-right: 310px;
-  min-width: 1250px;
-  .part-box-head-right {
-    display: none;
-  }
-  .part-box {
-    position: relative;
-  }
-  .invigilation-part-title {
-    position: absolute;
-    top: 20px;
-    left: 20px;
-    font-size: 16px;
-    font-weight: 500;
-    z-index: 9;
-  }
-  .el-icon-question {
-    margin-left: 5px;
-    color: #bdc8da;
-  }
-}
-.invigilation-list {
-  position: absolute;
-  top: 0;
-  right: 0;
-  width: 280px;
-  height: 100%;
-  border-radius: 6px;
-  z-index: 9;
-  overflow-y: auto;
-  overflow-x: hidden;
-
-  > h3 {
-    font-size: 18px;
-    font-weight: 600;
-    line-height: 25px;
-    margin-bottom: 20px;
-  }
-  .invigilation-student {
-    background-color: #fff;
-  }
-}
-.invigilation-analysis {
-  min-width: 940px;
-  .part-box-head-left h1 {
-    line-height: 25px;
-  }
-}
-.invigilation-summary {
-  margin: 0 -10px;
-  font-size: 0;
-
-  &-item {
-    display: inline-block;
-    vertical-align: top;
-    width: 20%;
-    padding: 0 10px;
-    font-size: 14px;
-    color: #202b4b;
-
-    h5 {
-      font-size: 14px;
-      height: 20px;
-      color: #626a82;
-      margin: 0;
-    }
-    p {
-      font-size: 32px;
-      line-height: 51px;
-      font-weight: 600;
-      margin: 0;
-    }
-    .icon {
-      margin-top: -4px;
-    }
-
-    &:first-child {
-      .part-box {
-        background-color: #3a93fb;
-      }
-      h5 {
-        color: #fff;
-      }
-      p {
-        color: #fff;
-        font-weight: 900;
-        font-size: 36px;
-      }
-    }
-  }
-}
-.invigilation-online {
-  height: 360px;
-}
-.invigilation-warning {
-  .part-box {
-    height: 282px;
-    padding-top: 50px;
-    padding-bottom: 10px;
-  }
-}
-.invigilation-trend {
-  height: 327px;
-}
-.invigilation-message {
-  min-height: 160px;
-  padding-top: 70px;
-  .message-item {
-    margin-bottom: 15px;
-
-    span {
-      line-height: 20px;
-
-      &:first-child {
-        color: #fe5863;
-      }
-
-      &:nth-of-type(2) {
-        margin: 0 20px 0 12px;
-        color: #8c94ac;
-      }
-    }
-  }
-}
-</style>
-<style lang="scss">
-// fullscreen style
-.app-fullscreen {
-  .part-box {
-    background: #2d325a;
-  }
-
-  .exam-invigilation {
-    margin: -30px;
-    padding-right: 280px;
-    color: #fff;
-    .part-box-head-right {
-      display: block;
-      cursor: pointer;
-      color: #737aae;
-      > i {
-        margin-right: 8px;
-        margin-top: -2px;
-      }
-    }
-    .part-box-head-left > h1 {
-      color: #fff;
-    }
-    .el-icon-question {
-      color: #737aae;
-    }
-  }
-
-  .invigilation-analysis {
-    padding: 30px;
-    background: #25294a;
-  }
-  .invigilation-list {
-    background: #25294a;
-    border-radius: 0;
-    padding-top: 30px;
-    padding-right: 20px;
-
-    .invigilation-student {
-      background: #2d325a;
-      border: 1px solid #2d325a;
-    }
-    .student-info {
-      &-time {
-        background: #353c70;
-      }
-      > p {
-        color: #737aae;
-      }
-    }
-  }
-  .invigilation-summary {
-    &-item {
-      h5 {
-        color: #737aae;
-      }
-      p {
-        color: #fff;
-      }
-
-      &:first-child {
-        .el-icon-question {
-          color: #fff;
-        }
-      }
-    }
-  }
-}
-</style>

+ 17 - 248
src/features/invigilation/ExamInvigilation/ExamInvigilationFull.vue

@@ -198,268 +198,37 @@
 </template>
 
 <script>
-import { mapState } from "vuex";
 import TextClock from "../common/TextClock";
 import InvigilationStudent from "../common/InvigilationStudent";
 import VueCharts from "@/plugins/VueCharts";
 import AnimateNumber from "@/components/AnimateNumber";
-import {
-  getInviTypesOption,
-  getInviAreaOption,
-  getInviTimeOption,
-  getInviMapOption,
-} from "./chartOpt";
-// import {
-//   examInvigilationCount,
-//   examInvigilationWarnDistribution,
-//   examInvigilationWarnTrend,
-//   examInvigilationVideoRandomList,
-//   examInvigilationWarnMessage,
-//   examinationMonitorMapList,
-//   examinationMonitorAreaList,
-// } from "@/api/invigilation";
-import {
-  examInvigilationCount,
-  examInvigilationWarnDistribution,
-  examInvigilationWarnTrend,
-  examInvigilationVideoRandomList,
-  examInvigilationWarnMessage,
-  examinationMonitorMapList,
-  examinationMonitorAreaList,
-} from "./datas";
-import { calcSum } from "@/utils/utils";
+import examInviMixins from "./examInviMixins";
 
 export default {
   name: "exam-invigilation-full",
   components: { TextClock, InvigilationStudent, VueCharts, AnimateNumber },
-  data() {
-    return {
-      schoolName: "",
-      summary: {
-        onlineCount: "",
-        waitingCount: "",
-        examingCount: "",
-        breachCount: "",
-        warnCount: "",
-        id: "",
-        name: "",
-        code: "",
+  mixins: [examInviMixins],
+  watch: {
+    isFullScreen: {
+      immediate: true,
+      handler(val, oldVal) {
+        if (val !== oldVal && !val) {
+          this.$router.replace({ name: "ExamInvigilation" });
+        }
       },
-      studentVideoList: [],
-      inviMapList: [],
-      inviMapChartOption: null,
-      inviTypesList: [],
-      inviTypesChartOption: null,
-      inviAreaList: [],
-      inviAreaChartOption: null,
-      inviTimeList: [],
-      inviTimeChartOption: null,
-      inviStudentWainList: [],
-      curMapAreaInd: 0,
-      // setTs
-      setTsMap: {
-        summary: [],
-        common: [],
-        studengWain: [],
-        timeTrend: [],
-        areaMap: [],
-        areaMapDet: [],
-      },
-    };
-  },
-  computed: {
-    ...mapState("invigilation", ["liveDomains"]),
-  },
-  mounted() {
-    this.initData();
+    },
   },
   beforeDestroy() {
-    this.clearSetTs();
+    this.exitFullscreen();
   },
   methods: {
-    initData() {
-      this.intervalSummary();
-      this.intervalCommonData();
-      this.intervalStudentWain();
-      this.intervalTimeTrend();
-      this.intervalMap();
-    },
-    addSetTime(typeName, action, time = 1 * 1000) {
-      this.setTsMap[typeName].push(setTimeout(action, time));
-    },
-    clearSetTs(typeName) {
-      if (typeName) {
-        if (!this.setTsMap[typeName] || !this.setTsMap[typeName].length) return;
-        this.setTsMap[typeName].forEach((t) => clearTimeout(t));
-        this.setTsMap[typeName] = [];
-        return;
-      }
-      Object.keys(this.setTsMap).forEach((k) => {
-        this.setTsMap[k].forEach((t) => clearTimeout(t));
-        this.setTsMap[k] = [];
-      });
-    },
-    videoAllMuted() {
-      this.$refs.InvigilationStudent.forEach((refInst) => {
-        refInst.mutedPlayer(true);
-      });
-    },
-    // interval
-    async intervalSummary() {
-      const typeName = "summary";
-      this.clearSetTs(typeName);
-
-      await this.getSummary();
-      this.addSetTime(typeName, this.intervalSummary, 3 * 1000);
-    },
-    async intervalCommonData() {
-      const typeName = "common";
-      this.clearSetTs(typeName);
-
-      await this.getInviTypesList();
-      await this.getInviAreaList();
-      await this.getStudentVideoList();
-
-      this.addSetTime(typeName, this.intervalCommonData, 10 * 1000);
-    },
-    async intervalStudentWain() {
-      const typeName = "studengWain";
-      this.clearSetTs(typeName);
-
-      await this.getStudentWainList();
-      this.addSetTime(typeName, this.intervalStudentWain, 5 * 1000);
-    },
-    async intervalTimeTrend() {
-      const typeName = "timeTrend";
-      this.clearSetTs(typeName);
+    exitFullscreen() {
+      const exitFullscreen =
+        document.exitFullscreen ||
+        document.mozCancelFullScreen ||
+        document.webkitCancelFullScreen;
 
-      await this.getInviTimeList();
-      this.addSetTime(typeName, this.intervalTimeTrend, 60 * 1000);
-    },
-    async intervalMap() {
-      const typeName = "areaMap";
-      this.clearSetTs(typeName);
-
-      await this.getMapList();
-      this.intervalMapDet();
-      let inTime = this.inviMapList.length * 5 * 1000;
-      inTime = inTime || 5 * 1000;
-      this.addSetTime(typeName, this.intervalMap, inTime);
-    },
-    intervalMapDet() {
-      const typeName = "areaMapDet";
-      this.clearSetTs(typeName);
-      if (!this.inviMapList.length) return;
-
-      if (this.curMapAreaInd !== 0) {
-        this.$refs.InviMapChart.dispatchAction({
-          type: "hideTip",
-        });
-        this.$refs.InviMapChart.dispatchAction({
-          type: "mapUnSelect",
-          dataIndex: this.curMapAreaInd - 1,
-        });
-      }
-      this.$refs.InviMapChart.dispatchAction({
-        type: "showTip",
-        seriesIndex: 0,
-        dataIndex: this.curMapAreaInd,
-      });
-      this.$refs.InviMapChart.dispatchAction({
-        type: "mapSelect",
-        dataIndex: this.curMapAreaInd,
-      });
-
-      this.curMapAreaInd++;
-      if (this.curMapAreaInd >= this.inviMapList.length) {
-        this.curMapAreaInd = 0;
-      } else {
-        this.addSetTime(typeName, this.intervalMapDet, 5 * 1000);
-      }
-    },
-    // fetch action
-    getMaxNum(nums) {
-      return Math.max.apply(null, nums);
-    },
-    async getSummary() {
-      // interval 3s
-      const res = await examInvigilationCount();
-      const data = res.data.data;
-      this.summary = Object.assign({}, this.summary, data);
-    },
-    async getStudentVideoList() {
-      // interval 10s
-      const res = await examInvigilationVideoRandomList({ randomNum: 4 });
-      const domainLen = this.liveDomains.length;
-      this.studentVideoList = res.data.data.map((item, index) => {
-        const domain = domainLen ? this.liveDomains[index % domainLen] : "";
-        item.liveUrl = item.monitorLiveUrl
-          ? `${domain}/live/${item.monitorLiveUrl.toLowerCase()}.flv`
-          : "";
-        item.progress = item.progress ? Math.round(item.progress * 100) : 0;
-        return item;
-      });
-    },
-    async getMapList() {
-      // interval list 5s
-      const res = await examinationMonitorMapList();
-      const dataList = res.data.data;
-      this.inviMapList = dataList.map((item) => {
-        item.selected = false;
-        return item;
-      });
-      this.inviMapChartOption = getInviMapOption(this.inviMapList);
-    },
-    async getInviTypesList() {
-      // interval 10s
-      const res = await examInvigilationWarnDistribution();
-      const dataList = res.data.data;
-      const maxNum = this.getMaxNum(dataList.map((item) => item.warnCount));
-      let inviTypesList = dataList.map((item) => {
-        return {
-          ...item,
-          percentage: Math.floor((100 * item.warnCount) / maxNum),
-        };
-      });
-      inviTypesList.sort((a, b) => b.warnCount - a.warnCount);
-      this.inviTypesList = inviTypesList;
-      this.inviTypesChartOption = getInviTypesOption(this.inviTypesList);
-    },
-    async getInviAreaList() {
-      // interval 10s
-      const res = await examinationMonitorAreaList();
-      const dataList = res.data.data;
-      const maxNum = this.getMaxNum(dataList.map((item) => item.warnCount));
-      let inviAreaList = dataList.map((item) => {
-        return {
-          ...item,
-          percentage: Math.floor((100 * item.warnCount) / maxNum),
-        };
-      });
-      inviAreaList.sort((a, b) => b.warnCount - a.warnCount);
-      this.inviAreaList = inviAreaList.slice(0, 5);
-      const otherAreaCount = calcSum(
-        inviAreaList.slice(5).map((item) => item.warnCount)
-      );
-      const chartDataList = [
-        ...this.inviAreaList,
-        { country: "中国", province: "其他", warnCount: otherAreaCount },
-      ];
-
-      this.inviAreaChartOption = getInviAreaOption(chartDataList);
-    },
-    async getInviTimeList() {
-      // interval 60s
-      const res = await examInvigilationWarnTrend();
-      const dataList = res.data.data;
-      this.inviTimeList = dataList;
-      this.inviTimeChartOption = getInviTimeOption(dataList);
-    },
-    async getStudentWainList() {
-      // // interval 5s
-      const res = await examInvigilationWarnMessage();
-      const dataList = res.data.data;
-      this.inviStudentWainList = dataList;
+      exitFullscreen.call(document);
     },
   },
 };

+ 29 - 13
src/features/invigilation/ExamInvigilation/chartOpt.js

@@ -109,29 +109,45 @@ export function getInviAreaOption(dataList) {
   };
 }
 
-export function getInviTimeOption(dataList) {
+export function getInviTimeOption(dataList, colorType) {
   if (!dataList.length) return;
 
+  const colorSet = {
+    white: {
+      gridBorderColor: "#e9e9e9",
+      legendTextColor: "#2c3e50",
+      axisLineColor: "#e9e9e9",
+      axisLabelColor: "#2c3e50",
+    },
+    dark: {
+      gridBorderColor: "#292C38",
+      legendTextColor: "rgba(225, 230, 239, 0.5)",
+      axisLineColor: "#292C38",
+      axisLabelColor: "#E1E6EF",
+    },
+  };
+  const color = colorSet[colorType];
+
   return {
     animation: animationIsOpen,
     grid: {
       show: true,
-      top: "22%",
-      bottom: "15%",
-      left: "8%",
-      right: "5%",
-      borderColor: "#292C38",
+      top: 50,
+      bottom: 20,
+      left: 50,
+      right: 30,
+      borderColor: color.gridBorderColor,
     },
     legend: {
       show: true,
-      top: 17,
-      right: 20,
+      top: 10,
+      right: 30,
       itemGap: 20,
       itemWidth: 16,
       itemHeight: 4,
       icon: "roundRect",
       textStyle: {
-        color: "rgba(225, 230, 239, 0.5)",
+        color: color.legendTextColor,
         fontSize: 12,
         padding: [0, 0, 0, 5],
       },
@@ -157,11 +173,11 @@ export function getInviTimeOption(dataList) {
       },
       axisLine: {
         lineStyle: {
-          color: "#292C38",
+          color: color.axisLineColor,
         },
       },
       axisLabel: {
-        color: "#E1E6EF",
+        color: color.axisLabelColor,
         fontSize: 10,
       },
       axisTick: {
@@ -174,7 +190,7 @@ export function getInviTimeOption(dataList) {
       splitLine: {
         show: true,
         lineStyle: {
-          color: "#292C38",
+          color: color.axisLineColor,
         },
       },
       axisLine: {
@@ -182,7 +198,7 @@ export function getInviTimeOption(dataList) {
       },
       axisLabel: {
         fontSize: 10,
-        color: "#E1E6EF",
+        color: color.axisLabelColor,
       },
       axisTick: {
         show: false,

+ 263 - 0
src/features/invigilation/ExamInvigilation/examInviMixins.js

@@ -0,0 +1,263 @@
+import { mapState } from "vuex";
+import {
+  getInviTypesOption,
+  getInviAreaOption,
+  getInviTimeOption,
+  getInviMapOption,
+} from "./chartOpt";
+import {
+  examInvigilationCount,
+  examInvigilationWarnDistribution,
+  examInvigilationWarnTrend,
+  examInvigilationVideoRandomList,
+  examInvigilationWarnMessage,
+  examinationMonitorMapList,
+  examinationMonitorAreaList,
+} from "@/api/invigilation";
+// import {
+//   examInvigilationCount,
+//   examInvigilationWarnDistribution,
+//   examInvigilationWarnTrend,
+//   examInvigilationVideoRandomList,
+//   examInvigilationWarnMessage,
+//   examinationMonitorMapList,
+//   examinationMonitorAreaList,
+// } from "./datas";
+import { calcSum } from "@/utils/utils";
+
+export default {
+  data() {
+    return {
+      colorType: "dark",
+      schoolName: "",
+      summary: {
+        onlineCount: "",
+        waitingCount: "",
+        examingCount: "",
+        breachCount: "",
+        warnCount: "",
+        id: "",
+        name: "",
+        code: "",
+      },
+      studentVideoList: [],
+      inviMapList: [],
+      inviMapChartOption: null,
+      inviTypesList: [],
+      inviTypesChartOption: null,
+      inviAreaList: [],
+      inviAreaChartOption: null,
+      inviTimeList: [],
+      inviTimeChartOption: null,
+      inviStudentWainList: [],
+      curMapAreaInd: 0,
+      // setTs
+      setTsMap: {
+        summary: [],
+        common: [],
+        studengWain: [],
+        timeTrend: [],
+        areaMap: [],
+        areaMapDet: [],
+      },
+    };
+  },
+  computed: {
+    ...mapState("invigilation", ["liveDomains"]),
+    isFullScreen() {
+      return this.$store.state.isFullScreen;
+    },
+  },
+  mounted() {
+    this.initData();
+  },
+  beforeDestroy() {
+    this.clearSetTs();
+  },
+  methods: {
+    initData() {
+      this.intervalSummary();
+      this.intervalCommonData();
+      this.intervalStudentWain();
+      this.intervalTimeTrend();
+      this.intervalMap();
+    },
+    addSetTime(typeName, action, time = 1 * 1000) {
+      this.setTsMap[typeName].push(setTimeout(action, time));
+    },
+    clearSetTs(typeName) {
+      if (typeName) {
+        if (!this.setTsMap[typeName] || !this.setTsMap[typeName].length) return;
+        this.setTsMap[typeName].forEach((t) => clearTimeout(t));
+        this.setTsMap[typeName] = [];
+        return;
+      }
+      Object.keys(this.setTsMap).forEach((k) => {
+        this.setTsMap[k].forEach((t) => clearTimeout(t));
+        this.setTsMap[k] = [];
+      });
+    },
+    videoAllMuted() {
+      this.$refs.InvigilationStudent.forEach((refInst) => {
+        refInst.mutedPlayer(true);
+      });
+    },
+    // interval
+    async intervalSummary() {
+      const typeName = "summary";
+      this.clearSetTs(typeName);
+
+      await this.getSummary();
+      this.addSetTime(typeName, this.intervalSummary, 3 * 1000);
+    },
+    async intervalCommonData() {
+      const typeName = "common";
+      this.clearSetTs(typeName);
+
+      await this.getInviTypesList();
+      await this.getInviAreaList();
+      await this.getStudentVideoList();
+
+      this.addSetTime(typeName, this.intervalCommonData, 10 * 1000);
+    },
+    async intervalStudentWain() {
+      const typeName = "studengWain";
+      this.clearSetTs(typeName);
+
+      await this.getStudentWainList();
+      this.addSetTime(typeName, this.intervalStudentWain, 5 * 1000);
+    },
+    async intervalTimeTrend() {
+      const typeName = "timeTrend";
+      this.clearSetTs(typeName);
+
+      await this.getInviTimeList();
+      this.addSetTime(typeName, this.intervalTimeTrend, 60 * 1000);
+    },
+    async intervalMap() {
+      const typeName = "areaMap";
+      this.clearSetTs(typeName);
+
+      await this.getMapList();
+      this.intervalMapDet();
+      let inTime = this.inviMapList.length * 5 * 1000;
+      inTime = inTime || 5 * 1000;
+      this.addSetTime(typeName, this.intervalMap, inTime);
+    },
+    intervalMapDet() {
+      const typeName = "areaMapDet";
+      this.clearSetTs(typeName);
+      if (!this.inviMapList.length) return;
+
+      if (this.curMapAreaInd !== 0) {
+        this.$refs.InviMapChart.dispatchAction({
+          type: "hideTip",
+        });
+        this.$refs.InviMapChart.dispatchAction({
+          type: "mapUnSelect",
+          dataIndex: this.curMapAreaInd - 1,
+        });
+      }
+      this.$refs.InviMapChart.dispatchAction({
+        type: "showTip",
+        seriesIndex: 0,
+        dataIndex: this.curMapAreaInd,
+      });
+      this.$refs.InviMapChart.dispatchAction({
+        type: "mapSelect",
+        dataIndex: this.curMapAreaInd,
+      });
+
+      this.curMapAreaInd++;
+      if (this.curMapAreaInd >= this.inviMapList.length) {
+        this.curMapAreaInd = 0;
+      } else {
+        this.addSetTime(typeName, this.intervalMapDet, 5 * 1000);
+      }
+    },
+    // fetch action
+    getMaxNum(nums) {
+      return Math.max.apply(null, nums);
+    },
+    async getSummary() {
+      // interval 3s
+      const res = await examInvigilationCount();
+      const data = res.data.data;
+      this.summary = Object.assign({}, this.summary, data);
+    },
+    async getStudentVideoList() {
+      // interval 10s
+      const res = await examInvigilationVideoRandomList({ randomNum: 4 });
+      const domainLen = this.liveDomains.length;
+      this.studentVideoList = res.data.data.map((item, index) => {
+        const domain = domainLen ? this.liveDomains[index % domainLen] : "";
+        item.liveUrl = item.monitorLiveUrl
+          ? `${domain}/live/${item.monitorLiveUrl.toLowerCase()}.flv`
+          : "";
+        item.progress = item.progress ? Math.round(item.progress * 100) : 0;
+        return item;
+      });
+    },
+    async getMapList() {
+      // interval list 5s
+      const res = await examinationMonitorMapList();
+      const dataList = res.data.data;
+      this.inviMapList = dataList.map((item) => {
+        item.selected = false;
+        return item;
+      });
+      this.inviMapChartOption = getInviMapOption(this.inviMapList);
+    },
+    async getInviTypesList() {
+      // interval 10s
+      const res = await examInvigilationWarnDistribution();
+      const dataList = res.data.data;
+      const maxNum = this.getMaxNum(dataList.map((item) => item.warnCount));
+      let inviTypesList = dataList.map((item) => {
+        return {
+          ...item,
+          percentage: Math.floor((100 * item.warnCount) / maxNum),
+        };
+      });
+      inviTypesList.sort((a, b) => b.warnCount - a.warnCount);
+      this.inviTypesList = inviTypesList;
+      this.inviTypesChartOption = getInviTypesOption(this.inviTypesList);
+    },
+    async getInviAreaList() {
+      // interval 10s
+      const res = await examinationMonitorAreaList();
+      const dataList = res.data.data;
+      const maxNum = this.getMaxNum(dataList.map((item) => item.warnCount));
+      let inviAreaList = dataList.map((item) => {
+        return {
+          ...item,
+          percentage: Math.floor((100 * item.warnCount) / maxNum),
+        };
+      });
+      inviAreaList.sort((a, b) => b.warnCount - a.warnCount);
+      this.inviAreaList = inviAreaList.slice(0, 5);
+      const otherAreaCount = calcSum(
+        inviAreaList.slice(5).map((item) => item.warnCount)
+      );
+      const chartDataList = [
+        ...this.inviAreaList,
+        { country: "中国", province: "其他", warnCount: otherAreaCount },
+      ];
+
+      this.inviAreaChartOption = getInviAreaOption(chartDataList);
+    },
+    async getInviTimeList() {
+      // interval 60s
+      const res = await examInvigilationWarnTrend();
+      const dataList = res.data.data;
+      this.inviTimeList = dataList;
+      this.inviTimeChartOption = getInviTimeOption(dataList, this.colorType);
+    },
+    async getStudentWainList() {
+      // // interval 5s
+      const res = await examInvigilationWarnMessage();
+      const dataList = res.data.data;
+      this.inviStudentWainList = dataList;
+    },
+  },
+};

+ 0 - 8
src/router/index.js

@@ -194,14 +194,6 @@ const routes = [
         /* webpackChunkName: "record" */ "../features/examwork/StudentManagement/StudentMonitorRecord.vue"
       ),
   },
-  {
-    path: "/invigilation/exam-invigilation-full",
-    name: "ExamInvigilationFull",
-    component: () =>
-      import(
-        /* webpackChunkName: "inspection" */ "../features/invigilation/ExamInvigilation/ExamInvigilationFull"
-      ),
-  },
   {
     path: "/invigilation",
     name: "Invigilation",

+ 8 - 0
src/router/invigilation.js

@@ -21,6 +21,14 @@ const routes = [
         /* webpackChunkName: "inspection" */ "../features/invigilation/ExamInvigilation/ExamInvigilation"
       ),
   },
+  {
+    path: "exam-invigilation-full",
+    name: "ExamInvigilationFull",
+    component: () =>
+      import(
+        /* webpackChunkName: "inspection" */ "../features/invigilation/ExamInvigilation/ExamInvigilationFull"
+      ),
+  },
   {
     path: "online-patrol",
     name: "OnlinePatrol",

+ 143 - 14
src/styles/base.scss

@@ -1036,6 +1036,12 @@ body {
     height: auto;
   }
 }
+
+.flex-stretch {
+  display: flex;
+  justify-content: space-between;
+  align-items: stretch;
+}
 // invi-container
 .invi-container {
   position: absolute;
@@ -1084,9 +1090,7 @@ body {
     height: 100%;
     padding-top: 65px;
     overflow: hidden;
-    display: flex;
-    justify-content: space-between;
-    align-items: stretch;
+    @extend .flex-stretch;
   }
   .invi-monitor {
     flex-shrink: 0;
@@ -1220,6 +1224,7 @@ body {
     }
     &-box {
       height: 100%;
+      padding: 8px;
       background: rgba(63, 67, 87, 0.3);
       border-radius: 10px;
       box-shadow: 0 0 2px rgba(255, 255, 255, 0.4);
@@ -1240,10 +1245,7 @@ body {
     }
   }
   .invi-types {
-    display: flex;
-    justify-content: space-between;
-    align-items: stretch;
-    padding: 8px;
+    @extend .flex-stretch;
     &-table {
       width: 50%;
       padding-top: 50px;
@@ -1255,10 +1257,8 @@ body {
   }
 
   .invi-area {
-    display: flex;
-    justify-content: space-between;
-    align-items: stretch;
-    padding: 8px;
+    @extend .flex-stretch;
+
     &-table {
       width: 50%;
       padding-top: 50px;
@@ -1294,14 +1294,13 @@ body {
   }
 
   .invi-time {
+    padding: 0;
     &-chart {
       height: 100%;
     }
   }
 
   .invi-student {
-    padding: 8px;
-
     &-table {
       padding-top: 50px;
       padding-bottom: 8px;
@@ -1353,11 +1352,14 @@ body {
       text-overflow: ellipsis;
     }
   }
+
+  .echarts > div {
+    margin: 0 auto !important;
+  }
 }
 .flip-list-move {
   transition: transform 0.5s;
 }
-
 @media screen and (max-width: 1919px) {
   .invi-container {
     .invi-header {
@@ -1463,7 +1465,134 @@ body {
     }
   }
 }
+// exam-invigilation
+.exam-invigilation {
+  &.invi-container {
+    position: relative;
+    background-image: none;
+    color: inherit;
+    @extend .flex-stretch;
+  }
+  .invi-body {
+    height: auto;
+    flex-grow: 2;
+    padding-top: 0;
+    display: block;
+    &-title {
+      font-size: 18px;
+      line-height: 1;
+      margin-bottom: 15px;
+    }
+  }
+  .invi-monitor {
+    position: relative;
+    padding: 13px 0 0 20px;
+    &-title {
+      position: absolute;
+      top: 0;
+      font-size: 18px;
+      line-height: 1;
+      margin: 0;
+    }
+    &-item {
+      height: 25%;
+      padding: 20px 0 0 0;
+    }
+    .invigilation-student {
+      background: #fff;
+    }
+    .student-info-content {
+      > p {
+        color: inherit;
+      }
+    }
+    .student-info-detail {
+      box-shadow: none;
+    }
+  }
+  .invi-summary {
+    position: static;
+    display: flex;
+    justify-content: space-between;
+    align-items: stretch;
+    margin-bottom: 10px;
+    &-item {
+      display: block;
+      flex-grow: 1;
+      background: #fff;
+      margin: 0;
+      &:not(:first-child) {
+        margin-left: 20px;
+      }
+    }
+  }
+  .invi-analysis {
+    width: auto;
+    padding: 10px 0;
+
+    &:last-child {
+      padding-bottom: 0;
+    }
+    &-item {
+      height: 300px;
+      padding: 0;
+    }
 
+    &-2 {
+      display: flex;
+      justify-content: space-between;
+      align-items: stretch;
+      .invi-analysis-item {
+        flex-grow: 1;
+        &:not(:first-child) {
+          margin-left: 20px;
+        }
+      }
+    }
+
+    &-box {
+      background: #fff;
+      padding: 10px;
+    }
+    &-title {
+      background: transparent;
+      font-size: 16px;
+      padding: 0;
+      line-height: 30px;
+      top: 10px;
+      left: 10px;
+    }
+  }
+
+  .invi-types-chart,
+  .invi-area-chart {
+    .invi-chart-title {
+      background: #fff;
+      box-shadow: none;
+      font-size: 14px;
+      color: inherit;
+    }
+  }
+
+  .invi-student {
+    &-table {
+      padding-top: 40px;
+
+      td {
+        color: inherit;
+      }
+
+      tr:nth-of-type(even) {
+        background: rgba(45, 153, 255, 0.1);
+      }
+    }
+  }
+
+  .invi-table {
+    color: inherit;
+    font-size: 14px;
+  }
+}
 // flv-media
 .flv-media {
   position: relative;