فهرست منبع

大屏页面接口调试

zhangjie 2 سال پیش
والد
کامیت
5ed7694eca

+ 14 - 0
src/api/invigilation.js

@@ -19,15 +19,18 @@ export function getLiveDomains() {
 export function examInvigilationCount() {
   return httpApp.post("/api/admin/report/examination_monitor/count", {});
 }
+// invi types
 export function examInvigilationWarnDistribution() {
   return httpApp.post(
     "/api/admin/report/examination_monitor/warn_distribution",
     {}
   );
 }
+// invi time trend
 export function examInvigilationWarnTrend() {
   return httpApp.post("/api/admin/report/examination_monitor/warn_trend", {});
 }
+// invi student
 export function examInvigilationVideoRandomList(datas) {
   const data = pickBy(datas, paramFilter);
   return httpApp.post(
@@ -35,9 +38,20 @@ export function examInvigilationVideoRandomList(datas) {
     {}
   );
 }
+// invi exam msg
 export function examInvigilationWarnMessage() {
   return httpApp.post("/api/admin/report/examination_monitor/warn_msg", {});
 }
+// exam-invigilation-full
+export function examinationMonitorMapList() {
+  return httpApp.post("/api/admin/report/examination_monitor/map", {});
+}
+export function examinationMonitorAreaList() {
+  return httpApp.post(
+    "/api/admin/report/examination_monitor/warn_region_distribution",
+    {}
+  );
+}
 
 // realtime-monitoring
 export function invigilateList(datas) {

BIN
src/assets/bg-invi-map.png


+ 198 - 28
src/features/invigilation/ExamInvigilation/ExamInvigilationFull.vue

@@ -11,7 +11,7 @@
     <div class="invi-body">
       <div class="invi-monitor">
         <div
-          v-for="item in students"
+          v-for="item in studentVideoList"
           :key="item.examStudentId"
           class="invi-monitor-item"
         >
@@ -48,7 +48,14 @@
             <p>{{ summary.breachCount }}</p>
           </div>
         </div>
-        <div class="invi-map"></div>
+        <div class="invi-map-chart">
+          <vue-charts
+            v-if="inviMapChartOption"
+            :options="inviMapChartOption"
+            autoresize
+          ></vue-charts>
+          <p class="chart-none" v-else>暂无数据</p>
+        </div>
       </div>
       <div class="invi-analysis">
         <div class="invi-analysis-item">
@@ -75,7 +82,9 @@
               </table>
             </div>
             <div class="invi-types-chart invi-chart">
-              <div class="invi-chart-title">预警类型占比</div>
+              <div v-if="inviTypesChartOption" class="invi-chart-title">
+                预警类型占比
+              </div>
               <vue-charts
                 v-if="inviTypesChartOption"
                 :options="inviTypesChartOption"
@@ -109,7 +118,9 @@
               </table>
             </div>
             <div class="invi-area-chart invi-chart">
-              <div class="invi-chart-title">考生分布</div>
+              <div v-if="inviAreaChartOption" class="invi-chart-title">
+                考生分布
+              </div>
               <vue-charts
                 v-if="inviAreaChartOption"
                 :options="inviAreaChartOption"
@@ -152,8 +163,8 @@
                     <th>预警类型</th>
                     <th>处理状态</th>
                   </tr>
-                  <tr v-for="item in inviStudentList" :key="item.id">
-                    <td>{{ item.createTime }}</td>
+                  <tr v-for="item in inviStudentWainList" :key="item.id">
+                    <td>{{ item.createTime | timeFilter }}</td>
                     <td>
                       <div class="td-cont" style="width: 60px;">
                         {{ item.name }}
@@ -161,7 +172,11 @@
                     </td>
                     <td>{{ item.identity }}</td>
                     <td>
-                      <div class="td-cont" style="width: 140px;">
+                      <div
+                        class="td-cont"
+                        style="width: 140px;"
+                        :title="item.info"
+                      >
                         {{ item.info }}
                       </div>
                     </td>
@@ -178,6 +193,7 @@
 </template>
 
 <script>
+import { mapState } from "vuex";
 import TextClock from "../common/TextClock";
 import InvigilationStudent from "../common/InvigilationStudent";
 import VueCharts from "@/plugins/VueCharts";
@@ -185,17 +201,27 @@ import {
   getInviTypesOption,
   getInviAreaOption,
   getInviTimeOption,
+  getInviMapOption,
 } from "./chartOpt";
-
 import {
-  students,
-  summary,
-  inviTypesList,
-  inviTimeList,
-  inviAreaList,
-  inviMapList,
-  inviStudentList,
-} from "./datas";
+  examInvigilationCount,
+  examInvigilationWarnDistribution,
+  examInvigilationWarnTrend,
+  examInvigilationVideoRandomList,
+  examInvigilationWarnMessage,
+  examinationMonitorMapList,
+  examinationMonitorAreaList,
+} from "@/api/invigilation";
+
+// import {
+//   students,
+//   summary,
+//   inviTypesList,
+//   inviTimeList,
+//   inviAreaList,
+//   inviMapList,
+//   inviStudentList,
+// } from "./datas";
 
 export default {
   name: "exam-invigilation-full",
@@ -203,7 +229,6 @@ export default {
   data() {
     return {
       schoolName: "",
-      students: [],
       summary: {
         onlineCount: "",
         waitingCount: "",
@@ -214,35 +239,168 @@ export default {
         name: "",
         code: "",
       },
+      studentVideoList: [],
       inviMapList: [],
+      inviMapChartOption: null,
       inviTypesList: [],
       inviTypesChartOption: null,
       inviAreaList: [],
       inviAreaChartOption: null,
       inviTimeList: [],
       inviTimeChartOption: null,
-      inviStudentList: [],
+      inviStudentWainList: [],
+      curMapAreaInd: 0,
+      // setTs
+      setTsMap: {
+        summary: [],
+        common: [],
+        studengWain: [],
+        timeTrend: [],
+        areaMap: [],
+        areaMapDet: [],
+      },
     };
   },
+  computed: {
+    ...mapState("invigilation", ["liveDomains"]),
+  },
   mounted() {
-    this.students = students;
-    this.summary = summary;
-    this.inviMapList = inviMapList;
-    this.parseInviTypesList(inviTypesList);
-    this.parseInviAreaList(inviAreaList);
-    this.parseInviTimeList(inviTimeList);
-    this.inviStudentList = inviStudentList;
+    // this.studentVideoList = students;
+    // this.summary = summary;
+    // this.parseMapList(inviMapList);
+    // this.parseInviTypesList(inviTypesList);
+    // this.parseInviAreaList(inviAreaList);
+    // this.parseInviTimeList(inviTimeList);
+    // this.inviStudentWainList = inviStudentList;
+    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 * 3 * 1000;
+      inTime = inTime || 3 * 1000;
+      this.addSetTime(typeName, this.intervalMap, inTime);
+    },
+    intervalMapDet() {
+      if (!this.inviMapList.length) return;
+      const typeName = "areaMapDet";
+      this.clearSetTs(typeName);
+
+      if (this.curMapAreaInd >= this.inviMapList.length - 1) {
+        this.curMapAreaInd = 0;
+        return;
+      }
+      this.inviMapList.forEach((item) => {
+        item.selected = false;
+      });
+      this.inviMapList[this.curMapAreaInd].selected = true;
+      this.inviMapChartOption = getInviMapOption(this.inviMapList);
+      this.curMapAreaInd++;
+      this.addSetTime(typeName, this.intervalMapDet, 3 * 1000);
+    },
+    // fetch action
     getMaxNum(nums) {
       return Math.max.apply(null, nums);
     },
-    parseInviTypesList(dataList) {
+    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 60s
+      const res = await examinationMonitorMapList();
+      const dataList = res.data.data;
+      this.inviMapList = dataList.map((item) => {
+        item.selected = false;
+        return item;
+      });
+      // this.inviMapChartOption = getInviMapOption(dataList);
+    },
+    async getInviTypesList() {
+      // interval 10s
+      const res = await examInvigilationWarnDistribution();
+      const dataList = res.data.data;
       const maxNum = this.getMaxNum(dataList.map((item) => item.warnCount));
       this.inviTypesList = dataList.map((item) => {
         return {
@@ -252,7 +410,10 @@ export default {
       });
       this.inviTypesChartOption = getInviTypesOption(this.inviTypesList);
     },
-    parseInviAreaList(dataList) {
+    async getInviAreaList() {
+      // interval 10s
+      const res = await examinationMonitorAreaList();
+      const dataList = res.data.data;
       const maxNum = this.getMaxNum(dataList.map((item) => item.warnCount));
       this.inviAreaList = dataList.map((item) => {
         return {
@@ -262,10 +423,19 @@ export default {
       });
       this.inviAreaChartOption = getInviAreaOption(this.inviAreaList);
     },
-    parseInviTimeList(dataList) {
+    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;
+    },
   },
 };
 </script>

+ 98 - 3
src/features/invigilation/ExamInvigilation/chartOpt.js

@@ -29,6 +29,14 @@ export function getInviTypesOption(dataList) {
     tooltip: {
       trigger: "item",
       formatter: "{b}<br/>{d}%",
+      backgroundColor: "rgba(63,67,87,0.9)",
+      padding: 10,
+      textStyle: {
+        fontSize: 12,
+        color: "#E1E6EF",
+        fontWeight: 500,
+        lineHeight: 18,
+      },
     },
     legend: {
       show: false,
@@ -75,6 +83,14 @@ export function getInviAreaOption(dataList) {
     tooltip: {
       trigger: "item",
       formatter: "{b}<br/>{d}%",
+      backgroundColor: "rgba(63,67,87,0.9)",
+      padding: 10,
+      textStyle: {
+        fontSize: 12,
+        color: "#E1E6EF",
+        fontWeight: 500,
+        lineHeight: 18,
+      },
     },
     legend: {
       show: false,
@@ -121,7 +137,16 @@ export function getInviTimeOption(dataList) {
       },
     },
     tooltip: {
-      show: false,
+      show: true,
+      trigger: "axis",
+      backgroundColor: "rgba(63,67,87,0.9)",
+      padding: 10,
+      textStyle: {
+        fontSize: 12,
+        color: "#E1E6EF",
+        fontWeight: 500,
+        lineHeight: 18,
+      },
     },
     xAxis: {
       type: "category",
@@ -165,7 +190,7 @@ export function getInviTimeOption(dataList) {
     },
     series: [
       {
-        name: "在线",
+        name: "在线考生",
         type: "line",
         smooth: true,
         showSymbol: false,
@@ -179,7 +204,7 @@ export function getInviTimeOption(dataList) {
         },
       },
       {
-        name: "预警",
+        name: "预警数量",
         type: "line",
         smooth: true,
         showSymbol: false,
@@ -195,3 +220,73 @@ export function getInviTimeOption(dataList) {
     ],
   };
 }
+
+export function getInviMapOption(dataList) {
+  if (!dataList.length) return;
+  // const onlineCountData = dataList.map((item) => {
+  //   return {
+  //     name: item.province,
+  //     value: item.onlineCount,
+  //   };
+  // });
+  // const warnCountData = dataList.map((item) => {
+  //   return {
+  //     name: item.province,
+  //     value: item.warnCount,
+  //   };
+  // });
+
+  const countData = dataList.map((item) => {
+    return {
+      name: item.province,
+      ...item,
+    };
+  });
+
+  return {
+    tooltip: {
+      trigger: "item",
+      formatter(params) {
+        if (!params.data) return;
+        const { name, onlineCount, warnCount } = params.data;
+        return `<span style="color:#2D99FF">${name}</span><br />在线人数:${onlineCount}<br />预警总数:${warnCount}`;
+      },
+      backgroundColor: "rgba(63,67,87,0.95)",
+      padding: 10,
+      textStyle: {
+        fontSize: 12,
+        color: "#E1E6EF",
+        fontWeight: 500,
+        lineHeight: 18,
+        rich: {
+          n: {
+            color: "#2D99FF",
+          },
+        },
+      },
+    },
+    series: [
+      {
+        name: "数量",
+        type: "map",
+        mapType: "china",
+        layoutCenter: ["50%", "50%"],
+        layoutSize: "110%",
+        data: countData,
+        itemStyle: {
+          areaColor: "rgba(63, 67, 87, 0.4)",
+          borderColor: "#2D99FF",
+          borderWidth: 1,
+        },
+        emphasis: {
+          label: {
+            show: false,
+          },
+          itemStyle: {
+            areaColor: "#2D99FF",
+          },
+        },
+      },
+    ],
+  };
+}

+ 32 - 2
src/features/invigilation/ExamInvigilation/datas.js

@@ -220,11 +220,41 @@ export const inviAreaList = [
 
 export const inviMapList = [
   {
-    country: "国",
-    province: "省份",
+    country: "国",
+    province: "湖北",
     onlineCount: 120,
     warnCount: 10,
   },
+  {
+    country: "中国",
+    province: "河北",
+    onlineCount: 145,
+    warnCount: 11,
+  },
+  {
+    country: "中国",
+    province: "湖南",
+    onlineCount: 123,
+    warnCount: 8,
+  },
+  {
+    country: "中国",
+    province: "河南",
+    onlineCount: 245,
+    warnCount: 20,
+  },
+  {
+    country: "中国",
+    province: "四川",
+    onlineCount: 89,
+    warnCount: 5,
+  },
+  {
+    country: "中国",
+    province: "上海",
+    onlineCount: 156,
+    warnCount: 5,
+  },
 ];
 
 export const inviStudentList = [

+ 5 - 1
src/filters/index.js

@@ -1,5 +1,5 @@
 import Vue from "vue";
-import { dateFormatForAPI } from "@/utils/utils";
+import { dateFormatForAPI, timeFormatForAPI } from "@/utils/utils";
 import { APPROVE_STATUS, EXAM_RECORD_STATUS } from "@/constant/constants";
 
 Vue.filter("booleanYesNoFilter", function (val) {
@@ -36,6 +36,10 @@ Vue.filter("datetimeFilter", function (val) {
   if (val === null) return "";
   return dateFormatForAPI(val);
 });
+Vue.filter("timeFilter", function (val) {
+  if (val === null) return "";
+  return timeFormatForAPI(val);
+});
 
 Vue.filter("scoreStatusFilter", function (val) {
   if (val === null) return "无";

+ 1 - 1
src/plugins/VueCharts.js

@@ -20,7 +20,7 @@ import "echarts/lib/chart/pie";
 // import "echarts/lib/chart/map";
 import "zrender/lib/svg/svg";
 // map
-// import "echarts/map/js/china";
+import "echarts/map/js/china";
 // import "echarts/map/js/china-contour";
 
 // If you want to use ECharts extensions, just import the extension package and it will work

+ 6 - 0
src/styles/base.scss

@@ -1175,6 +1175,7 @@ body {
     text-align: center;
     z-index: 8;
     top: 40px;
+    white-space: nowrap;
 
     &-item {
       display: inline-block;
@@ -1201,6 +1202,11 @@ body {
       }
     }
   }
+  .invi-map-chart {
+    height: 100%;
+    padding-top: 130px;
+    padding-bottom: 10px;
+  }
   .invi-analysis {
     flex-shrink: 0;
     flex-grow: 0;

+ 3 - 0
src/utils/utils.js

@@ -6,6 +6,9 @@ import MD5 from "js-md5";
 export function dateFormatForAPI(date) {
   return moment(date).format(YYYYMMDDHHmmss);
 }
+export function timeFormatForAPI(date) {
+  return moment(date).format("HH:mm:ss");
+}
 
 export function formatEmptyToNull(obj) {
   Object.keys(obj).forEach((key) => {