zhangjie 4 лет назад
Родитель
Сommit
18ccb738ba

+ 2 - 0
package.json

@@ -23,11 +23,13 @@
     "core-js": "^3.6.5",
     "crypto-js": "^4.0.0",
     "element-ui": "^2.13.2",
+    "flv.js": "^1.5.0",
     "js-cookie": "^2.2.1",
     "lodash-es": "^4.17.15",
     "moment": "^2.27.0",
     "query-string": "^6.13.1",
     "register-service-worker": "^1.7.1",
+    "trtc-js-sdk": "^4.6.1",
     "vue": "^2.6.11",
     "vue-awesome": "^4.1.0",
     "vue-router": "^3.3.4",

+ 2 - 6
public/index.html

@@ -6,10 +6,6 @@
     <meta name="viewport" content="width=device-width,initial-scale=1.0" />
     <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
     <title>在线考试管理后台</title>
-    <!-- 未压缩版 -->
-    <script src="https://cdn.ronghub.com/RongIMLib-2.5.9.js"></script>
-    <!-- 压缩版 -->
-    <script src="https://cdn.ronghub.com/RongRTC-3.2.4.min.js"></script>
   </head>
   <body>
     <noscript>
@@ -19,7 +15,7 @@
       >
     </noscript>
     <div id="app"></div>
-    <script>
+    <!-- <script>
       var _hmt = _hmt || [];
       (function () {
         var hmId = "xxxxx";
@@ -28,7 +24,7 @@
         var s = document.getElementsByTagName("script")[0];
         s.parentNode.insertBefore(hm, s);
       })();
-    </script>
+    </script> -->
     <!-- built files will be auto injected -->
   </body>
 </html>

+ 27 - 0
src/api/invigilation.js

@@ -0,0 +1,27 @@
+import { httpApp } from "@/plugins/axiosIndex";
+
+export function warningStudentDetail({ recordId }) {
+  const data = {
+    recordId,
+  };
+  return httpApp.post("/api/oe/monitor/call/query", data);
+}
+
+export function communicationList() {
+  return httpApp.post(
+    "/api/oe/monitor/call/list",
+    {},
+    {
+      noErrorMessage: true,
+    }
+  );
+}
+export function communicationOver(callCancel) {
+  return httpApp.post(
+    "/api/oe/monitor/call/cancel",
+    { callCancel },
+    {
+      noErrorMessage: true,
+    }
+  );
+}

+ 0 - 0
src/assets/icon-stars.png → src/assets/bg-stars.png


+ 510 - 2
src/features/invigilation/ExamInvigilation/ExamInvigilation.vue

@@ -1,6 +1,211 @@
 <template>
   <div class="exam-invigilation">
-    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>
+      </div>
+      <div class="invigilation-summary">
+        <div class="invigilation-summary-item">
+          <div class="part-box">
+            <h5>
+              <span>在线(人)</span>
+              <el-popover
+                placement="top-start"
+                width="200"
+                trigger="hover"
+                content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。"
+              >
+                <i class="el-icon-question" slot="reference"></i>
+              </el-popover>
+            </h5>
+            <p>26000</p>
+          </div>
+        </div>
+        <div class="invigilation-summary-item">
+          <div class="part-box">
+            <h5>
+              <span>待考(人)</span>
+              <el-popover
+                placement="top-start"
+                width="200"
+                trigger="hover"
+                content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。"
+              >
+                <i class="el-icon-question" slot="reference"></i>
+              </el-popover>
+            </h5>
+            <p>170</p>
+          </div>
+        </div>
+        <div class="invigilation-summary-item">
+          <div class="part-box">
+            <h5>
+              <span>考试中(人)</span>
+              <el-popover
+                placement="top-start"
+                width="200"
+                trigger="hover"
+                content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。"
+              >
+                <i class="el-icon-question" slot="reference"></i>
+              </el-popover>
+            </h5>
+            <p>2560</p>
+          </div>
+        </div>
+        <div class="invigilation-summary-item">
+          <div class="part-box">
+            <h5>
+              <span>通讯故障(人)</span>
+              <el-popover
+                placement="top-start"
+                width="200"
+                trigger="hover"
+                content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。"
+              >
+                <i class="el-icon-question" slot="reference"></i>
+              </el-popover>
+            </h5>
+            <p>26000</p>
+          </div>
+        </div>
+        <div class="invigilation-summary-item">
+          <div class="part-box">
+            <h5>
+              <span>预警(人)</span>
+              <el-popover
+                placement="top-start"
+                width="200"
+                trigger="hover"
+                content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。"
+              >
+                <i class="el-icon-question" slot="reference"></i>
+              </el-popover>
+            </h5>
+            <p>15</p>
+          </div>
+        </div>
+      </div>
+      <div class="invigilation-online part-box">
+        <h3 class="invigilation-part-title">
+          <span>各机构在线考试人数分布</span>
+          <el-popover
+            placement="top-start"
+            width="200"
+            trigger="hover"
+            content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。"
+          >
+            <i class="el-icon-question" slot="reference"></i>
+          </el-popover>
+        </h3>
+      </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"
+                  width="200"
+                  trigger="hover"
+                  content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。"
+                >
+                  <i class="el-icon-question" slot="reference"></i>
+                </el-popover>
+              </h3>
+            </div>
+          </el-col>
+          <el-col :span="12">
+            <div class="part-box">
+              <h3 class="invigilation-part-title">
+                <span>预警类型分布</span>
+                <el-popover
+                  placement="top-start"
+                  width="200"
+                  trigger="hover"
+                  content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。"
+                >
+                  <i class="el-icon-question" slot="reference"></i>
+                </el-popover>
+              </h3>
+            </div>
+          </el-col>
+        </el-row>
+      </div>
+      <div class="invigilation-trend part-box">
+        <h3 class="invigilation-part-title">
+          <span>预警时间趋势</span>
+          <el-popover
+            placement="top-start"
+            width="200"
+            trigger="hover"
+            content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。"
+          >
+            <i class="el-icon-question" slot="reference"></i>
+          </el-popover>
+        </h3>
+      </div>
+      <div class="invigilation-message part-box">
+        <h3 class="invigilation-part-title">
+          <span>预警消息</span>
+          <el-popover
+            placement="top-start"
+            width="200"
+            trigger="hover"
+            content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。"
+          >
+            <i class="el-icon-question" slot="reference"></i>
+          </el-popover>
+        </h3>
+        <div class="message-list">
+          <div class="message-item">
+            <span><i class="el-icon-warning"></i></span>
+            <span>2020-7-1 08:23</span>
+            <span
+              >张三(证件号:1001001)陌生人入境,系统已提示李四(账号:lisi)进行人工干预</span
+            >
+          </div>
+          <div class="message-item">
+            <span><i class="el-icon-warning"></i></span>
+            <span>2020-7-1 08:23</span>
+            <span
+              >张三(证件号:1001001)陌生人入境,系统已提示李四(账号:lisi)进行人工干预</span
+            >
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="invigilation-list">
+      <h3>实时监控台</h3>
+      <div class="invigilation-student invigilation-student-warning">
+        <div class="student-video"></div>
+        <div class="student-info">
+          <h6><span>刘西西</span><i class="icon icon-net-break"></i></h6>
+          <p><span>证件号:</span><span>000000000000000008</span></p>
+          <p><span>答题进度:</span><span>20%</span></p>
+          <div class="student-time">
+            <i class="el-icon-alarm-clock"></i>
+            <span>50:32:15</span>
+          </div>
+        </div>
+      </div>
+      <div class="invigilation-student invigilation-student-netbreak">
+        <div class="student-video"></div>
+        <div class="student-info">
+          <h6><span>刘西西</span><i class="icon icon-net-break"></i></h6>
+          <p><span>证件号:</span><span>000000000000000008</span></p>
+          <p><span>答题进度:</span><span>20%</span></p>
+          <div class="student-time">
+            <i class="el-icon-alarm-clock"></i>
+            <span>50:32:15</span>
+          </div>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
@@ -10,6 +215,309 @@ export default {
   data() {
     return {};
   },
-  methods: {},
+  mounted() {},
+  methods: {
+    exitFullscreen() {
+      const exitFullscreen =
+        document.exitFullscreen ||
+        document.mozCancelFullScreen ||
+        document.webkitCancelFullScreen;
+
+      exitFullscreen.call(document);
+    },
+  },
 };
 </script>
