zhangjie 2 年 前
コミット
9f4260ffde

+ 11 - 0
src/api/system.js

@@ -25,3 +25,14 @@ export function enableSystemNotice({ id, enable }) {
 // export function deleteSystemNotice(id) {
 //   return httpApp.post("/api/admin/black/delete", { id });
 // }
+
+// data-statistics
+export function statSummary() {
+  return httpApp.post("/api/admin/statistics/summary", {});
+}
+export function statOrgStudent() {
+  return httpApp.post("/api/admin/statistics/org", {});
+}
+export function statAreaStudent() {
+  return httpApp.post("/api/admin/statistics/area", {});
+}

+ 132 - 0
src/features/system/DataStatistics/DataStatistics.vue

@@ -0,0 +1,132 @@
+<template>
+  <div class="data-stat">
+    <div class="data-stat-header">
+      <div class="stat-summary">
+        <div class="stat-summary-item">
+          <h4>当前在线人数</h4>
+          <p>
+            <animate-number :value="summary.onlineCount"></animate-number>
+          </p>
+        </div>
+        <div class="stat-summary-item">
+          <h4>当前考试中人数</h4>
+          <p>
+            <animate-number :value="summary.examingCount"></animate-number>
+          </p>
+        </div>
+        <div class="stat-summary-item">
+          <h4>累计考试科次</h4>
+          <p>
+            <animate-number :value="summary.stdExamCount"></animate-number>
+          </p>
+        </div>
+        <div class="stat-summary-item">
+          <h4>累计服务考生</h4>
+          <p><animate-number :value="summary.studentCount"></animate-number></p>
+        </div>
+      </div>
+    </div>
+    <div class="data-stat-body">
+      <div class="data-stat-table">
+        <div class="data-stat-table-title">机构考生分布</div>
+        <el-table :data="tableData" stripe>
+          <el-table-column label="机构名称"></el-table-column>
+          <el-table-column width="80" label="在线人数"></el-table-column>
+          <el-table-column width="80" label="在考人数"></el-table-column>
+        </el-table>
+      </div>
+      <div class="data-stat-chart">
+        <div class="data-stat-chart-title">在线考生地域分布</div>
+        <vue-charts
+          v-if="areaChartOption"
+          :options="areaChartOption"
+          autoresize
+        ></vue-charts>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {
+  statSummary,
+  statOrgStudent,
+  statAreaStudent,
+} from "../../../api/system";
+
+export default {
+  name: "DataStatistics",
+  data() {
+    return {
+      summary: {
+        onlineCount: 0,
+        examingCount: 0,
+        stdExamCount: 0,
+        studentCount: 0,
+      },
+      tableData: [],
+      areaChartOption: null,
+      // setTs
+      setTsMap: {
+        summary: [],
+        areaMap: [],
+      },
+    };
+  },
+  mounted() {
+    // this.initData();
+  },
+  beforeDestroy() {
+    this.clearSetTs();
+  },
+  methods: {
+    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] = [];
+      });
+    },
+    initData() {
+      this.intervalSummary();
+      this.intervalStudent();
+    },
+    async getSummary() {
+      const res = await statSummary();
+      this.summary = res.data;
+    },
+    async getStatOrgStudent() {
+      const res = await statOrgStudent();
+      this.tableData = res.data;
+    },
+    async getStatAreaStudent() {
+      const res = await statAreaStudent();
+      // todo:
+      this.areaChartOption = res.data;
+    },
+    async intervalSummary() {
+      const typeName = "summary";
+      this.clearSetTs(typeName);
+
+      await this.getSummary();
+      this.addSetTime(typeName, this.intervalSummary, 5 * 1000);
+    },
+    async intervalStudent() {
+      const typeName = "areaMap";
+      this.clearSetTs(typeName);
+
+      await this.getStatOrgStudent();
+      await this.getStatAreaStudent();
+      this.addSetTime(typeName, this.intervalStudent, 10 * 1000);
+    },
+  },
+};
+</script>

+ 24 - 0
src/router/index.js

@@ -72,6 +72,30 @@ const routes = [
             /* webpackChunkName: "system" */ "../features/system/OrgManagement/OrgManagement.vue"
           ),
       },
+      {
+        path: "statistics",
+        name: "DataStatistics",
+        component: () =>
+          import(
+            /* webpackChunkName: "system" */ "../features/system/DataStatistics/DataStatistics.vue"
+          ),
+      },
+      {
+        path: "black-list",
+        name: "BlackList",
+        component: () =>
+          import(
+            /* webpackChunkName: "system" */ "../features/system/BlackList/BlackList.vue"
+          ),
+      },
+      {
+        path: "system-notice",
+        name: "SystemNotice",
+        component: () =>
+          import(
+            /* webpackChunkName: "system" */ "../features/system/SystemNotice/SystemNotice.vue"
+          ),
+      },
     ],
   },
   {

+ 93 - 0
src/styles/base.scss

@@ -2112,3 +2112,96 @@ body {
     height: 100%;
   }
 }
+
+// data-statistics
+.data-stat {
+  position: absolute;
+  top: 80px;
+  right: 20px;
+  bottom: 50px;
+  left: 20px;
+  overflow: hidden;
+  background-color: #fff;
+  &-header {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    z-index: 9;
+    padding: 20px;
+  }
+  &-body {
+    position: relative;
+    height: 100%;
+    padding-top: 88px;
+    padding-left: 340px;
+  }
+  &-table {
+    position: absolute;
+    left: 10px;
+    top: 88px;
+    bottom: 20px;
+    width: 320px;
+    z-index: 9;
+    overflow-x: hidden;
+    overflow-y: auto;
+    background-color: #f0f4f9;
+    border-radius: 10px;
+    padding: 15px 10px;
+
+    &-title {
+      font-size: 16px;
+      font-weight: 600;
+      line-height: 1;
+      margin-bottom: 10px;
+    }
+
+    .el-table {
+      background-color: #f0f4f9;
+
+      td,
+      th {
+        background-color: #f0f4f9;
+        padding-top: 8px;
+        padding-bottom: 8px;
+      }
+    }
+  }
+
+  .stat-summary {
+    text-align: center;
+    &-item {
+      display: inline-block;
+      vertical-align: middle;
+      width: 25%;
+      font-size: 0;
+
+      > h4 {
+        font-size: 14px;
+        line-height: 1;
+        margin-bottom: 8px;
+      }
+      p {
+        margin: 0;
+        font-size: 26px;
+        line-height: 1;
+      }
+    }
+  }
+
+  &-chart {
+    height: 100%;
+    position: relative;
+
+    &-title {
+      font-size: 16px;
+      font-weight: 600;
+      line-height: 1;
+
+      position: absolute;
+      top: 15px;
+      left: 0;
+      z-index: 9;
+    }
+  }
+}