+
+<style lang="scss" scoped>
+.exam-invigilation {
+  position: relative;
+  padding-right: 310px;
+  .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%;
+  background: #fff;
+  border-radius: 6px;
+  padding: 20px;
+  z-index: 9;
+  overflow-y: auto;
+  overflow-x: hidden;
+
+  > h3 {
+    font-size: 18px;
+    font-weight: 600;
+    line-height: 25px;
+    margin-bottom: 20px;
+  }
+  .invigilation-student {
+    padding: 10px;
+    position: relative;
+    margin-bottom: 20px;
+
+    &-warning {
+      &::before {
+        content: "";
+        display: block;
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 50px;
+        height: 50px;
+        background-image: url(../../../assets/bg-discipline.png);
+        background-size: 100% 100%;
+        z-index: 99;
+      }
+      &::after {
+        content: "";
+        display: block;
+        position: absolute;
+        top: 4px;
+        right: 4px;
+        width: 12px;
+        height: 12px;
+
+        border: 2px solid #fff;
+        border-radius: 50%;
+        background: #fe5863;
+        z-index: 99;
+      }
+    }
+
+    &-netbreak {
+      .icon-net-break {
+        display: inline-block !important;
+      }
+      .student-time {
+        background-color: #fe5863 !important;
+      }
+    }
+  }
+  .student-video {
+    height: 150px;
+    border-radius: 6px;
+    background: #626a82;
+    margin-bottom: 20px;
+    position: relative;
+
+    &::before {
+      content: "";
+      display: block;
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      width: 32px;
+      height: 20px;
+      margin-left: -16px;
+      margin-top: -10px;
+      background-image: url(../../../assets/icon-video.png);
+      background-size: 100% 100%;
+    }
+
+    > video {
+      width: 100%;
+      height: 100%;
+    }
+  }
+  .student-info {
+    position: relative;
+    padding: 0 10px 10px;
+    .student-time {
+      position: absolute;
+      height: 24px;
+      top: 0;
+      right: 0;
+      padding: 4px 12px;
+      border-radius: 12px;
+      background: #abb8c9;
+      color: #fff;
+      font-size: 12px;
+      > i {
+        margin-right: 5px;
+      }
+    }
+
+    > h6 {
+      font-size: 18px;
+      line-height: 25px;
+      margin-bottom: 8;
+      > .icon {
+        margin-top: -4px;
+        margin-left: 8px;
+        display: none;
+      }
+    }
+    > p {
+      font-size: 14px;
+      font-weight: 400;
+      color: #626a82;
+      line-height: 20px;
+      margin: 0;
+    }
+  }
+}
+.invigilation-analysis {
+  .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;
+
+    h5 {
+      font-size: 14px;
+      height: 20px;
+      color: #626a82;
+      margin: 0;
+    }
+    p {
+      font-size: 32px;
+      line-height: 51px;
+      font-weight: 600;
+      color: #202b4b;
+      margin: 0;
+    }
+
+    &: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;
+  }
+}
+.invigilation-trend {
+  height: 327px;
+}
+.invigilation-message {
+  min-height: 160px;
+  padding-top: 70px;
+  .message-item {
+    margin-bottom: 15px;
+
+    span {
+      display: inline-block;
+      vertical-align: middle;
+      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: #202442;
+    border-radius: 0;
+    .invigilation-student {
+      background: #2d325a;
+      border-radius: 10px;
+    }
+    .student-info {
+      .student-time {
+        background: #353c70;
+      }
+      > p {
+        color: #737aae;
+      }
+    }
+  }
+  .invigilation-summary {
+    &-item {
+      h5 {
+        color: #737aae;
+      }
+      p {
+        color: #fff;
+      }
+
+      &:first-child {
+        .el-icon-question {
+          color: #fff;
+        }
+      }
+    }
+  }
+}
+</style>

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

@@ -1,15 +1,304 @@
 <template>
-  <div class="InvigilationDetail">
-    InvigilationDetail
+  <div class="invigilation-detail">
+    <div class="part-box-head">
+      <div class="part-box-head-left"><h1>监考明细管理</h1></div>
+    </div>
+
+    <div class="part-filter">
+      <div class="part-filter-form">
+        <el-form
+          class="part-box-filter-form"
+          ref="FilterForm"
+          label-position="left"
+          inline
+        >
+          <el-form-item>
+            <el-select
+              v-model="filter.batchId"
+              placeholder="请选择批次"
+              clearable
+            >
+              <el-option
+                v-for="item in batchs"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.examroom"
+              placeholder="请选择场次"
+              clearable
+            >
+              <el-option
+                v-for="item in exams"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.examroom"
+              placeholder="请选择考场"
+              clearable
+            >
+              <el-option
+                v-for="item in exams"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.subjectId"
+              placeholder="请选择科目"
+              clearable
+            >
+              <el-option
+                v-for="item in subjects"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-input
+              v-model.trim="filter.content"
+              placeholder="姓名/证件号"
+              clearable
+            ></el-input>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.subjectId"
+              placeholder="交卷方式"
+              clearable
+            >
+              <el-option
+                v-for="item in subjects"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.subjectId"
+              placeholder="筛选状态"
+              clearable
+            >
+              <el-option
+                v-for="item in subjects"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.subjectId"
+              placeholder="违纪/缺考"
+              clearable
+            >
+              <el-option
+                v-for="item in subjects"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="陌生人脸">
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.strangePersonDown"
+              placeholder="下限"
+              :controls="false"
+            ></el-input-number>
+            <span>-</span>
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.strangePersonUp"
+              placeholder="上限"
+              :controls="false"
+            ></el-input-number>
+          </el-form-item>
+          <el-form-item label="异常处理">
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.exceptionDown"
+              placeholder="下限"
+              :controls="false"
+            ></el-input-number>
+            <span>-</span>
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.exceptionUp"
+              placeholder="上限"
+              :controls="false"
+            ></el-input-number>
+          </el-form-item>
+          <el-form-item label="预警数">
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.warningDown"
+              placeholder="下限"
+              :controls="false"
+            ></el-input-number>
+            <span>-</span>
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.warningUp"
+              placeholder="上限"
+              :controls="false"
+            ></el-input-number>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="toPage(1)">查询</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+
+      <div class="part-filter-info">
+        <div class="part-filter-info-main summary-line">
+          <p class="summary-line-item">
+            <i class="icon icon-users"></i
+            ><span class="line-name">全部应考</span><span>50人</span>
+          </p>
+          <p class="summary-line-item">
+            <i class="line-point line-point-info"></i
+            ><span class="line-name">已登录</span><span>5人</span>
+          </p>
+          <p class="summary-line-item">
+            <i class="line-point line-point-success"></i
+            ><span class="line-name">已待考</span><span>3人</span>
+          </p>
+          <p class="summary-line-item">
+            <i class="line-point line-point-primary"></i>
+            <el-popover
+              placement="top-start"
+              width="200"
+              trigger="hover"
+              content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。"
+            >
+              <span class="line-name" slot="reference">考试中</span>
+            </el-popover>
+            <span>2人</span>
+          </p>
+          <p class="summary-line-item">
+            <i class="line-point line-point-danger"></i>
+            <span class="line-name">已交卷</span><span>1人</span>
+          </p>
+        </div>
+      </div>
+    </div>
+
+    <el-table ref="TableList" :data="dataList" border stripe>
+      <el-table-column prop="batchName" label="批次"></el-table-column>
+      <el-table-column prop="examName" label="场次"></el-table-column>
+      <el-table-column prop="examroom" label="考场"> </el-table-column>
+      <el-table-column prop="examId" label="考试ID"></el-table-column>
+      <el-table-column prop="stdCardNo" label="证件号"></el-table-column>
+      <el-table-column prop="stdName" label="姓名"></el-table-column>
+      <el-table-column prop="subjectName" label="联系电话"></el-table-column>
+      <el-table-column prop="subjectCode" label="科目(代码)"></el-table-column>
+      <el-table-column prop="strangeNumber" label="状态"></el-table-column>
+      <el-table-column prop="strangeNumber" label="交卷方式"></el-table-column>
+      <el-table-column prop="strangeNumber" label="陌生人脸"></el-table-column>
+      <el-table-column
+        prop="exceptionNumber"
+        label="异常处理"
+      ></el-table-column>
+      <el-table-column prop="warningNumber" label="预警数"></el-table-column>
+      <el-table-column prop="isDiscipline" label="违纪"></el-table-column>
+      <el-table-column label="操作">
+        <template slot-scope="scope">
+          <el-button
+            class="btn-table-icon"
+            type="primary"
+            icon="icon icon-view"
+            @click="toDetail(scope.row)"
+            >详情</el-button
+          >
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="part-page">
+      <el-pagination
+        background
+        layout="prev, pager, next,total,sizes,jumper"
+        :current-page="current"
+        :total="total"
+        :page-size="size"
+        @current-change="toPage"
+      >
+      </el-pagination>
+    </div>
   </div>
 </template>
 
 <script>
 export default {
-  name: "InvigilationDetail",
+  name: "invigilation-detail",
   data() {
-    return {};
+    return {
+      filter: {
+        batchId: null,
+        examroom: null,
+        subjectId: null,
+        auditStatus: null,
+        content: "",
+        strangePersonUp: null,
+        strangePersonDown: null,
+        exceptionUp: null,
+        exceptionDown: null,
+        warningUp: null,
+        warningDown: null,
+      },
+      current: 1,
+      total: 0,
+      size: 10,
+      batchs: [],
+      exams: [],
+      subjects: [],
+      dataList: [
+        {
+          id: 1,
+          batchName: "第一批次",
+          examName: "第一场次",
+          examroom: "第一考场",
+          examId: "123456",
+          stdCardNo: "000000000000000008",
+          stdName: "张龙龙",
+          subjectName: "大学英语",
+          subjectCode: "10006",
+          strangeNumber: "0",
+          exceptionNumber: "2",
+          warningNumber: "2",
+          isDiscipline: "",
+          auditStatus: "未阅",
+        },
+      ],
+    };
+  },
+  methods: {
+    getList() {},
+    toPage() {},
+    cleanUnread() {},
+    batchAction() {},
+    toDetail(row) {
+      console.log(row);
+      this.$router.push({ name: "WainingDetail", params: { id: row.id } });
+    },
   },
-  methods: {},
 };
 </script>

+ 270 - 3
src/features/invigilation/OnlinePatrol/OnlinePatrol.vue

@@ -1,6 +1,214 @@
 <template>
   <div class="online-patrol">
-    online-patrol
+    <div class="part-box-head">
+      <div class="part-box-head-left"><h1>在线巡考</h1></div>
+    </div>
+    <div class="part-filter">
+      <div class="part-filter-form">
+        <el-form ref="FilterForm" label-position="left" inline>
+          <el-form-item>
+            <el-select
+              v-model="filter.batchId"
+              placeholder="请选择批次"
+              clearable
+            >
+              <el-option
+                v-for="item in batchs"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.examroom"
+              placeholder="请选择场次"
+              clearable
+            >
+              <el-option
+                v-for="item in exams"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.examroom"
+              placeholder="请选择考场"
+              clearable
+            >
+              <el-option
+                v-for="item in exams"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.subjectId"
+              placeholder="筛选状态"
+              clearable
+            >
+              <el-option
+                v-for="item in subjects"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.auditStatus"
+              placeholder="通讯故障"
+              clearable
+            >
+              <el-option
+                v-for="item in subjects"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-input
+              v-model.trim="filter.content"
+              placeholder="申请人姓名"
+              clearable
+            ></el-input>
+          </el-form-item>
+          <el-form-item label="陌生人脸">
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.strangePersonDown"
+              placeholder="下限"
+              :controls="false"
+            ></el-input-number>
+            <span>-</span>
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.strangePersonUp"
+              placeholder="上限"
+              :controls="false"
+            ></el-input-number>
+          </el-form-item>
+          <el-form-item label="异常处理">
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.exceptionDown"
+              placeholder="下限"
+              :controls="false"
+            ></el-input-number>
+            <span>-</span>
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.exceptionUp"
+              placeholder="上限"
+              :controls="false"
+            ></el-input-number>
+          </el-form-item>
+          <el-form-item label="预警数">
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.warningDown"
+              placeholder="下限"
+              :controls="false"
+            ></el-input-number>
+            <span>-</span>
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.warningUp"
+              placeholder="上限"
+              :controls="false"
+            ></el-input-number>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="toPage(1)">查询</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+
+      <div class="part-filter-info">
+        <div class="part-filter-info-main summary-line">
+          <p class="summary-line-item">
+            <i class="icon icon-users"></i
+            ><span class="line-name">全部应考</span><span>50人</span>
+          </p>
+          <p class="summary-line-item">
+            <i class="line-point line-point-info"></i
+            ><span class="line-name">已登录</span><span>5人</span>
+          </p>
+          <p class="summary-line-item">
+            <i class="line-point line-point-success"></i
+            ><span class="line-name">已待考</span><span>3人</span>
+          </p>
+          <p class="summary-line-item">
+            <i class="line-point line-point-primary"></i
+            ><span class="line-name">考试中</span><span>2人</span>
+          </p>
+          <p class="summary-line-item">
+            <i class="line-point line-point-danger"></i>
+            <el-popover
+              placement="top-start"
+              width="200"
+              trigger="hover"
+              content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。"
+            >
+              <span class="line-name" slot="reference">通讯故障</span>
+            </el-popover>
+            <span>1人</span>
+          </p>
+        </div>
+      </div>
+    </div>
+
+    <el-table ref="TableList" :data="dataList" border stripe>
+      <el-table-column prop="examroom" label="考场(代码)"> </el-table-column>
+      <el-table-column prop="stdCardNo" label="证件号"></el-table-column>
+      <el-table-column prop="stdName" label="姓名"></el-table-column>
+      <el-table-column prop="subjectCode" label="科目(代码)"></el-table-column>
+      <el-table-column prop="subjectCode" label="剩余时间"></el-table-column>
+      <el-table-column prop="subjectCode" label="进度"></el-table-column>
+      <el-table-column prop="subjectCode" label="通讯"></el-table-column>
+      <el-table-column prop="subjectCode" label="更新时间"></el-table-column>
+      <el-table-column
+        prop="exceptionNumber"
+        label="异常处理"
+      ></el-table-column>
+      <el-table-column prop="strangeNumber" label="陌生人脸"></el-table-column>
+      <el-table-column prop="warningNumber" label="预警数"></el-table-column>
+      <el-table-column prop="isDiscipline" label="违纪"></el-table-column>
+      <el-table-column label="操作">
+        <template slot-scope="scope">
+          <el-button
+            class="btn-table-icon"
+            type="primary"
+            icon="icon icon-view"
+            @click="toDetail(scope.row)"
+            >详情</el-button
+          >
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="part-page">
+      <el-pagination
+        background
+        layout="prev, pager, next,total,sizes,jumper"
+        :current-page="current"
+        :total="total"
+        :page-size="size"
+        @current-change="toPage"
+      >
+      </el-pagination>
+    </div>
+
+    <div class="patrol-analysis part-box"></div>
   </div>
 </template>
 
@@ -8,8 +216,67 @@
 export default {
   name: "online-patrol",
   data() {
-    return {};
+    return {
+      filter: {
+        batchId: null,
+        examroom: null,
+        subjectId: null,
+        auditStatus: null,
+        content: "",
+        strangePersonUp: null,
+        strangePersonDown: null,
+        exceptionUp: null,
+        exceptionDown: null,
+        warningUp: null,
+        warningDown: null,
+      },
+      current: 1,
+      total: 0,
+      size: 10,
+      batchs: [],
+      exams: [],
+      subjects: [],
+      dataList: [
+        {
+          id: 1,
+          batchName: "第一批次",
+          examName: "第一场次",
+          examroom: "第一考场",
+          examId: "123456",
+          stdCardNo: "000000000000000008",
+          stdName: "张龙龙",
+          subjectName: "大学英语",
+          subjectCode: "10006",
+          strangeNumber: "0",
+          exceptionNumber: "2",
+          warningNumber: "2",
+          isDiscipline: "",
+          auditStatus: "未阅",
+        },
+      ],
+    };
+  },
+  methods: {
+    getList() {},
+    toPage() {},
+    cleanUnread() {},
+    batchAction() {},
+    toDetail(row) {
+      console.log(row);
+      this.$router.push({ name: "WainingDetail", params: { id: row.id } });
+    },
   },
-  methods: {},
 };
 </script>
+
+<style lang="scss" scoped>
+.online-patrol {
+  .el-form {
+    margin-bottom: 10px;
+  }
+}
+.patrol-analysis {
+  height: 355px;
+  margin-top: 20px;
+}
+</style>

+ 179 - 3
src/features/invigilation/ProgressDetail/ProgressDetail.vue

@@ -1,6 +1,134 @@
 <template>
   <div class="ProgressDetail">
-    ProgressDetail
+    <div class="part-box-head">
+      <div class="part-box-head-left"><h1>进度查询</h1></div>
+    </div>
+    <div class="part-filter">
+      <div class="part-filter-form">
+        <el-form ref="FilterForm" label-position="left" inline>
+          <el-form-item>
+            <el-select
+              v-model="filter.batchId"
+              placeholder="请选择批次"
+              clearable
+            >
+              <el-option
+                v-for="item in batchs"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.examroom"
+              placeholder="请选择考场"
+              clearable
+            >
+              <el-option
+                v-for="item in exams"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.subjectId"
+              placeholder="请选择科目"
+              clearable
+            >
+              <el-option
+                v-for="item in subjects"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-input
+              v-model.trim="filter.content"
+              placeholder="姓名/证件号"
+              clearable
+            ></el-input>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="toPage(1)">查询</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="part-filter-info">
+        <div class="part-filter-info-main summary-line">
+          <p class="summary-line-item">
+            <i class="icon icon-users"></i>
+            <span class="line-name">全部应考</span>
+            <span>50人</span>
+          </p>
+          <p class="summary-line-item">
+            <i class="icon icon-rate"></i>
+            <span class="lin-name">完成率</span><span>20人</span>
+          </p>
+          <p class="summary-line-item">
+            <i class="line-point line-point-success"></i>
+            <el-popover
+              placement="bottom-start"
+              width="200"
+              trigger="hover"
+              content="通讯故障。"
+            >
+              <span class="line-name" slot="reference">已待考</span>
+            </el-popover>
+            <span>5人</span>
+          </p>
+          <p class="summary-line-item">
+            <i class="line-point line-point-danger"></i><span>未完成</span
+            ><span>3人</span>
+          </p>
+        </div>
+        <div class="part-filter-info-sub">
+          <el-button type="primary" icon="el-icon-download" @click="toExport"
+            >导出查询结果</el-button
+          >
+        </div>
+      </div>
+    </div>
+
+    <el-table ref="TableList" :data="dataList" border stripe>
+      <el-table-column prop="examroom" label="批次名称(代码)">
+      </el-table-column>
+      <el-table-column
+        prop="stdCardNo"
+        label="场次名称(代码)"
+      ></el-table-column>
+      <el-table-column prop="stdName" label="考场名称(代码)"></el-table-column>
+      <el-table-column prop="subjectCode" label="证件号"></el-table-column>
+      <el-table-column prop="subjectCode" label="姓名"></el-table-column>
+      <el-table-column prop="subjectCode" label="联系电话"></el-table-column>
+      <el-table-column prop="subjectCode" label="科目(代码)"></el-table-column>
+      <el-table-column
+        prop="exceptionNumber"
+        label="异常处理"
+      ></el-table-column>
+      <el-table-column
+        prop="strangeNumber"
+        label="剩余考试次数"
+      ></el-table-column>
+      <el-table-column prop="warningNumber" label="完成状态"></el-table-column>
+    </el-table>
+    <div class="part-page">
+      <el-pagination
+        background
+        layout="prev, pager, next,total,sizes,jumper"
+        :current-page="current"
+        :total="total"
+        :page-size="size"
+        @current-change="toPage"
+      >
+      </el-pagination>
+    </div>
   </div>
 </template>
 
@@ -8,8 +136,56 @@
 export default {
   name: "ProgressDetail",
   data() {
-    return {};
+    return {
+      filter: {
+        batchId: null,
+        examroom: null,
+        subjectId: null,
+        auditStatus: null,
+        content: "",
+        strangePersonUp: null,
+        strangePersonDown: null,
+        exceptionUp: null,
+        exceptionDown: null,
+        warningUp: null,
+        warningDown: null,
+      },
+      current: 1,
+      total: 0,
+      size: 10,
+      batchs: [],
+      exams: [],
+      subjects: [],
+      dataList: [
+        {
+          id: 1,
+          batchName: "第一批次",
+          examName: "第一场次",
+          examroom: "第一考场",
+          examId: "123456",
+          stdCardNo: "000000000000000008",
+          stdName: "张龙龙",
+          subjectName: "大学英语",
+          subjectCode: "10006",
+          strangeNumber: "0",
+          exceptionNumber: "2",
+          warningNumber: "2",
+          isDiscipline: "",
+          auditStatus: "未阅",
+        },
+      ],
+    };
+  },
+  methods: {
+    getList() {},
+    toPage() {},
+    cleanUnread() {},
+    batchAction() {},
+    toDetail(row) {
+      console.log(row);
+      this.$router.push({ name: "WainingDetail", params: { id: row.id } });
+    },
+    toExport() {},
   },
-  methods: {},
 };
 </script>

+ 277 - 5
src/features/invigilation/RealtimeMonitoring/RealtimeMonitoring.vue

@@ -1,15 +1,287 @@
 <template>
-  <div class="RealtimeMonitoring">
-    RealtimeMonitoring
+  <div class="realtime-monitoring">
+    <div class="realtime-top clear-float">
+      <p>考场名称:哈尔滨工业大学第十一考场</p>
+      <el-select v-model="batchId" placeholder="请选择批次" clearable>
+        <el-option
+          v-for="item in batchs"
+          :key="item.id"
+          :value="item.id"
+          :label="item.name"
+        ></el-option>
+      </el-select>
+      <p>现在是2020年6月2日 星期二 上午 09:30:12</p>
+    </div>
+
+    <div class="part-box-head">
+      <div class="part-box-head-left">
+        <h1>实时监控台</h1>
+      </div>
+      <div class="part-box-head-right">
+        <el-radio-group size="small" v-model="pageType">
+          <el-radio-button label="0">
+            <i class="el-icon-s-fold"></i>列表</el-radio-button
+          >
+          <el-radio-button label="1"
+            ><i class="el-icon-video-camera"></i> 视频</el-radio-button
+          >
+        </el-radio-group>
+      </div>
+    </div>
+
+    <div class="part-filter">
+      <div class="part-filter-info">
+        <div class="part-filter-info-main summary-line">
+          <p class="summary-line-item">
+            <i class="icon icon-users"></i
+            ><span class="line-name">全部应考</span><span>50人</span>
+          </p>
+          <p class="summary-line-item">
+            <i class="line-point line-point-info"></i
+            ><span class="line-name">已登录</span><span>5人</span>
+          </p>
+          <p class="summary-line-item">
+            <i class="line-point line-point-success"></i
+            ><span class="line-name">已待考</span><span>3人</span>
+          </p>
+          <p class="summary-line-item">
+            <i class="line-point line-point-primary"></i
+            ><span class="line-name">考试中</span><span>2人</span>
+          </p>
+          <p class="summary-line-item">
+            <i class="line-point line-point-danger"></i>
+            <el-popover
+              placement="top-start"
+              width="200"
+              trigger="hover"
+              content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。"
+            >
+              <span class="line-name" slot="reference">通讯故障</span>
+            </el-popover>
+            <span>1人</span>
+          </p>
+        </div>
+        <div class="part-filter-info-sub">
+          <el-button icon="el-icon-phone-outline" type="success"
+            >通话待办</el-button
+          >
+        </div>
+      </div>
+
+      <div class="part-filter-form">
+        <el-form ref="FilterForm" label-position="left" inline>
+          <el-form-item>
+            <el-select
+              v-model="filter.batchId"
+              placeholder="试题下载"
+              clearable
+            >
+              <el-option
+                v-for="item in batchs"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.examroom"
+              placeholder="考试状态"
+              clearable
+            >
+              <el-option
+                v-for="item in exams"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.examroom"
+              placeholder="通讯故障"
+              clearable
+            >
+              <el-option
+                v-for="item in exams"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="预警量">
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.warningDown"
+              placeholder="下限"
+              :controls="false"
+            ></el-input-number>
+            <span>-</span>
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.warningUp"
+              placeholder="上限"
+              :controls="false"
+            ></el-input-number>
+          </el-form-item>
+          <el-form-item>
+            <el-input
+              v-model.trim="filter.content"
+              placeholder="姓名/证件号"
+              clearable
+            ></el-input>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="toPage(1)">查询</el-button>
+          </el-form-item>
+        </el-form>
+
+        <div class="part-filter-form-action">
+          <el-button type="primary" icon="icon icon-clean" @click="cleanUnread"
+            >手动收卷</el-button
+          >
+          <el-button type="danger" icon="icon icon-warning" @click="batchAction"
+            >结束监考</el-button
+          >
+        </div>
+      </div>
+    </div>
+
+    <el-table ref="TableList" :data="dataList" border stripe>
+      <el-table-column prop="stdCardNo" label="证件号"></el-table-column>
+      <el-table-column prop="stdName" label="姓名"></el-table-column>
+      <el-table-column prop="subjectCode" label="科目名称"></el-table-column>
+      <el-table-column prop="subjectCode" label="科目代码"></el-table-column>
+      <el-table-column prop="subjectCode" label="剩余时间"></el-table-column>
+      <el-table-column prop="subjectCode" label="试题下载"></el-table-column>
+      <el-table-column prop="subjectCode" label="考试状态"></el-table-column>
+      <el-table-column prop="subjectCode" label="进度"></el-table-column>
+      <el-table-column prop="subjectCode" label="通讯"></el-table-column>
+      <el-table-column prop="subjectCode" label="推流通讯"></el-table-column>
+      <el-table-column prop="subjectCode" label="IP"></el-table-column>
+      <el-table-column prop="subjectCode" label="更新时间"></el-table-column>
+      <el-table-column prop="warningNumber" label="预警数"></el-table-column>
+      <el-table-column prop="isDiscipline" label="违纪"></el-table-column>
+      <el-table-column label="操作">
+        <template slot-scope="scope">
+          <el-button
+            class="btn-table-icon"
+            type="primary"
+            icon="icon icon-view"
+            @click="toDetail(scope.row)"
+            >详情</el-button
+          >
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="part-page">
+      <el-pagination
+        background
+        layout="prev, pager, next,total,sizes,jumper"
+        :current-page="current"
+        :total="total"
+        :page-size="size"
+        @current-change="toPage"
+      >
+      </el-pagination>
+    </div>
   </div>
 </template>
 
 <script>
 export default {
-  name: "RealtimeMonitoring",
+  name: "realtime-monitoring",
   data() {
-    return {};
+    return {
+      filter: {
+        examroom: null,
+        subjectId: null,
+        auditStatus: null,
+        content: "",
+        strangePersonUp: null,
+        strangePersonDown: null,
+        exceptionUp: null,
+        exceptionDown: null,
+        warningUp: null,
+        warningDown: null,
+      },
+      current: 1,
+      total: 0,
+      size: 10,
+      batchId: "",
+      batchs: [],
+      exams: [],
+      subjects: [],
+      pageType: "0",
+      dataList: [
+        {
+          id: 1,
+          batchName: "第一批次",
+          examName: "第一场次",
+          examroom: "第一考场",
+          examId: "123456",
+          stdCardNo: "000000000000000008",
+          stdName: "张龙龙",
+          subjectName: "大学英语",
+          subjectCode: "10006",
+          strangeNumber: "0",
+          exceptionNumber: "2",
+          warningNumber: "2",
+          isDiscipline: "",
+          auditStatus: "未阅",
+        },
+      ],
+    };
+  },
+  methods: {
+    getList() {},
+    toPage() {},
+    cleanUnread() {},
+    batchAction() {},
+    toDetail(row) {
+      console.log(row);
+      this.$router.push({ name: "WainingDetail", params: { id: row.id } });
+    },
   },
-  methods: {},
 };
 </script>
+
+<style lang="scss" scoped>
+.realtime-top {
+  position: relative;
+  padding: 9px 20px 9px 73px;
+  background: rgba(24, 134, 254, 1);
+  border-radius: 6px;
+  color: #fff;
+  margin-bottom: 30px;
+
+  &::before {
+    content: "";
+    display: block;
+    position: absolute;
+    width: 59px;
+    height: 49px;
+    left: 13px;
+    top: 0;
+    background-image: url(../../../assets/bg-stars.png);
+    background-size: 100% 100%;
+  }
+
+  > * {
+    float: left;
+    line-height: 32px;
+    margin: 0;
+  }
+  > p:first-child {
+    margin-right: 40px;
+  }
+  > p:last-child {
+    float: right;
+    font-size: 12px;
+    opacity: 0.8;
+  }
+}
+</style>

+ 161 - 3
src/features/invigilation/ReexamApply/ReexamApply.vue

@@ -1,6 +1,113 @@
 <template>
   <div class="ReexamApply">
-    ReexamApply
+    <div class="part-box-head">
+      <div class="part-box-head-left"><h1>重考申请</h1></div>
+    </div>
+
+    <div class="part-filter">
+      <div class="part-filter-form">
+        <el-form ref="FilterForm" label-position="left" inline>
+          <el-form-item>
+            <el-select
+              v-model="filter.batchId"
+              placeholder="请选择批次"
+              clearable
+            >
+              <el-option
+                v-for="item in batchs"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.examroom"
+              placeholder="请选择考场"
+              clearable
+            >
+              <el-option
+                v-for="item in exams"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.subjectId"
+              placeholder="请选择科目"
+              clearable
+            >
+              <el-option
+                v-for="item in subjects"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-input
+              v-model.trim="filter.content"
+              placeholder="姓名/证件号"
+              clearable
+            ></el-input>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="toPage(1)">查询</el-button>
+          </el-form-item>
+        </el-form>
+
+        <div class="part-filter-form-action">
+          <el-button type="primary" icon="el-icon-s-tools" @click="batchAction"
+            >批量设置重考</el-button
+          >
+        </div>
+      </div>
+    </div>
+
+    <el-table
+      ref="TableList"
+      :data="dataList"
+      @selection-change="handleSelectionChange"
+      border
+      stripe
+    >
+      <el-table-column type="selection" width="55"> </el-table-column>
+      <el-table-column
+        prop="batchName"
+        label="考场名称(代码)"
+      ></el-table-column>
+      <el-table-column prop="stdCardNo" label="证件号"></el-table-column>
+      <el-table-column prop="stdName" label="姓名"></el-table-column>
+      <el-table-column prop="subjectName" label="科目名称"></el-table-column>
+      <el-table-column prop="subjectCode" label="科目(代码)"></el-table-column>
+      <el-table-column label="操作">
+        <template slot-scope="scope">
+          <el-button
+            class="btn-table-icon"
+            type="primary"
+            icon="el-icon-s-tools"
+            @click="toDetail(scope.row)"
+            >重考</el-button
+          >
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="part-page">
+      <el-pagination
+        background
+        layout="prev, pager, next,total,sizes,jumper"
+        :current-page="current"
+        :total="total"
+        :page-size="size"
+        @current-change="toPage"
+      >
+      </el-pagination>
+    </div>
   </div>
 </template>
 
@@ -8,8 +115,59 @@
 export default {
   name: "ReexamApply",
   data() {
-    return {};
+    return {
+      filter: {
+        batchId: null,
+        examroom: null,
+        subjectId: null,
+        auditStatus: null,
+        content: "",
+        strangePersonUp: null,
+        strangePersonDown: null,
+        exceptionUp: null,
+        exceptionDown: null,
+        warningUp: null,
+        warningDown: null,
+      },
+      current: 1,
+      total: 0,
+      size: 10,
+      batchs: [],
+      exams: [],
+      subjects: [],
+      dataList: [
+        {
+          id: 1,
+          batchName: "第一批次",
+          examName: "第一场次",
+          examroom: "第一考场",
+          examId: "123456",
+          stdCardNo: "000000000000000008",
+          stdName: "张龙龙",
+          subjectName: "大学英语",
+          subjectCode: "10006",
+          strangeNumber: "0",
+          exceptionNumber: "2",
+          warningNumber: "2",
+          isDiscipline: "",
+          auditStatus: "未阅",
+        },
+      ],
+      multipleSelection: [],
+    };
+  },
+  methods: {
+    getList() {},
+    toPage() {},
+    handleSelectionChange(val) {
+      this.multipleSelection = val;
+    },
+    cleanUnread() {},
+    batchAction() {},
+    toDetail(row) {
+      console.log(row);
+      this.$router.push({ name: "WainingDetail", params: { id: row.id } });
+    },
   },
-  methods: {},
 };
 </script>

+ 183 - 5
src/features/invigilation/ReexamChecked/ReexamChecked.vue

@@ -1,15 +1,193 @@
 <template>
-  <div class="ReexamChecked">
-    ReexamChecked
+  <div class="reexam-checked">
+    <div class="part-box-head">
+      <div class="part-box-head-left"><h1>重考已审</h1></div>
+    </div>
+    <div class="part-filter">
+      <div class="part-filter-form">
+        <el-form ref="FilterForm" label-position="left" inline>
+          <el-form-item>
+            <el-select
+              v-model="filter.batchId"
+              placeholder="请选择原批次"
+              clearable
+            >
+              <el-option
+                v-for="item in batchs"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.examroom"
+              placeholder="请选择考场"
+              clearable
+            >
+              <el-option
+                v-for="item in exams"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.subjectId"
+              placeholder="请选择科目"
+              clearable
+            >
+              <el-option
+                v-for="item in subjects"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.subjectId"
+              placeholder="请选择申请理由"
+              clearable
+            >
+              <el-option
+                v-for="item in subjects"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-input
+              v-model.trim="filter.content"
+              placeholder="姓名/证件号"
+              clearable
+            ></el-input>
+          </el-form-item>
+          <el-form-item label="申请时间">
+            <el-date-picker
+              v-model="applyTime"
+              type="daterange"
+              range-separator="至"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+            >
+            </el-date-picker>
+          </el-form-item>
+          <el-form-item>
+            <el-input
+              v-model.trim="filter.content"
+              placeholder="申请人姓名"
+              clearable
+            ></el-input>
+          </el-form-item>
+
+          <el-form-item>
+            <el-button type="primary" @click="toPage(1)">查询</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+
+    <el-table ref="TableList" :data="dataList" border stripe>
+      <el-table-column prop="examroom" label="考场(代码)"> </el-table-column>
+      <el-table-column prop="stdCardNo" label="证件号"></el-table-column>
+      <el-table-column prop="stdCardNo" label="科目名称"></el-table-column>
+      <el-table-column prop="stdName" label="姓名"></el-table-column>
+      <el-table-column prop="subjectCode" label="科目(代码)"></el-table-column>
+      <el-table-column prop="subjectCode" label="原批次"></el-table-column>
+      <el-table-column prop="subjectCode" label="原场次"></el-table-column>
+      <el-table-column prop="subjectCode" label="申请理由"></el-table-column>
+      <el-table-column prop="subjectCode" label="申请时间"></el-table-column>
+      <el-table-column prop="subjectCode" label="申请人"></el-table-column>
+      <el-table-column prop="subjectCode" label="审核时间"></el-table-column>
+      <el-table-column prop="subjectCode" label="审核结果"></el-table-column>
+      <el-table-column prop="subjectCode" label="审核人"></el-table-column>
+      <el-table-column label="操作">
+        <template slot-scope="scope">
+          <el-button
+            class="btn-table-icon"
+            type="primary"
+            icon="icon icon-view"
+            @click="toDetail(scope.row)"
+            >详情</el-button
+          >
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="part-page">
+      <el-pagination
+        background
+        layout="prev, pager, next,total,sizes,jumper"
+        :current-page="current"
+        :total="total"
+        :page-size="size"
+        @current-change="toPage"
+      >
+      </el-pagination>
+    </div>
   </div>
 </template>
 
 <script>
 export default {
-  name: "ReexamChecked",
+  name: "reexam-checked",
   data() {
-    return {};
+    return {
+      filter: {
+        batchId: null,
+        examroom: null,
+        subjectId: null,
+        auditStatus: null,
+        content: "",
+        strangePersonUp: null,
+        strangePersonDown: null,
+        exceptionUp: null,
+        exceptionDown: null,
+        warningUp: null,
+        warningDown: null,
+      },
+      current: 1,
+      total: 0,
+      size: 10,
+      batchs: [],
+      exams: [],
+      subjects: [],
+      applyTime: "",
+      dataList: [
+        {
+          id: 1,
+          batchName: "第一批次",
+          examName: "第一场次",
+          examroom: "第一考场",
+          examId: "123456",
+          stdCardNo: "000000000000000008",
+          stdName: "张龙龙",
+          subjectName: "大学英语",
+          subjectCode: "10006",
+          strangeNumber: "0",
+          exceptionNumber: "2",
+          warningNumber: "2",
+          isDiscipline: "",
+          auditStatus: "未阅",
+        },
+      ],
+    };
+  },
+  methods: {
+    getList() {},
+    toPage() {},
+    cleanUnread() {},
+    batchAction() {},
+    toDetail(row) {
+      console.log(row);
+      this.$router.push({ name: "WainingDetail", params: { id: row.id } });
+    },
   },
-  methods: {},
 };
 </script>

+ 180 - 5
src/features/invigilation/ReexamPending/ReexamPending.vue

@@ -1,15 +1,190 @@
 <template>
-  <div class="ReexamPending">
-    ReexamPending
+  <div class="reexam-pending">
+    <div class="part-box-head">
+      <div class="part-box-head-left"><h1>重考待审</h1></div>
+    </div>
+    <div class="part-filter">
+      <div class="part-filter-form">
+        <el-form ref="FilterForm" label-position="left" inline>
+          <el-form-item>
+            <el-select
+              v-model="filter.batchId"
+              placeholder="请选择原批次"
+              clearable
+            >
+              <el-option
+                v-for="item in batchs"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.examroom"
+              placeholder="请选择考场"
+              clearable
+            >
+              <el-option
+                v-for="item in exams"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.subjectId"
+              placeholder="请选择科目"
+              clearable
+            >
+              <el-option
+                v-for="item in subjects"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.subjectId"
+              placeholder="请选择申请理由"
+              clearable
+            >
+              <el-option
+                v-for="item in subjects"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-input
+              v-model.trim="filter.content"
+              placeholder="姓名/证件号"
+              clearable
+            ></el-input>
+          </el-form-item>
+          <el-form-item label="申请时间">
+            <el-date-picker
+              v-model="applyTime"
+              type="daterange"
+              range-separator="至"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+            >
+            </el-date-picker>
+          </el-form-item>
+          <el-form-item>
+            <el-input
+              v-model.trim="filter.content"
+              placeholder="申请人姓名"
+              clearable
+            ></el-input>
+          </el-form-item>
+
+          <el-form-item>
+            <el-button type="primary" @click="toPage(1)">查询</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+
+    <el-table ref="TableList" :data="dataList" border stripe>
+      <el-table-column prop="examroom" label="考场(代码)"> </el-table-column>
+      <el-table-column prop="stdCardNo" label="证件号"></el-table-column>
+      <el-table-column prop="stdCardNo" label="科目名称"></el-table-column>
+      <el-table-column prop="stdName" label="姓名"></el-table-column>
+      <el-table-column prop="subjectCode" label="科目(代码)"></el-table-column>
+      <el-table-column prop="subjectCode" label="原批次"></el-table-column>
+      <el-table-column prop="subjectCode" label="原场次"></el-table-column>
+      <el-table-column prop="subjectCode" label="申请理由"></el-table-column>
+      <el-table-column prop="subjectCode" label="申请时间"></el-table-column>
+      <el-table-column prop="subjectCode" label="申请人"></el-table-column>
+      <el-table-column label="操作">
+        <template slot-scope="scope">
+          <el-button
+            class="btn-table-icon"
+            type="primary"
+            icon="icon icon-view"
+            @click="toDetail(scope.row)"
+            >详情</el-button
+          >
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="part-page">
+      <el-pagination
+        background
+        layout="prev, pager, next,total,sizes,jumper"
+        :current-page="current"
+        :total="total"
+        :page-size="size"
+        @current-change="toPage"
+      >
+      </el-pagination>
+    </div>
   </div>
 </template>
 
 <script>
 export default {
-  name: "ReexamPending",
+  name: "reexam-pending",
   data() {
-    return {};
+    return {
+      filter: {
+        batchId: null,
+        examroom: null,
+        subjectId: null,
+        auditStatus: null,
+        content: "",
+        strangePersonUp: null,
+        strangePersonDown: null,
+        exceptionUp: null,
+        exceptionDown: null,
+        warningUp: null,
+        warningDown: null,
+      },
+      current: 1,
+      total: 0,
+      size: 10,
+      batchs: [],
+      exams: [],
+      subjects: [],
+      applyTime: "",
+      dataList: [
+        {
+          id: 1,
+          batchName: "第一批次",
+          examName: "第一场次",
+          examroom: "第一考场",
+          examId: "123456",
+          stdCardNo: "000000000000000008",
+          stdName: "张龙龙",
+          subjectName: "大学英语",
+          subjectCode: "10006",
+          strangeNumber: "0",
+          exceptionNumber: "2",
+          warningNumber: "2",
+          isDiscipline: "",
+          auditStatus: "未阅",
+        },
+      ],
+    };
+  },
+  methods: {
+    getList() {},
+    toPage() {},
+    cleanUnread() {},
+    batchAction() {},
+    toDetail(row) {
+      console.log(row);
+      this.$router.push({ name: "WainingDetail", params: { id: row.id } });
+    },
   },
-  methods: {},
 };
 </script>

+ 86 - 0
src/features/invigilation/StudentLogManage/StudentLogDetailDialog.vue

@@ -0,0 +1,86 @@
+<template>
+  <el-dialog
+    class="student-log-detail-dialog"
+    :visible.sync="modalIsShow"
+    title="日志详情"
+    width="1000px"
+    top="94px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @open="visibleChange"
+  >
+    <el-table ref="TableList" :data="dataList" border stripe>
+      <el-table-column prop="creatTime" label="发生时间"></el-table-column>
+      <el-table-column prop="type" label="事件类型"></el-table-column>
+      <el-table-column prop="content" label="详情"></el-table-column>
+    </el-table>
+    <div class="part-page">
+      <el-pagination
+        background
+        layout="prev, pager, next,total,sizes,jumper"
+        :current-page="current"
+        :total="total"
+        :page-size="size"
+        @current-change="toPage"
+      >
+      </el-pagination>
+    </div>
+    <div slot="footer"></div>
+  </el-dialog>
+</template>
+
+<script>
+// import { updateCourse } from "../api";
+
+export default {
+  name: "student-log-detail-dialog",
+  props: {
+    detailId: {
+      type: [String, Number],
+      required: true,
+    },
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      current: 1,
+      total: 0,
+      size: 10,
+      dataList: [
+        {
+          id: 1,
+          creatTime: "2020-08-02 15:12:24",
+          type: "进入考试",
+          content: "MAC地址:",
+        },
+        {
+          id: 2,
+          creatTime: "2020-08-02 15:12:24",
+          type: "网络断开",
+          content: "系统自动重试2次,网络已恢复",
+        },
+        {
+          id: 3,
+          creatTime: "2020-08-02 15:12:24",
+          type: "重新登录",
+          content: "MAC地址:",
+        },
+      ],
+    };
+  },
+  methods: {
+    visibleChange() {
+      this.getList();
+    },
+    getList() {},
+    toPage() {},
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+  },
+};
+</script>

+ 163 - 3
src/features/invigilation/StudentLogManage/StudentLogManage.vue

@@ -1,15 +1,175 @@
 <template>
   <div class="StudentLogManage">
-    StudentLogManage
+    <div class="part-box-head">
+      <div class="part-box-head-left"><h1>考生端日志管理</h1></div>
+    </div>
+
+    <div class="part-filter">
+      <div class="part-filter-form">
+        <el-form ref="FilterForm" label-position="left" inline>
+          <el-form-item>
+            <el-select
+              v-model="filter.examId"
+              placeholder="请选择考试任务"
+              clearable
+            >
+              <el-option
+                v-for="item in batchs"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.batchId"
+              placeholder="请选择批次"
+              clearable
+            >
+              <el-option
+                v-for="item in exams"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.examroom"
+              placeholder="请选择考场"
+              clearable
+            >
+              <el-option
+                v-for="item in exams"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.subjectId"
+              placeholder="请选择科目"
+              clearable
+            >
+              <el-option
+                v-for="item in subjects"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-input
+              v-model.trim="filter.content"
+              placeholder="姓名/证件号"
+              clearable
+            ></el-input>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="toPage(1)">查询</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+
+    <el-table ref="TableList" :data="dataList" border stripe>
+      <el-table-column prop="batchName" label="排序"></el-table-column>
+      <el-table-column prop="batchName" label="批次名称(ID)"></el-table-column>
+      <el-table-column prop="examName" label="场次ID"></el-table-column>
+      <el-table-column prop="examName" label="考场名称(代码)"></el-table-column>
+      <el-table-column prop="stdCardNo" label="证件号"></el-table-column>
+      <el-table-column prop="stdName" label="姓名"></el-table-column>
+      <el-table-column prop="subjectCode" label="科目(代码)">
+        <template slot-scope="scope">
+          <span>{{ scope.row.subjectName }}({{ scope.row.subjectCode }})</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作">
+        <template slot-scope="scope">
+          <el-button
+            class="btn-table-icon"
+            type="primary"
+            icon="icon icon-view"
+            @click="toDetail(scope.row)"
+            >详情</el-button
+          >
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="part-page">
+      <el-pagination
+        background
+        layout="prev, pager, next,total,sizes,jumper"
+        :current-page="current"
+        :total="total"
+        :page-size="size"
+        @current-change="toPage"
+      >
+      </el-pagination>
+    </div>
+
+    <student-log-detail-dialog
+      :detail-id="curLogId"
+      ref="StudentLogDetailDialog"
+    ></student-log-detail-dialog>
   </div>
 </template>
 
 <script>
+import StudentLogDetailDialog from "./StudentLogDetailDialog";
+
 export default {
   name: "StudentLogManage",
+  components: { StudentLogDetailDialog },
   data() {
-    return {};
+    return {
+      filter: {
+        examId: null,
+        batchId: null,
+        examroom: null,
+        subjectId: null,
+        content: "",
+      },
+      current: 1,
+      total: 0,
+      size: 10,
+      batchs: [],
+      exams: [],
+      subjects: [],
+      dataList: [
+        {
+          id: 1,
+          batchName: "第一批次",
+          examName: "第一场次",
+          examroom: "第一考场",
+          examId: "123456",
+          stdCardNo: "000000000000000008",
+          stdName: "张龙龙",
+          subjectName: "大学英语",
+          subjectCode: "10006",
+          strangeNumber: "0",
+          exceptionNumber: "2",
+          warningNumber: "2",
+          isDiscipline: "",
+          auditStatus: "未阅",
+        },
+      ],
+      curLogId: "",
+    };
+  },
+  methods: {
+    getList() {},
+    toPage() {},
+    toDetail(row) {
+      console.log(row);
+      this.curLogId = row.id;
+      this.$refs.StudentLogDetailDialog.open();
+    },
   },
-  methods: {},
 };
 </script>

+ 108 - 112
src/features/invigilation/VideoCommunication/VideoCommunication.vue

@@ -41,12 +41,8 @@
       fullscreen
     >
       <div class="communication-box">
-        <div class="communication-host">
-          <video src="" ref="VideoHost" v-show="videoHostReady"></video>
-        </div>
-        <div class="communication-guest">
-          <video src="" ref="VideoGuest" v-show="videoGuestReady"></video>
-        </div>
+        <div class="communication-host" id="communication-host"></div>
+        <div class="communication-guest" id="communication-guest"></div>
         <div class="communication-action">
           <el-button round type="danger" @click="hangup">结束通话</el-button>
         </div>
@@ -61,10 +57,11 @@
 </template>
 
 <script>
-import { initRongRTC, connectIM } from "@/plugins/RongIM";
+import TRTC from "trtc-js-sdk";
+import { communicationList, communicationOver } from "@/api/invigilation";
 
 export default {
-  name: "VideoCommunication",
+  name: "video-communication",
   data() {
     return {
       pageType: "0",
@@ -89,138 +86,137 @@ export default {
           stdName: "于梁",
         },
       ],
+      setT: null,
       durationTime: "00:10:23",
-      token:
-        "iy7OAXkkr+ooBirzYIT8BC7ePbpkBY7eRygkv+h3Iok=@tlem.cn.rongnav.com;tlem.cn.rongcfg.com",
-      rongUserId: null,
-      rongRTC: null,
-      room: null,
-      stream: null,
-      streamUser: null,
-      curStreamType: null,
-      videoHostReady: false,
-      videoGuestReady: false,
+      appId: "1400411036",
+      videoUserSig:
+        "eJyrVgrxCdYrSy1SslIy0jNQ0gHzM1NS80oy0zLBwsUGZlDh4pTsxIKCzBQlK0MTAwMTQ0MDYzOITGpFQWZRKlDc1NTUyMDAACJakpkLFrM0NzI0NLKEqi3OTAeaWmoeWZAd6O1h7B4YbhJoXOYdkeNmEebm5Z1R5JxXEmVhbOZkYWFQkZvjV26rVAsAYQkvlQ__",
+      videoUserId: "s06",
+      client: null,
+      localStream: null,
     };
   },
   mounted() {
-    this.initRong();
+    this.initClinet();
+    this.getCommunicationList();
   },
   methods: {
-    async initRong() {
-      this.rongUserId = await connectIM(this.token).catch((error) => {
-        console.log(error);
-      });
-      if (!this.rongUserId) {
-        this.$message.error("初始化错误!");
-        return;
-      }
-      const { Mode, ROLE } = window.RongRTC;
-      this.rongRTC = initRongRTC({
-        debug: false,
-        mode: Mode.LIVE,
-        liveRole: ROLE.ANCHOR,
+    async initClinet() {
+      this.client = TRTC.createClient({
+        mode: "rtc",
+        sdkAppId: this.appId,
+        userId: this.videoUserId,
+        userSig: this.videoUserSig,
       });
     },
+    async getCommunicationList() {
+      const data = await communicationList().catch(() => {});
+      this.students = data.records;
+
+      // this.setT = setTimeout(() => {
+      //   this.getCommunicationList();
+      // }, 2000);
+    },
     async answer(student, isVideo) {
       this.dialogVisible = true;
+      // 结束学生的通话申请
+      await communicationOver({ recordId: student.recordId });
 
-      const { Room, Stream, StreamType } = this.rongRTC;
-      // 初始化房间
-      this.room = new Room({
-        id: student.roomId,
-        joined: (user) => {
-          console.log(user);
-        },
-        left: (user) => {
-          console.log(user);
-        },
+      // 添加远程用户视频发布监听
+      this.client.on("stream-added", (event) => {
+        const remoteStream = event.stream;
+        if (remoteStream.userId_ !== "s01") return;
+
+        this.client
+          .subscribe(remoteStream, { audio: true, video: true })
+          .then(() => {
+            console.log("订阅视频成功!");
+          })
+          .catch((error) => {
+            console.log("订阅视频失败!", error);
+          });
       });
+      this.client.on("stream-subscribed", (event) => {
+        const remoteStream = event.stream;
+        remoteStream.play("communication-host", { objectFit: "contain" });
+      });
+
       // 加入房间
-      let joinRoomSuccess = true;
-      await this.room.join({ id: this.rongUserId }).catch((error) => {
-        console.log(error);
-        joinRoomSuccess = false;
+      let roomJoinResult = true;
+      await this.client
+        .join({
+          roomId: student.roomId,
+          role: "audience",
+          // role: "anchor"
+        })
+        .catch((error) => {
+          roomJoinResult = false;
+          console.log("加入房间失败!", error);
+        });
+      if (!roomJoinResult) return;
+
+      // 切换角色,连麦互动
+      let switchResult = true;
+      await this.client.switchRole("anchor").catch((error) => {
+        console.log("切换角色失败!", error);
+        switchResult = false;
       });
-      if (!joinRoomSuccess) return;
+      if (!switchResult) return;
 
       // 初始化stream
-      this.stream = new Stream({
-        published: (user) => {
-          console.log(user);
-          this.stream.subscribe(user).then((user) => {
-            let {
-              stream: { mediaStream },
-            } = user;
-            this.$refs.VideoHost.srcObject = mediaStream;
-            this.$refs.VideoHost.autoplay = true;
-            this.videoHostReady = true;
-          });
-        },
-        unpublished: (user) => {
-          this.stream.unsubscribe(user);
-        },
+      this.localStream = TRTC.createStream({
+        userId: this.videoUserId,
+        audio: true,
+        video: isVideo,
       });
 
-      // 按需发布本地音视频
-      this.curStreamType = isVideo
-        ? StreamType.AUDIO_AND_VIDEO
-        : StreamType.AUDIO;
-      const localStream = await this.stream.get().catch((error) => {
-        console.log(error);
-        this.$message.error("获取本地音视频失败!");
+      let initLocalStreamResult = true;
+      await this.localStream.initialize().catch((error) => {
+        console.log("初始化视频失败!", error);
+        initLocalStreamResult = false;
       });
-      if (!localStream) return;
-
-      const streamUser = {
-        id: this.rongUserId,
-        stream: {
-          tag: "RongCloudRTC",
-          type: this.curStreamType,
-          mediaStream: localStream.mediaStream,
-        },
-      };
-      const streamPublishResult = await this.stream
-        .publish(streamUser)
-        .catch((error) => {
-          console.log(error);
-        });
-      console.log(streamPublishResult);
-      // {configUrl, liveUrl}
-      if (isVideo && streamPublishResult) {
-        this.$refs.VideoGuest.autoplay = true;
-        this.$refs.VideoGuest.srcObject = localStream.mediaStream;
-        this.videoGuestReady = true;
-      }
+      if (!initLocalStreamResult) return;
+
+      // 发布本地视频
+      let publishStreamResult = true;
+      this.client.publish(this.localStream).catch((error) => {
+        console.log("发布本地视频失败!", error);
+        publishStreamResult = false;
+      });
+      if (!publishStreamResult) return;
+
+      // 播放本地视频
+      this.localStream.play("communication-guest", { muted: true });
     },
     async hangup() {
-      const streamUser = {
-        id: this.rongUserId,
-        stream: {
-          tag: "RongCloudRTC",
-          type: this.curStreamType,
-        },
-      };
-      await this.stream.unpublish(streamUser).catch((error) => {
-        console.log(error);
+      // 取消发布本地视频
+      let unpublishStreamResult = true;
+      this.client.unpublish(this.localStream).catch((error) => {
+        console.log("取消发布本地视频失败!", error);
+        unpublishStreamResult = false;
       });
-      await this.room.leave().catch((error) => {
-        console.log(error);
+      if (!unpublishStreamResult) return;
+
+      this.localStream.close();
+      this.localStream = null;
+
+      let result = true;
+      await this.client.leave().catch((error) => {
+        console.log("离开房间失败!", error);
+        result = false;
       });
+      if (!result) return;
 
+      this.client.off("*");
       this.dialogVisible = false;
-      this.initData();
-    },
-    initData() {
-      this.room = null;
-      this.stream = null;
-      this.streamUser = null;
-      this.curStreamType = null;
-      this.videoHostReady = false;
-      this.videoGuestReady = false;
     },
   },
   beforeDestroy() {
-    if (this.rongRTC) this.rongRTC.destroy();
+    if (this.setT) clearTimeout(this.setT);
+    if (this.client) {
+      this.client.leave();
+      this.client.off("*");
+    }
   },
 };
 </script>

+ 75 - 0
src/features/invigilation/WainingManage/FlvMedia.vue

@@ -0,0 +1,75 @@
+<template>
+  <video
+    class="flv-media"
+    :id="data.id"
+    ref="VideoMedia"
+    @ended="destroyPlayer"
+    muted
+  ></video>
+</template>
+
+<script>
+import flvjs from "flv.js";
+
+export default {
+  name: "flv-media",
+  props: {
+    data: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      flvPlayer: null,
+    };
+  },
+  mounted() {
+    this.initVideo();
+  },
+  methods: {
+    initVideo() {
+      if (!this.data.src) return;
+      if (!flvjs.isSupported()) return;
+
+      this.flvPlayer = flvjs.createPlayer(
+        {
+          type: "flv",
+          isLive: true,
+          hasAudio: false,
+          url: this.data.src,
+        },
+        {
+          fixAudioTimestampGap: false,
+        }
+      );
+      this.flvPlayer.attachMediaElement(this.$refs.VideoMedia);
+      this.flvPlayer.load();
+      this.flvPlayer.play();
+    },
+    playPlayer() {
+      this.flvPlayer.play();
+    },
+    pausePlayer() {
+      this.flvPlayer.pause();
+    },
+    destroyPlayer() {
+      if (!this.flvPlayer) return;
+      this.flvPlayer.pause();
+      this.flvPlayer.unload();
+      this.flvPlayer.detachMediaElement();
+      this.flvPlayer.destroy();
+      this.flvPlayer = null;
+    },
+    reloadVideo() {
+      this.destroyPlayer();
+      this.initVideo();
+    },
+  },
+  beforeDestroy() {
+    this.destroyPlayer();
+  },
+};
+</script>

+ 211 - 213
src/features/invigilation/WainingManage/WainingDetail.vue

@@ -4,6 +4,20 @@
       <div class="warning-detail-title">
         <h2>预警详情</h2>
         <el-button size="mini" icon="el-icon-arrow-left">返回列表</el-button>
+        <el-button
+          @click="initSubscribeVideo"
+          type="primary"
+          size="mini"
+          icon="el-icon-arrow-left"
+          >开始视频</el-button
+        >
+        <el-button
+          @click="closeSubscribeVideo"
+          type="danger"
+          size="mini"
+          icon="el-icon-arrow-left"
+          >关闭视频</el-button
+        >
       </div>
       <div class="warning-detail-student">
         <div class="student-head">
@@ -32,18 +46,22 @@
           </div>
           <div class="student-video">
             <div class="student-video-item">
-              <video ref="FirstViewVideo" v-if="firstViewVideoReady"></video>
+              <flv-media
+                ref="FirstViewVideo"
+                :data="firstViewVideo"
+                v-if="firstViewVideoReady"
+              ></flv-media>
               <div class="student-video-none" v-else>
                 <i class="el-icon-video-camera-solid"></i>
               </div>
             </div>
             <div class="student-video-item">
-              <video
-                src=""
-                ref="ThreeViewVideo"
-                v-show="threeViewVideoReady"
-              ></video>
-              <div class="student-video-none" v-show="!threeViewVideoReady">
+              <flv-media
+                ref="ThirdViewVideo"
+                :data="secondViewVideo"
+                v-if="secondViewVideoReady"
+              ></flv-media>
+              <div class="student-video-none" v-else>
                 <i class="el-icon-video-camera-solid"></i>
               </div>
             </div>
@@ -78,11 +96,22 @@
             <el-button type="primary" slot="reference">实时通话</el-button>
           </el-popover>
         </div>
-        <div class="warning-body-head-info">
-          <p><span>系统预警</span><span>1次</span></p>
-          <p><span>陌生人脸</span><span>1次</span></p>
-          <p><span>异常处理</span><span>1次</span></p>
-          <p><span></span><span>违纪状态:正常</span></p>
+        <div class="warning-body-head-info summary-line">
+          <p class="summary-line-item">
+            <i class="line-point line-point-danger"></i
+            ><span class="line-name">系统预警</span><span>1次</span>
+          </p>
+          <p class="summary-line-item">
+            <i class="line-point line-point-danger"></i
+            ><span class="line-name">陌生人脸</span><span>1次</span>
+          </p>
+          <p class="summary-line-item">
+            <i class="line-point line-point-danger"></i
+            ><span class="line-name">异常处理</span><span>1次</span>
+          </p>
+          <p class="summary-line-item">
+            <span></span><span>违纪状态:正常</span>
+          </p>
           <el-button type="danger" icon="icon icon-stop">违纪处理</el-button>
           <el-button type="warning" icon="icon icon-forbide"
             >强制收卷</el-button
@@ -103,13 +132,9 @@
       append-to-body
       fullscreen
     >
-      <div class="communication-box">
-        <div class="communication-host">
-          <video src="" ref="VideoHost" v-show="videoHostReady"></video>
-        </div>
-        <div class="communication-guest">
-          <video src="" ref="VideoGuest" v-show="videoGuestReady"></video>
-        </div>
+      <div class="communication-box" v-show="!isWaiting">
+        <div class="communication-host" id="communication-host"></div>
+        <div class="communication-guest" id="communication-guest"></div>
         <div class="communication-action">
           <el-button round type="danger" @click="hangup">结束通话</el-button>
         </div>
@@ -118,18 +143,32 @@
           <span>持续时长:{{ durationTime }}</span>
         </div>
       </div>
+      <div class="communication-wait" v-show="isWaiting">
+        <p class="communication-wait-tips">等待接听…</p>
+        <div class="communication-wait-avatar">
+          <img :src="curStudent.stdAvatar" :alt="curStudent.stdName" />
+        </div>
+        <p class="communication-wait-username">{{ curStudent.stdName }}</p>
+        <div class="communication-wait-action">
+          <el-button round type="danger" @click="hangup">取消通话</el-button>
+        </div>
+      </div>
       <span slot="footer" class="dialog-footer"> </span>
     </el-dialog>
   </div>
 </template>
 
 <script>
-import { initRongRTC, connectIM } from "@/plugins/RongIM";
+import TRTC from "trtc-js-sdk";
+import FlvMedia from "./FlvMedia";
+import { warningStudentDetail } from "@/api/invigilation";
 
 export default {
   name: "warning-detail",
+  components: { FlvMedia },
   data() {
     return {
+      recordId: "777",
       exceptionList: [
         {
           title: "身份验证不通过",
@@ -151,208 +190,179 @@ export default {
         stdName: "张龙龙",
         subjectName: "大学英语",
         subjectCode: "10006",
-        liveUrlFirst:
-          "F7XLyBeoubx0mK+pY9TmqnPWqaI5x6SmcN2+qjnWpKUtjfvIBIT8+jmH8+Ygg+X6Jobx/CeF8/gWtcvIFbWHiVbWs4VT1ryFQ9b4iVTvspEmjL+RJo34kX3Ro4ZD+6eFffj+hlP0vJFD8LGReuT5kXrg+ZFt3rORbeemhnr4+5JG9I6FffSyhVb09sgix6iXetaU/3WCqv0k0Pn7LoH7+HaE+Kpzg6mtIdby+XSBrf50ga6XRdqlr1TZpL1z55+LSITLyBe1ZHVxu8v9ZdaUpXTq/Kog1P77cof48SOF+6kmhqmsIdeu/nSM+qsj0/2rI9CUmnjbrIt72r6sReGIlye1y8gXfsxCoA==",
-        liveUrlThree: "",
         roomId: "8888",
       },
+      firstViewVideo: {
+        id: "111",
+        src: "http://live.qmth.com.cn/live/8888_mobile_first.flv",
+        name: "第一视角",
+      },
+      secondViewVideo: {
+        id: "222",
+        src: "http://live.qmth.com.cn/live/8888_mobile_second.flv",
+        name: "第二视角",
+      },
       firstViewVideoReady: false,
-      threeViewVideoReady: false,
+      secondViewVideoReady: false,
       // communication
       durationTime: "00:10:23",
-      token:
-        "R2wA227fM5guGJjIUQiOFy7ePbpkBY7e/88fWPXjr+k=@tlem.cn.rongnav.com;tlem.cn.rongcfg.com",
-      // token:
-      //   "iy7OAXkkr+ooBirzYIT8BC7ePbpkBY7eRygkv+h3Iok=@tlem.cn.rongnav.com;tlem.cn.rongcfg.com",
-      rongUserId: null,
-      rongRTC: null,
-      room: null,
-      stream: null,
-      streamUser: null,
-      curStreamType: null,
-      videoHostReady: false,
-      videoGuestReady: false,
+      appId: "1400411036",
+      videoUserSig:
+        "eJyrVgrxCdYrSy1SslIy0jNQ0gHzM1NS80oy0zLBwsUGZlDh4pTsxIKCzBQlK0MTAwMTQ0MDYzOITGpFQWZRKlDc1NTUyMDAACJakpkLFrM0NzI0NLKEqi3OTAeaWmoeWZAd6O1h7B4YbhJoXOYdkeNmEebm5Z1R5JxXEmVhbOZkYWFQkZvjV26rVAsAYQkvlQ__",
+      videoUserId: "s06",
+      client: null,
+      localStream: null,
       dialogVisible: false,
+      isWaiting: true,
+      setT: null,
     };
   },
   mounted() {
-    this.initRong();
+    // this.initClinet();
+    // this.initData();
   },
   methods: {
-    async initRong() {
-      this.rongUserId = await connectIM(this.token).catch((error) => {
-        console.log(error);
+    initClinet() {
+      this.client = TRTC.createClient({
+        mode: "rtc",
+        sdkAppId: this.appId,
+        userId: this.videoUserId,
+        userSig: this.videoUserSig,
       });
-      if (!this.rongUserId) {
-        this.$message.error("初始化错误!");
-        return;
-      }
-
-      this.initVideo();
     },
-    async initVideo() {
-      const { Mode, ROLE } = window.RongRTC;
-      this.rongRTC = initRongRTC({
-        debug: false,
-        mode: Mode.LIVE,
-        liveRole: ROLE.AUDIENCE,
+    async initData() {
+      const data = await warningStudentDetail({ recordId: this.recordId });
+      const records = data.records.map((item) => {
+        item.src = `http://live.qmth.com.cn/live/${item.liveUrl}.flv`;
+        item.name = item.source;
+        return item;
       });
-      const { Stream } = this.rongRTC;
-
-      // 初始化stream
-      this.stream = new Stream({
-        published: (user) => {
-          console.log(user);
-        },
-        unpublished: (user) => {
-          console.log(user);
-        },
-      });
-
-      const { mediaStream } = await this.stream
-        .subscribe({ liveUrl: this.curStudent.liveUrlFirst })
-        .catch((error) => {
-          console.log(error);
-        });
-      console.log(mediaStream);
+      this.firstViewVideo = records[0] || {};
+      this.secondViewVideo = records[1] || {};
 
-      if (mediaStream) {
-        this.firstViewVideoReady = true;
-        this.$nextTick(() => {
-          this.$refs.FirstViewVideo.srcObject = mediaStream;
-          this.$refs.FirstViewVideo.autoplay = true;
-        });
-      }
-
-      // this.stream.subscribe({ liveUrl: this.curStudent.liveUrlThree }).then((user) => {
-      //   let {
-      //     stream: { mediaStream },
-      //   } = user;
-      //   this.$refs.ThreeViewVideo.srcObject = mediaStream;
-      //   this.$refs.ThreeViewVideo.autoplay = true;
-      //   this.threeViewVideoReady = true;
-      // });
+      if (records.length) this.initSubscribeVideo();
+    },
+    initSubscribeVideo() {
+      if (this.firstViewVideo.id) this.firstViewVideoReady = true;
+      if (this.secondViewVideo.id) this.secondViewVideoReady = true;
     },
-    closeVideo() {
-      this.stream.unsubscribe({ liveUrl: this.curStudent.liveUrlFirst });
-      this.stream.unsubscribe({ liveUrl: this.curStudent.liveUrlThree });
-      this.stream = null;
-      this.rongRTC.destroy();
-      this.rongRTC = null;
+    closeSubscribeVideo() {
       this.firstViewVideoReady = false;
-      this.threeViewVideoReady = false;
+      this.secondViewVideoReady = false;
     },
     async answer(isVideo) {
-      this.closeVideo();
+      this.closeSubscribeVideo();
       this.dialogVisible = true;
 
-      const { Mode, ROLE } = window.RongRTC;
-      this.rongRTC = initRongRTC({
-        debug: false,
-        mode: Mode.LIVE,
-        liveRole: ROLE.ANCHOR,
+      // 添加远程用户视频发布监听
+      this.client.on("stream-added", (event) => {
+        console.log(event);
+        const remoteStream = event.stream;
+        if (remoteStream.userId_ !== "s01") return;
+
+        // 延迟订阅视频
+        this.setT = setTimeout(() => {
+          this.client
+            .subscribe(remoteStream, { audio: true, video: true })
+            .then(() => {
+              console.log("订阅视频成功!");
+            })
+            .catch((error) => {
+              console.log("订阅视频失败!", error);
+            });
+        }, 5000);
       });
+      this.client.on("stream-subscribed", (event) => {
+        const remoteStream = event.stream;
+        if (remoteStream.userId_ !== "s01") return;
 
-      const { Room, Stream, StreamType } = this.rongRTC;
-      // 初始化房间
-      this.room = new Room({
-        id: this.curStudent.roomId,
-        joined: (user) => {
-          console.log(user);
-        },
-        left: (user) => {
-          console.log(user);
-        },
+        this.isWaiting = false;
+        this.$nextTick(() => {
+          remoteStream.play("communication-host", { objectFit: "contain" });
+        });
       });
+
       // 加入房间
-      let joinRoomSuccess = true;
-      await this.room.join({ id: this.rongUserId }).catch((error) => {
-        console.log(error);
-        joinRoomSuccess = false;
+      let roomJoinResult = true;
+      await this.client
+        .join({
+          roomId: this.curStudent.roomId,
+          role: "audience",
+        })
+        .catch((error) => {
+          roomJoinResult = false;
+          console.log("加入房间失败!", error);
+        });
+      if (!roomJoinResult) return;
+
+      // 切换角色,连麦互动
+      let switchResult = true;
+      await this.client.switchRole("anchor").catch((error) => {
+        console.log("切换角色失败!", error);
+        switchResult = false;
       });
-      if (!joinRoomSuccess) return;
+      if (!switchResult) return;
 
       // 初始化stream
-      this.stream = new Stream({
-        published: (user) => {
-          console.log(user);
-          this.stream.subscribe(user).then((user) => {
-            let {
-              stream: { mediaStream },
-            } = user;
-            this.$refs.VideoHost.srcObject = mediaStream;
-            this.$refs.VideoHost.autoplay = true;
-            this.videoHostReady = true;
-          });
-        },
-        unpublished: (user) => {
-          this.stream.unsubscribe(user);
-        },
+      this.localStream = TRTC.createStream({
+        userId: this.videoUserId,
+        audio: true,
+        video: isVideo,
       });
 
-      // 按需发布本地音视频
-      this.curStreamType = isVideo
-        ? StreamType.AUDIO_AND_VIDEO
-        : StreamType.AUDIO;
-      const localStream = await this.stream.get().catch((error) => {
-        console.log(error);
-        this.$message.error("获取本地音视频失败!");
+      let initLocalStreamResult = true;
+      await this.localStream.initialize().catch((error) => {
+        console.log("初始化视频失败!", error);
+        initLocalStreamResult = false;
       });
-      if (!localStream) return;
+      if (!initLocalStreamResult) return;
 
-      const streamUser = {
-        id: this.rongUserId,
-        stream: {
-          tag: "RongCloudRTC",
-          type: this.curStreamType,
-          mediaStream: localStream.mediaStream,
-        },
-      };
-      const streamPublishResult = await this.stream
-        .publish(streamUser)
-        .catch((error) => {
-          console.log(error);
-        });
-      console.log(streamPublishResult);
-      // {configUrl, liveUrl}
-      if (isVideo && streamPublishResult) {
-        this.$refs.VideoGuest.autoplay = true;
-        this.$refs.VideoGuest.srcObject = localStream.mediaStream;
-        this.videoGuestReady = true;
-      }
+      // 发布本地视频
+      let publishStreamResult = true;
+      this.client.publish(this.localStream).catch((error) => {
+        console.log("发布本地视频失败!", error);
+        publishStreamResult = false;
+      });
+      if (!publishStreamResult) return;
+
+      // 播放本地视频
+      this.localStream.play("communication-guest", { muted: true });
     },
     async hangup() {
-      const streamUser = {
-        id: this.rongUserId,
-        stream: {
-          tag: "RongCloudRTC",
-          type: this.curStreamType,
-        },
-      };
-      await this.stream.unpublish(streamUser).catch((error) => {
-        console.log(error);
+      if (this.setT) clearTimeout(this.setT);
+
+      // 取消发布本地视频
+      let unpublishStreamResult = true;
+      this.client.unpublish(this.localStream).catch((error) => {
+        console.log("取消发布本地视频失败!", error);
+        unpublishStreamResult = false;
       });
-      await this.room.leave().catch((error) => {
-        console.log(error);
+      if (!unpublishStreamResult) return;
+
+      this.localStream.close();
+      this.localStream = null;
+
+      // 离开房间
+      let result = true;
+      await this.client.leave().catch((error) => {
+        console.log("离开房间失败!", error);
+        result = false;
       });
+      if (!result) return;
 
+      this.client.off("*");
       this.dialogVisible = false;
-      this.initData();
-    },
-    initData() {
-      this.room = null;
-      this.stream = null;
-      this.streamUser = null;
-      this.curStreamType = null;
-      this.videoHostReady = false;
-      this.videoGuestReady = false;
-      this.rongRTC.destroy();
-      this.rongRTC = null;
-      this.initVideo();
+      this.localStream = null;
+      this.isWaiting = true;
+      // this.initSubscribeVideo();
     },
   },
   beforeDestroy() {
-    if (this.rongRTC) this.rongRTC.destroy();
+    if (this.setT) clearTimeout(this.setT);
+    if (this.client) {
+      this.client.leave();
+      this.client.off("*");
+    }
   },
 };
 </script>
@@ -433,22 +443,34 @@ export default {
   margin-left: 210px;
   height: 240px;
   font-size: 0;
+  position: relative;
 
   &-item {
+    position: relative;
     display: inline-block;
     vertical-align: top;
     font-size: 14px;
     padding: 0 15px;
     width: 50%;
     height: 100%;
-    > video {
-      display: block;
-      width: 100%;
-      height: 100%;
-      border-radius: 6px;
-      box-shadow: 0 0 1px #333;
-      background: #606060;
-    }
+  }
+  video {
+    display: block;
+    width: 100%;
+    height: 100%;
+    border-radius: 6px;
+    box-shadow: 0 0 1px #333;
+    background: #606060;
+  }
+
+  &-main {
+    position: absolute;
+    top: 0;
+    left: 15px;
+    right: 15px;
+    bottom: 0;
+    z-index: 9;
+    border-radius: 6px;
   }
 
   &-none {
@@ -563,33 +585,9 @@ export default {
   &-info {
     float: right;
     > p {
-      margin: 0;
       height: 32px;
       line-height: 32px;
-      display: inline-block;
-      vertical-align: middle;
       margin-right: 20px;
-      &::before {
-        content: "";
-        display: inline-block;
-        vertical-align: middle;
-        width: 8px;
-        height: 8px;
-        border: 2px solid #fe5863;
-        margin-right: 6px;
-        margin-top: -3px;
-        border-radius: 50%;
-      }
-
-      > span {
-        color: #626a82;
-
-        &:last-child {
-          color: #202b4b;
-          font-weight: 600;
-          margin-left: 8px;
-        }
-      }
     }
   }
 }

+ 122 - 121
src/features/invigilation/WainingManage/WainingManage.vue

@@ -6,128 +6,129 @@
         <span>考场名称:哈尔滨工业大学第十一考场</span>
       </div>
     </div>
-    <div class="part-box part-box-filter">
-      <el-form
-        class="part-box-filter-form"
-        ref="FilterForm"
-        label-position="left"
-        inline
-      >
-        <el-form-item>
-          <el-select
-            v-model="filter.batchId"
-            placeholder="请选择批次"
-            clearable
-          >
-            <el-option
-              v-for="item in batchs"
-              :key="item.id"
-              :value="item.id"
-              :label="item.name"
-            ></el-option>
-          </el-select>
-        </el-form-item>
-        <el-form-item>
-          <el-select v-model="filter.examId" placeholder="请选择考场" clearable>
-            <el-option
-              v-for="item in exams"
-              :key="item.id"
-              :value="item.id"
-              :label="item.name"
-            ></el-option>
-          </el-select>
-        </el-form-item>
-        <el-form-item>
-          <el-select
-            v-model="filter.subjectId"
-            placeholder="请选择科目"
-            clearable
+    <div class="part-filter">
+      <div class="part-filter-form">
+        <el-form ref="FilterForm" label-position="left" inline>
+          <el-form-item>
+            <el-select
+              v-model="filter.batchId"
+              placeholder="请选择批次"
+              clearable
+            >
+              <el-option
+                v-for="item in batchs"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.examroom"
+              placeholder="请选择考场"
+              clearable
+            >
+              <el-option
+                v-for="item in exams"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.subjectId"
+              placeholder="请选择科目"
+              clearable
+            >
+              <el-option
+                v-for="item in subjects"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="filter.auditStatus"
+              placeholder="审阅状态"
+              clearable
+            >
+              <el-option
+                v-for="item in subjects"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-input
+              v-model.trim="filter.content"
+              placeholder="姓名/证件号"
+              clearable
+            ></el-input>
+          </el-form-item>
+          <el-form-item label="陌生人脸">
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.strangePersonDown"
+              placeholder="下限"
+              :controls="false"
+            ></el-input-number>
+            <span>-</span>
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.strangePersonUp"
+              placeholder="上限"
+              :controls="false"
+            ></el-input-number>
+          </el-form-item>
+          <el-form-item label="异常处理">
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.exceptionDown"
+              placeholder="下限"
+              :controls="false"
+            ></el-input-number>
+            <span>-</span>
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.exceptionUp"
+              placeholder="上限"
+              :controls="false"
+            ></el-input-number>
+          </el-form-item>
+          <el-form-item label="预警数">
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.warningDown"
+              placeholder="下限"
+              :controls="false"
+            ></el-input-number>
+            <span>-</span>
+            <el-input-number
+              style="width: 52px;"
+              v-model.trim="filter.warningUp"
+              placeholder="上限"
+              :controls="false"
+            ></el-input-number>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="toPage(1)">查询</el-button>
+          </el-form-item>
+        </el-form>
+        <div class="part-filter-form-action">
+          <el-button type="primary" icon="icon icon-clean" @click="cleanUnread"
+            >清除未阅</el-button
           >
-            <el-option
-              v-for="item in subjects"
-              :key="item.id"
-              :value="item.id"
-              :label="item.name"
-            ></el-option>
-          </el-select>
-        </el-form-item>
-        <el-form-item>
-          <el-select
-            v-model="filter.auditStatus"
-            placeholder="审阅状态"
-            clearable
+          <el-button type="danger" icon="icon icon-warning" @click="batchAction"
+            >批量处理违纪</el-button
           >
-            <el-option
-              v-for="item in subjects"
-              :key="item.id"
-              :value="item.id"
-              :label="item.name"
-            ></el-option>
-          </el-select>
-        </el-form-item>
-        <el-form-item>
-          <el-input
-            v-model.trim="filter.content"
-            placeholder="姓名/证件号"
-            clearable
-          ></el-input>
-        </el-form-item>
-        <el-form-item label="陌生人脸">
-          <el-input-number
-            style="width: 52px;"
-            v-model.trim="filter.strangePersonDown"
-            placeholder="下限"
-            :controls="false"
-          ></el-input-number>
-          <span>-</span>
-          <el-input-number
-            style="width: 52px;"
-            v-model.trim="filter.strangePersonUp"
-            placeholder="上限"
-            :controls="false"
-          ></el-input-number>
-        </el-form-item>
-        <el-form-item label="异常处理">
-          <el-input-number
-            style="width: 52px;"
-            v-model.trim="filter.exceptionDown"
-            placeholder="下限"
-            :controls="false"
-          ></el-input-number>
-          <span>-</span>
-          <el-input-number
-            style="width: 52px;"
-            v-model.trim="filter.exceptionUp"
-            placeholder="上限"
-            :controls="false"
-          ></el-input-number>
-        </el-form-item>
-        <el-form-item label="预警数">
-          <el-input-number
-            style="width: 52px;"
-            v-model.trim="filter.warningDown"
-            placeholder="下限"
-            :controls="false"
-          ></el-input-number>
-          <span>-</span>
-          <el-input-number
-            style="width: 52px;"
-            v-model.trim="filter.warningUp"
-            placeholder="上限"
-            :controls="false"
-          ></el-input-number>
-        </el-form-item>
-        <el-form-item>
-          <el-button type="primary" @click="toPage(1)">查询</el-button>
-        </el-form-item>
-      </el-form>
-      <div class="part-box-filter-action">
-        <el-button type="primary" icon="icon icon-clean" @click="cleanUnread"
-          >清除未阅</el-button
-        >
-        <el-button type="danger" icon="icon icon-warning" @click="batchAction"
-          >批量处理违纪</el-button
-        >
+        </div>
       </div>
     </div>
 
@@ -181,7 +182,7 @@ export default {
     return {
       filter: {
         batchId: null,
-        examId: null,
+        examroom: null,
         subjectId: null,
         auditStatus: null,
         content: "",

+ 0 - 72
src/plugins/RongIM.js

@@ -1,72 +0,0 @@
-window.RongIMLib.RongIMClient.init("3argexb63s9ie");
-// 连接状态监听器
-window.RongIMClient.setConnectionStatusListener({
-  onChanged: function (status) {
-    console.log("status changed", status);
-    switch (status) {
-      case window.RongIMLib.ConnectionStatus.CONNECTING:
-        break;
-      case window.RongIMLib.ConnectionStatus.DISCONNECTED:
-        break;
-      case window.RongIMLib.ConnectionStatus.KICKED_OFFLINE_BY_OTHER_CLIENT:
-      case window.RongIMLib.ConnectionStatus.DOMAIN_INCORRECT:
-      case window.RongIMLib.ConnectionStatus.NETWORK_UNAVAILABLE:
-        reconnect();
-        // callbacks.error(status);
-        break;
-    }
-  },
-});
-
-// 消息监听器
-window.RongIMClient.setOnReceiveMessageListener({
-  onReceived: function (message) {
-    console.log(message);
-  },
-});
-
-function reconnect() {
-  var callback = {
-    onSuccess: function (userId) {
-      console.log("reconnect success. " + userId);
-    },
-    onTokenIncorrect: function () {
-      console.log("token 无效");
-    },
-    onError: function () {
-      reconnect();
-    },
-  };
-  var config = {
-    auto: true,
-    url: "cdn.ronghub.com/RongIMLib-2.2.6.min.js?d=" + Date.now(),
-    rate: [100, 1000, 3000, 6000, 10000],
-  };
-  window.RongIMClient.reconnect(callback, config);
-}
-
-export const initRongRTC = (options) => {
-  return new window.RongRTC({
-    RongIMLib: window.RongIMLib,
-    ...options,
-  });
-};
-
-export const connectIM = (token) => {
-  return new Promise((resolve, reject) => {
-    window.RongIMClient.connect(token, {
-      onSuccess: (userId) => {
-        console.log("connect success, userId:" + userId);
-        resolve(userId);
-      },
-      onTokenIncorrect: () => {
-        reject({ type: "token", msg: "token unvalid" });
-        console.log("token unvalid");
-      },
-      onError: (errorCode) => {
-        console.log(errorCode);
-        reject({ type: "error", msg: errorCode });
-      },
-    });
-  });
-};

+ 3 - 0
src/router/invigilation.js

@@ -42,6 +42,9 @@ const routes = [
     path: "/invigilation/waining-detail/:id",
     name: "WainingDetail",
     component: WainingDetail,
+    meta: {
+      relate: "WainingManage",
+    },
   },
   {
     path: "/invigilation/reexam-apply",

+ 135 - 9
src/styles/base.scss

@@ -21,17 +21,49 @@
       > h1 {
         color: #202b4b;
         font-size: 18px;
+        font-weight: 600;
+        margin: 0;
       }
     }
     &-right {
       float: right;
     }
   }
-
+}
+.part {
   &-filter {
-    padding: 20px 20px 10px;
-    display: flex;
+    margin-bottom: 20px;
+    background-color: #fff;
+    border-radius: 10px;
+
+    div:nth-of-type(2) {
+      border-top: 1px solid #e9e9e9;
+    }
+
+    &-info {
+      display: flex;
+      align-items: stretch;
+      justify-content: space-between;
+      padding: 10px 20px;
+
+      &-sub {
+        padding-left: 20px;
+      }
+    }
+
     &-form {
+      display: flex;
+      align-items: stretch;
+      justify-content: space-between;
+      padding: 20px 20px 10px;
+
+      &-action {
+        padding-bottom: 10px;
+        white-space: nowrap;
+        display: flex;
+        align-items: flex-end;
+      }
+
       .el-form-item {
         margin-bottom: 10px;
       }
@@ -39,14 +71,9 @@
         margin-bottom: 0;
       }
     }
-    &-action {
-      padding-bottom: 10px;
-      white-space: nowrap;
-      display: flex;
-      align-items: flex-end;
-    }
   }
 }
+
 .part-page {
   margin-top: 20px;
   text-align: right;
@@ -61,6 +88,19 @@
   }
 }
 
+// :fullscreen
+.app-fullscreen {
+  .nav-bar,
+  .side-bar,
+  .app-footer {
+    display: none;
+  }
+  .main-container {
+    margin: 0;
+    padding: 0;
+  }
+}
+
 // communication-dialog
 .communication-dialog {
   background-color: transparent;
@@ -72,6 +112,7 @@
     margin: 0 auto;
     position: relative;
     border-radius: 6px;
+    overflow: hidden;
     video {
       display: block;
       width: 100%;
@@ -119,6 +160,44 @@
       }
     }
   }
+
+  .communication-wait {
+    width: 460px;
+    height: 511px;
+    background: #fff;
+    margin: 100px auto 0;
+    position: relative;
+    border-radius: 6px;
+    text-align: center;
+    padding: 40px;
+
+    &-tips {
+      font-size: 16px;
+      font-weight: 600;
+      color: rgba(28, 209, 161, 1);
+      line-height: 22px;
+    }
+
+    &-avatar {
+      width: 180px;
+      height: 180px;
+      border-radius: 50%;
+      overflow: hidden;
+      margin: 20px auto 12px;
+      > img {
+        display: block;
+        width: 100%;
+        height: auto;
+      }
+    }
+    &-username {
+      font-size: 18px;
+      font-weight: 600;
+      color: rgba(32, 43, 75, 1);
+      line-height: 25px;
+      margin-bottom: 100px;
+    }
+  }
   // element
   .el-dialog__footer,
   .el-dialog__header {
@@ -126,6 +205,53 @@
   }
 }
 
+// summary-line
+.summary-line {
+  font-size: 0;
+  padding: 6px 10px 6px 0;
+  &-item {
+    font-size: 14px;
+    display: inline-block;
+    vertical-align: middle;
+    margin: 0 36px 0 0;
+    line-height: 20px;
+    color: #202b4b;
+
+    > .icon {
+      margin-right: 8px;
+      margin-top: -2px;
+    }
+
+    span.line-name {
+      color: #626a82;
+      margin-right: 8px;
+    }
+
+    i.line-point {
+      display: inline-block;
+      vertical-align: middle;
+      width: 8px;
+      height: 8px;
+      border: 2px solid #202b4b;
+      margin-right: 6px;
+      margin-top: -3px;
+      border-radius: 50%;
+    }
+
+    i.line-point-primary {
+      border-color: #1886fe;
+    }
+    i.line-point-info {
+      border-color: #5fcafc;
+    }
+    i.line-point-success {
+      border-color: #1cd1a1;
+    }
+    i.line-point-danger {
+      border-color: #fe5863;
+    }
+  }
+}
 // element-custom ------------->
 // el-button
 .el-button {

+ 16 - 0
src/styles/icons.scss

@@ -41,6 +41,9 @@
   &-clean {
     background-image: url(../assets/icon-clean.png);
   }
+  &-scan {
+    background-image: url(../assets/icon-scan.png);
+  }
   &-stop {
     background-image: url(../assets/icon-stop.png);
   }
@@ -66,7 +69,20 @@
   &-analysis {
     background-image: url(../assets/icon-analysis.png);
   }
+  &-rate {
+    background-image: url(../assets/icon-rate.png);
+  }
+  &-users {
+    height: 13px;
+    background-image: url(../assets/icon-users.png);
+  }
   &-exam-detail {
     background-image: url(../assets/icon-exam-detail.png);
   }
+  &-net-break {
+    background-image: url(../assets/icon-net-break.png);
+  }
+  &-full-screen {
+    background-image: url(../assets/icon-full-screen.png);
+  }
 }

+ 69 - 3
src/views/Layout/components/NavBar.vue

@@ -35,8 +35,14 @@
             <span v-if="username">{{ username }},欢迎你</span>
           </div>
         </li>
-        <li @click="toLogout">
-          <div class="menu-item">
+        <li>
+          <div class="menu-item" @click="toFullscreen">
+            <i class="icon icon-full-screen"></i>
+            <span>全屏</span>
+          </div>
+        </li>
+        <li>
+          <div class="menu-item" @click="toLogout">
             <i class="icon icon-logout"></i>
             <span>退出登录</span>
           </div>
@@ -70,10 +76,11 @@ export default {
         business: businessMenuConfig,
         invigilation: invigilationMenuConfig,
       },
+      isFullscreen: false,
     };
   },
   mounted() {
-    const curRouterName = this.$route.name;
+    const curRouterName = this.$route.meta.relate || this.$route.name;
     Object.keys(this.modaleNavs).forEach((mkey) => {
       if (this.curNav) return;
       let curRouter = null;
@@ -85,6 +92,7 @@ export default {
 
       if (curRouter) this.toPage({ name: mkey });
     });
+    this.registFullscreenChange();
   },
   methods: {
     toPage(nav) {
@@ -95,6 +103,64 @@ export default {
       // console.log('Logout');
       this.$router.push("/login");
     },
+    checkDocIsFullscreen() {
+      return (
+        document.fullscreenElement ||
+        document.msFullscreenElement ||
+        document.mozFullscreenElement ||
+        document.webkitFullscreenElement
+      );
+    },
+    registFullscreenChange() {
+      document.documentElement.onfullscreenchange = () => {
+        if (this.checkDocIsFullscreen()) {
+          this.isFullscreen = true;
+          document.body.className += "app-fullscreen";
+        } else {
+          this.isFullscreen = false;
+          document.body.className = document.body.className.replace(
+            "app-fullscreen",
+            ""
+          );
+        }
+      };
+
+      document.addEventListener("keydown", (e) => {
+        if (
+          e.keyCode === 122 ||
+          (e.keyCode === 27 && this.checkDocIsFullscreen())
+        ) {
+          e.preventDefault();
+          this.toFullscreen();
+        }
+      });
+    },
+    async toFullscreen() {
+      const fullscreenEnabled =
+        document.fullscreenEnabled ||
+        document.mozFullScreenEnabled ||
+        document.webkitFullscreenEnabled ||
+        document.msFullscreenEnabled;
+      if (!fullscreenEnabled) {
+        this.$message.error("当前浏览器不支持全屏!");
+        return;
+      }
+      const de = document.documentElement;
+      const requestFullscreen =
+        de.requestFullscreen ||
+        de.mozRequestFullScreen ||
+        de.webkitRequestFullscreen;
+      const exitFullscreen =
+        document.exitFullscreen ||
+        document.mozCancelFullScreen ||
+        document.webkitCancelFullScreen;
+
+      if (this.isFullscreen) {
+        await exitFullscreen.call(document).catch(() => {});
+      } else {
+        await requestFullscreen.call(de).catch(() => {});
+      }
+    },
   },
 };
 </script>

+ 1 - 1
src/views/Layout/components/SideBar.vue

@@ -43,7 +43,7 @@ export default {
   },
   data() {
     return {
-      curRouterName: this.$route.name,
+      curRouterName: this.$route.meta.relate || this.$route.name,
     };
   },
   methods: {

+ 23 - 0
yarn.lock

@@ -4522,6 +4522,11 @@ es-to-primitive@^1.2.0:
     is-date-object "^1.0.1"
     is-symbol "^1.0.2"
 
+es6-promise@^4.2.5:
+  version "4.2.8"
+  resolved "https://registry.npm.taobao.org/es6-promise/download/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
+  integrity sha1-TrIVlMlyvEBVPSduUQU5FD21Pgo=
+
 escape-html@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
@@ -5181,6 +5186,14 @@ flush-write-stream@^1.0.0:
     inherits "^2.0.3"
     readable-stream "^2.3.6"
 
+flv.js@^1.5.0:
+  version "1.5.0"
+  resolved "https://registry.npm.taobao.org/flv.js/download/flv.js-1.5.0.tgz#fa59bed4391d70435cfa8740ac40ec0070ef98ae"
+  integrity sha1-+lm+1DkdcENc+odArEDsAHDvmK4=
+  dependencies:
+    es6-promise "^4.2.5"
+    webworkify "^1.5.0"
+
 follow-redirects@1.5.10:
   version "1.5.10"
   resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
@@ -10700,6 +10713,11 @@ trim-right@^1.0.1:
   resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
   integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=
 
+trtc-js-sdk@^4.6.1:
+  version "4.6.1"
+  resolved "https://registry.npm.taobao.org/trtc-js-sdk/download/trtc-js-sdk-4.6.1.tgz?cache=0&sync_timestamp=1597029484476&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftrtc-js-sdk%2Fdownload%2Ftrtc-js-sdk-4.6.1.tgz#7792522eacdfebbcb169f2943ccf877f6bdb28bb"
+  integrity sha1-d5JSLqzf67yxafKUPM+Hf2vbKLs=
+
 tryer@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
@@ -11350,6 +11368,11 @@ websocket-extensions@>=0.1.1:
   resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29"
   integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==
 
+webworkify@^1.5.0:
+  version "1.5.0"
+  resolved "https://registry.npm.taobao.org/webworkify/download/webworkify-1.5.0.tgz#734ad87a774de6ebdd546e1d3e027da5b8f4a42c"
+  integrity sha1-c0rYendN5uvdVG4dPgJ9pbj0pCw=
+
 whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"