|
@@ -1,12 +1,11 @@
|
|
<template>
|
|
<template>
|
|
<div class="warning-detail">
|
|
<div class="warning-detail">
|
|
<div class="warning-detail-head">
|
|
<div class="warning-detail-head">
|
|
- <div class="warning-detail-title">
|
|
|
|
- <h2>预警详情</h2>
|
|
|
|
- <el-button size="mini" icon="el-icon-arrow-left" @click="goBack"
|
|
|
|
- >返回列表</el-button
|
|
|
|
- >
|
|
|
|
- <!-- <el-button
|
|
|
|
|
|
+ <h2>预警详情</h2>
|
|
|
|
+ <el-button size="mini" icon="el-icon-arrow-left" @click="goBack"
|
|
|
|
+ >返回列表</el-button
|
|
|
|
+ >
|
|
|
|
+ <!-- <el-button
|
|
@click="initSubscribeVideo"
|
|
@click="initSubscribeVideo"
|
|
type="primary"
|
|
type="primary"
|
|
size="mini"
|
|
size="mini"
|
|
@@ -20,44 +19,44 @@
|
|
icon="el-icon-arrow-left"
|
|
icon="el-icon-arrow-left"
|
|
>关闭视频</el-button
|
|
>关闭视频</el-button
|
|
> -->
|
|
> -->
|
|
- </div>
|
|
|
|
- <div class="warning-detail-student">
|
|
|
|
- <div class="student-head">
|
|
|
|
- <div class="student-head-left">
|
|
|
|
- <p><i class="icon icon-user-act"></i></p>
|
|
|
|
- <p>
|
|
|
|
- <span>姓名:</span><span>{{ detailInfo.examStudentName }}</span>
|
|
|
|
- </p>
|
|
|
|
- <p>
|
|
|
|
- <span>证件号:</span><span>{{ detailInfo.identity }}</span>
|
|
|
|
- </p>
|
|
|
|
- <p>
|
|
|
|
- <span>科目(代码):</span
|
|
|
|
- ><span>{{ detailInfo.courseNameCode }}</span>
|
|
|
|
- </p>
|
|
|
|
- </div>
|
|
|
|
- <div class="student-head-right">
|
|
|
|
- <el-button
|
|
|
|
- class="el-icon-btn"
|
|
|
|
- size="mini"
|
|
|
|
- type="primary"
|
|
|
|
- icon="el-icon-arrow-left"
|
|
|
|
- title="查看上一个"
|
|
|
|
- @click="changeStudent(0)"
|
|
|
|
- :disabled="holding"
|
|
|
|
- ></el-button>
|
|
|
|
- <el-button
|
|
|
|
- class="el-icon-btn"
|
|
|
|
- size="mini"
|
|
|
|
- type="primary"
|
|
|
|
- icon="el-icon-arrow-right"
|
|
|
|
- title="查看下一个"
|
|
|
|
- @click="changeStudent(1)"
|
|
|
|
- :disabled="holding"
|
|
|
|
- ></el-button>
|
|
|
|
- </div>
|
|
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div class="warning-detail-body">
|
|
|
|
+ <div class="detail-body-head">
|
|
|
|
+ <div class="detail-body-head-left">
|
|
|
|
+ <p>
|
|
|
|
+ <i class="icon icon-person"></i>
|
|
|
|
+ <span>{{ detailInfo.examStudentName }}</span>
|
|
|
|
+ </p>
|
|
|
|
+ <p>
|
|
|
|
+ <span>证件号:</span><span>{{ detailInfo.identity }}</span>
|
|
|
|
+ </p>
|
|
|
|
+ <p>
|
|
|
|
+ <span>科目(代码):</span
|
|
|
|
+ ><span>{{ detailInfo.courseNameCode }}</span>
|
|
|
|
+ </p>
|
|
</div>
|
|
</div>
|
|
- <div class="student-views">
|
|
|
|
|
|
+ <div class="detail-body-head-right">
|
|
|
|
+ <el-button
|
|
|
|
+ type="primary"
|
|
|
|
+ title="查看上一个"
|
|
|
|
+ :disabled="holding"
|
|
|
|
+ @click="changeStudent(0)"
|
|
|
|
+ >
|
|
|
|
+ <i class="icon icon-arrow-left"></i>
|
|
|
|
+ </el-button>
|
|
|
|
+ <el-button
|
|
|
|
+ type="primary"
|
|
|
|
+ title="查看下一个"
|
|
|
|
+ :disabled="holding"
|
|
|
|
+ @click="changeStudent(1)"
|
|
|
|
+ >
|
|
|
|
+ <i class="icon icon-arrow-right"></i>
|
|
|
|
+ </el-button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="warning-detail-main">
|
|
|
|
+ <div class="warning-action">
|
|
<div class="student-avatar">
|
|
<div class="student-avatar">
|
|
<img
|
|
<img
|
|
:src="detailInfo.basePhotoPath"
|
|
:src="detailInfo.basePhotoPath"
|
|
@@ -67,186 +66,155 @@
|
|
<div class="avatar-default" v-else>
|
|
<div class="avatar-default" v-else>
|
|
<i class="el-icon-user-solid"></i>
|
|
<i class="el-icon-user-solid"></i>
|
|
</div>
|
|
</div>
|
|
|
|
+ <div class="avatar-title">学生底照</div>
|
|
</div>
|
|
</div>
|
|
- <div class="student-video">
|
|
|
|
- <div class="student-video-item">
|
|
|
|
- <flv-media
|
|
|
|
- ref="FirstViewVideo"
|
|
|
|
- :live-url="firstViewVideo.liveUrl"
|
|
|
|
- v-if="firstViewVideoReady"
|
|
|
|
- ></flv-media>
|
|
|
|
- <div class="student-video-none" v-else>
|
|
|
|
- <i class="el-icon-video-camera-solid"></i>
|
|
|
|
- </div>
|
|
|
|
- <div
|
|
|
|
- v-if="firstViewVideoReady"
|
|
|
|
- :class="[
|
|
|
|
- 'student-video-muted',
|
|
|
|
- { 'is-active': !firstViewVideo.muted },
|
|
|
|
- ]"
|
|
|
|
- @click="videoMute('first')"
|
|
|
|
- >
|
|
|
|
- <i class="icon icon-audio"></i>
|
|
|
|
- </div>
|
|
|
|
|
|
+ <div class="warning-summary">
|
|
|
|
+ <div class="warning-summary-row">
|
|
|
|
+ <p class="warning-summary-col">
|
|
|
|
+ <i class="icon icon-bell"></i>
|
|
|
|
+ <span class="line-name"
|
|
|
|
+ >系统预警
|
|
|
|
+ <em :class="{ 'color-danger': detailInfo.warningCount > 0 }"
|
|
|
|
+ >{{ detailInfo.warningCount }}次</em
|
|
|
|
+ ></span
|
|
|
|
+ >
|
|
|
|
+ </p>
|
|
|
|
+ <p class="warning-summary-col">
|
|
|
|
+ <i class="icon icon-face"></i>
|
|
|
|
+ <span class="line-name"
|
|
|
|
+ >陌生人脸
|
|
|
|
+ <em>{{ detailInfo.multipleFaceCount }}次</em>
|
|
|
|
+ </span>
|
|
|
|
+ </p>
|
|
</div>
|
|
</div>
|
|
- <div class="student-video-item">
|
|
|
|
- <flv-media
|
|
|
|
- ref="SecondViewVideo"
|
|
|
|
- :live-url="secondViewVideo.liveUrl"
|
|
|
|
- v-if="secondViewVideoReady"
|
|
|
|
- ></flv-media>
|
|
|
|
- <div class="student-video-none" v-else>
|
|
|
|
- <i class="el-icon-video-camera-solid"></i>
|
|
|
|
- </div>
|
|
|
|
- <div
|
|
|
|
- v-if="secondViewVideoReady"
|
|
|
|
- :class="[
|
|
|
|
- 'student-video-muted',
|
|
|
|
- { 'is-active': !secondViewVideo.muted },
|
|
|
|
- ]"
|
|
|
|
- @click="videoMute('second')"
|
|
|
|
- >
|
|
|
|
- <i class="icon icon-audio"></i>
|
|
|
|
- </div>
|
|
|
|
|
|
+ <div class="warning-summary-row">
|
|
|
|
+ <p class="warning-summary-col">
|
|
|
|
+ <i class="icon icon-info"></i>
|
|
|
|
+ <span class="line-name"
|
|
|
|
+ >异常处理
|
|
|
|
+ <em>{{ detailInfo.exceptionCount }}次</em>
|
|
|
|
+ </span>
|
|
|
|
+ </p>
|
|
|
|
+ <p class="warning-summary-col">
|
|
|
|
+ <i class="icon icon-success"></i>
|
|
|
|
+ <span class="line-name"
|
|
|
|
+ >违纪状态
|
|
|
|
+ <em :class="{ 'color-danger': isBreach }">
|
|
|
|
+ {{ isBreach ? "违纪" : "正常" }}</em
|
|
|
|
+ >
|
|
|
|
+ </span>
|
|
|
|
+ </p>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="summary-bg">
|
|
|
|
+ <div class="summary-bg-line"></div>
|
|
|
|
+ <div class="summary-bg-line"></div>
|
|
|
|
+ <div class="summary-bg-spin"></div>
|
|
|
|
+ <div class="summary-bg-spin"></div>
|
|
|
|
+ <div class="summary-bg-spin"></div>
|
|
|
|
+ <div class="summary-bg-spin"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
- </div>
|
|
|
|
- <div class="student-exception">
|
|
|
|
- <ul>
|
|
|
|
- <li v-for="(log, index) in exceptionSummary" :key="index">
|
|
|
|
- <i>{{ index + 1 }}</i>
|
|
|
|
- <h4>{{ log.title }}</h4>
|
|
|
|
- <p v-if="log.desc">{{ log.desc }}</p>
|
|
|
|
- <!-- <p>
|
|
|
|
- 时间段:
|
|
|
|
- <span v-if="log.startTime">{{ log.startTime }} ~ </span>
|
|
|
|
- <span>{{ log.endTime }}</span>
|
|
|
|
- </p>
|
|
|
|
- <p v-if="log.durationTime">持续时长约:{{ log.durationTime }}</p> -->
|
|
|
|
- </li>
|
|
|
|
- </ul>
|
|
|
|
- </div>
|
|
|
|
- </div>
|
|
|
|
- </div>
|
|
|
|
-
|
|
|
|
- <div class="warning-detail-body">
|
|
|
|
- <div class="warning-body-head clear-float">
|
|
|
|
- <div class="warning-body-head-action">
|
|
|
|
- <h3>考试轨迹</h3>
|
|
|
|
- <!-- <el-button
|
|
|
|
- class="el-icon-btn"
|
|
|
|
- type="primary"
|
|
|
|
- icon="icon icon-view"
|
|
|
|
- ></el-button> -->
|
|
|
|
- <el-button
|
|
|
|
- class="el-icon-btn"
|
|
|
|
- type="primary"
|
|
|
|
- icon="icon icon-text"
|
|
|
|
- @click="toSendTextMsg"
|
|
|
|
- title="发送文字提醒"
|
|
|
|
- v-if="actionValid"
|
|
|
|
- ></el-button>
|
|
|
|
- <el-button
|
|
|
|
- class="el-icon-btn"
|
|
|
|
- type="primary"
|
|
|
|
- icon="icon icon-audio"
|
|
|
|
- @click="toSendAudioMsg"
|
|
|
|
- v-if="actionValid"
|
|
|
|
- ></el-button>
|
|
|
|
- <el-popover
|
|
|
|
- class="warning-body-head-call"
|
|
|
|
- placement="bottom-start"
|
|
|
|
- v-model="popoverVisible"
|
|
|
|
- v-if="detailInfo.monitorVideoSource && actionValid"
|
|
|
|
- >
|
|
|
|
- <el-button type="success" @click="answer(0)" :loading="holding"
|
|
|
|
|
|
+ <div class="action-list">
|
|
|
|
+ <el-button
|
|
|
|
+ v-if="actionValid"
|
|
|
|
+ icon="icon icon-text-message"
|
|
|
|
+ size="mideum"
|
|
|
|
+ @click="toSendTextMsg"
|
|
|
|
+ >文字提醒</el-button
|
|
|
|
+ >
|
|
|
|
+ <el-button
|
|
|
|
+ v-if="actionValid"
|
|
|
|
+ icon="icon icon-record"
|
|
|
|
+ size="mideum"
|
|
|
|
+ @click="toSendAudioMsg"
|
|
|
|
+ >录音提醒</el-button
|
|
|
|
+ >
|
|
|
|
+ <el-button
|
|
|
|
+ v-if="detailInfo.monitorVideoSource && actionValid"
|
|
|
|
+ icon="icon icon-call"
|
|
|
|
+ size="mideum"
|
|
|
|
+ :loading="holding"
|
|
|
|
+ @click="answer(0)"
|
|
>语音通话</el-button
|
|
>语音通话</el-button
|
|
>
|
|
>
|
|
- <el-button type="primary" @click="answer(1)" :loading="holding"
|
|
|
|
|
|
+ <el-button
|
|
|
|
+ v-if="detailInfo.monitorVideoSource && actionValid"
|
|
|
|
+ icon="icon icon-media"
|
|
|
|
+ size="mideum"
|
|
|
|
+ :loading="holding"
|
|
|
|
+ @click="answer(1)"
|
|
>视频通话</el-button
|
|
>视频通话</el-button
|
|
>
|
|
>
|
|
- <el-button type="primary" slot="reference">实时通话</el-button>
|
|
|
|
- </el-popover>
|
|
|
|
- </div>
|
|
|
|
- <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>{{ detailInfo.warningCount }}次</span>
|
|
|
|
- </p>
|
|
|
|
- <p class="summary-line-item">
|
|
|
|
- <i class="line-point line-point-danger"></i>
|
|
|
|
- <span class="line-name">陌生人脸</span>
|
|
|
|
- <span>{{ detailInfo.multipleFaceCount }}次</span>
|
|
|
|
- </p>
|
|
|
|
- <p class="summary-line-item">
|
|
|
|
- <i class="line-point line-point-danger"></i>
|
|
|
|
- <span class="line-name">异常处理</span>
|
|
|
|
- <span>{{ detailInfo.exceptionCount }}次</span>
|
|
|
|
- </p>
|
|
|
|
- <p class="summary-line-item">
|
|
|
|
- <span></span>
|
|
|
|
- <span>
|
|
|
|
- <b>违纪状态:</b>
|
|
|
|
- <b :class="{ 'color-danger': isBreach }">
|
|
|
|
- {{ isBreach ? "违纪" : "正常" }}
|
|
|
|
- </b>
|
|
|
|
- </span>
|
|
|
|
- </p>
|
|
|
|
- <el-button
|
|
|
|
- :type="isBreach ? 'success' : 'danger'"
|
|
|
|
- icon="icon icon-stop"
|
|
|
|
- @click="toBreach"
|
|
|
|
- >{{ isBreach ? "撤销违纪" : "违纪处理" }}</el-button
|
|
|
|
- >
|
|
|
|
- <el-button
|
|
|
|
- type="warning"
|
|
|
|
- icon="icon icon-forbide"
|
|
|
|
- @click="toFinish"
|
|
|
|
- v-if="detailInfo.statusCode === 'ANSWERING'"
|
|
|
|
- >强制收卷</el-button
|
|
|
|
- >
|
|
|
|
- </div>
|
|
|
|
- </div>
|
|
|
|
- <div class="warning-body-main">
|
|
|
|
- <div
|
|
|
|
- class="warning-history"
|
|
|
|
- v-for="log in detailInfo.examStudentLogList"
|
|
|
|
- :key="log.id"
|
|
|
|
- >
|
|
|
|
- <div class="warning-history-info">
|
|
|
|
- <h3>{{ log.title }}</h3>
|
|
|
|
- <p v-if="log.desc">{{ log.desc }}</p>
|
|
|
|
- <p>
|
|
|
|
- 时间段:
|
|
|
|
- <span v-if="log.startTime">{{ log.startTime }} ~ </span>
|
|
|
|
- <span>{{ log.endTime }}</span>
|
|
|
|
- </p>
|
|
|
|
- <p v-if="log.durationTime">持续时长约:{{ log.durationTime }}</p>
|
|
|
|
|
|
+ <el-button
|
|
|
|
+ icon="icon icon-info-danger"
|
|
|
|
+ size="mideum"
|
|
|
|
+ @click="toBreach"
|
|
|
|
+ >{{ isBreach ? "撤销违纪" : "违纪处理" }}</el-button
|
|
|
|
+ >
|
|
|
|
+ <el-button
|
|
|
|
+ v-if="detailInfo.statusCode !== 'ANSWERING'"
|
|
|
|
+ icon="icon icon-paper-danger"
|
|
|
|
+ size="mideum"
|
|
|
|
+ @click="toFinish"
|
|
|
|
+ >强制收卷</el-button
|
|
|
|
+ >
|
|
</div>
|
|
</div>
|
|
- <div
|
|
|
|
- :class="[
|
|
|
|
- 'warning-history-type',
|
|
|
|
- log.viewType === 'common' ? 'type-common' : 'type-exception',
|
|
|
|
- ]"
|
|
|
|
- >
|
|
|
|
- <i
|
|
|
|
- :class="[
|
|
|
|
- 'icon',
|
|
|
|
- {
|
|
|
|
- 'icon-current-step': log.viewType === 'common',
|
|
|
|
- 'icon-warning-act': log.viewType === 'warning',
|
|
|
|
- 'icon-net-break': log.viewType === 'exception',
|
|
|
|
- },
|
|
|
|
- ]"
|
|
|
|
- ></i>
|
|
|
|
|
|
+ </div>
|
|
|
|
+ <div class="warning-content">
|
|
|
|
+ <div class="warning-videos">
|
|
|
|
+ <div
|
|
|
|
+ v-for="item in viewVideos"
|
|
|
|
+ :key="item.source"
|
|
|
|
+ class="student-video-item"
|
|
|
|
+ >
|
|
|
|
+ <div class="student-video-container">
|
|
|
|
+ <div class="student-video-tips">{{ item.name }}</div>
|
|
|
|
+ <flv-media
|
|
|
|
+ :ref="item.ref"
|
|
|
|
+ :live-url="item.liveUrl"
|
|
|
|
+ @muted-change="videoAllMuted"
|
|
|
|
+ ></flv-media>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
</div>
|
|
</div>
|
|
- <div class="warning-history-media">
|
|
|
|
- <ul class="media-list" v-if="log.photos">
|
|
|
|
- <li v-for="(photo, pindex) in log.photos" :key="pindex">
|
|
|
|
- <img :src="photo" @click="toViewImg(photo)" />
|
|
|
|
- </li>
|
|
|
|
- </ul>
|
|
|
|
|
|
+ <!-- track -->
|
|
|
|
+ <div class="warning-track">
|
|
|
|
+ <h3 class="warning-track-title">
|
|
|
|
+ <i class="icon icon-track"></i>考试轨迹
|
|
|
|
+ </h3>
|
|
|
|
+ <div
|
|
|
|
+ class="warning-track-item"
|
|
|
|
+ v-for="log in detailInfo.examStudentLogList"
|
|
|
|
+ :key="log.id"
|
|
|
|
+ >
|
|
|
|
+ <div
|
|
|
|
+ :class="[
|
|
|
|
+ 'warning-track-type',
|
|
|
|
+ log.viewType === 'common' ? 'type-common' : 'type-exception',
|
|
|
|
+ ]"
|
|
|
|
+ >
|
|
|
|
+ <i :class="['icon', `icon-track-${log.viewType}`]"></i>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="warning-track-body">
|
|
|
|
+ <div class="warning-track-info">
|
|
|
|
+ <h3>{{ log.title }}</h3>
|
|
|
|
+ <p v-if="log.desc">{{ log.desc }}</p>
|
|
|
|
+ <p>
|
|
|
|
+ 时间段:
|
|
|
|
+ <span v-if="log.startTime">{{ log.startTime }} ~ </span>
|
|
|
|
+ <span>{{ log.endTime }}</span>
|
|
|
|
+ </p>
|
|
|
|
+ <p v-if="log.durationTime">
|
|
|
|
+ 持续时长约:{{ log.durationTime }}
|
|
|
|
+ </p>
|
|
|
|
+ </div>
|
|
|
|
+ <ul class="warning-track-media" v-if="log.photos">
|
|
|
|
+ <li v-for="(photo, pindex) in log.photos" :key="pindex">
|
|
|
|
+ <img :src="photo" @click="toViewImg(photo)" />
|
|
|
|
+ </li>
|
|
|
|
+ </ul>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@@ -260,12 +228,12 @@
|
|
></student-breach-dialog>
|
|
></student-breach-dialog>
|
|
<!-- warning-text-message-dialog -->
|
|
<!-- warning-text-message-dialog -->
|
|
<warning-text-message-dialog
|
|
<warning-text-message-dialog
|
|
- :record-id="recordId"
|
|
|
|
|
|
+ :record-id="examRecordId"
|
|
ref="WarningTextMessageDialog"
|
|
ref="WarningTextMessageDialog"
|
|
></warning-text-message-dialog>
|
|
></warning-text-message-dialog>
|
|
<!-- audio-record-dialog -->
|
|
<!-- audio-record-dialog -->
|
|
<audio-record-dialog
|
|
<audio-record-dialog
|
|
- :record-id="recordId"
|
|
|
|
|
|
+ :record-id="examRecordId"
|
|
ref="AudioRecordDialog"
|
|
ref="AudioRecordDialog"
|
|
></audio-record-dialog>
|
|
></audio-record-dialog>
|
|
<!-- image-preview -->
|
|
<!-- image-preview -->
|
|
@@ -285,6 +253,7 @@
|
|
append-to-body
|
|
append-to-body
|
|
fullscreen
|
|
fullscreen
|
|
>
|
|
>
|
|
|
|
+ <!-- TODO:拖动 -->
|
|
<div class="communication-box" v-show="!isWaiting">
|
|
<div class="communication-box" v-show="!isWaiting">
|
|
<div class="communication-host" id="communication-host"></div>
|
|
<div class="communication-host" id="communication-host"></div>
|
|
<div class="communication-guest" id="communication-guest"></div>
|
|
<div class="communication-guest" id="communication-guest"></div>
|
|
@@ -325,6 +294,8 @@ import {
|
|
import {
|
|
import {
|
|
invigilateDetail,
|
|
invigilateDetail,
|
|
invigilateFinish,
|
|
invigilateFinish,
|
|
|
|
+ communicationCalling,
|
|
|
|
+ communicationOver,
|
|
warningStudentDetail,
|
|
warningStudentDetail,
|
|
getUserMonitorKey,
|
|
getUserMonitorKey,
|
|
} from "@/api/invigilation";
|
|
} from "@/api/invigilation";
|
|
@@ -334,7 +305,12 @@ import WarningTextMessageDialog from "./WarningTextMessageDialog";
|
|
import AudioRecordDialog from "./audioRecord/AudioRecordDialog";
|
|
import AudioRecordDialog from "./audioRecord/AudioRecordDialog";
|
|
import SimpleImagePreview from "@/components/imagePreview/SimpleImagePreview";
|
|
import SimpleImagePreview from "@/components/imagePreview/SimpleImagePreview";
|
|
import SecondTimer from "../common/SecondTimer";
|
|
import SecondTimer from "../common/SecondTimer";
|
|
-import { formatDate, timeNumberToText, objTypeOf } from "@/utils/utils";
|
|
|
|
|
|
+import {
|
|
|
|
+ formatDate,
|
|
|
|
+ timeNumberToText,
|
|
|
|
+ objTypeOf,
|
|
|
|
+ snakeToHump,
|
|
|
|
+} from "@/utils/utils";
|
|
import { mapState } from "vuex";
|
|
import { mapState } from "vuex";
|
|
|
|
|
|
const domEmpty = (dom) => {
|
|
const domEmpty = (dom) => {
|
|
@@ -355,21 +331,14 @@ export default {
|
|
},
|
|
},
|
|
data() {
|
|
data() {
|
|
return {
|
|
return {
|
|
- recordId: this.$route.params.recordId,
|
|
|
|
|
|
+ examRecordId: this.$route.params.examRecordId,
|
|
|
|
+ autoAnswerInfo: null,
|
|
detailInfo: {},
|
|
detailInfo: {},
|
|
curDetail: {},
|
|
curDetail: {},
|
|
serialIds: [],
|
|
serialIds: [],
|
|
exceptionSummary: [],
|
|
exceptionSummary: [],
|
|
- firstViewVideo: {
|
|
|
|
- liveUrl: "",
|
|
|
|
- muted: true,
|
|
|
|
- },
|
|
|
|
- secondViewVideo: {
|
|
|
|
- liveUrl: "",
|
|
|
|
- muted: true,
|
|
|
|
- },
|
|
|
|
- firstViewVideoReady: false,
|
|
|
|
- secondViewVideoReady: false,
|
|
|
|
|
|
+ viewVideos: [],
|
|
|
|
+ viewVideoReady: false,
|
|
holding: false,
|
|
holding: false,
|
|
// communication
|
|
// communication
|
|
popoverVisible: false,
|
|
popoverVisible: false,
|
|
@@ -378,7 +347,7 @@ export default {
|
|
localStream: null,
|
|
localStream: null,
|
|
dialogVisible: false,
|
|
dialogVisible: false,
|
|
isWaiting: true,
|
|
isWaiting: true,
|
|
- subscribeSetT: null,
|
|
|
|
|
|
+ subscribeSetTs: [],
|
|
loopRunning: false,
|
|
loopRunning: false,
|
|
loopSetTs: [],
|
|
loopSetTs: [],
|
|
isHandup: false,
|
|
isHandup: false,
|
|
@@ -407,11 +376,13 @@ export default {
|
|
},
|
|
},
|
|
},
|
|
},
|
|
mounted() {
|
|
mounted() {
|
|
|
|
+ const autoAnswerInfo = window.sessionStorage.getItem("autoAnswerInfo");
|
|
|
|
+ this.autoAnswerInfo = autoAnswerInfo ? JSON.parse(autoAnswerInfo) : null;
|
|
this.initData();
|
|
this.initData();
|
|
},
|
|
},
|
|
methods: {
|
|
methods: {
|
|
async initData() {
|
|
async initData() {
|
|
- this.recordId = this.$route.params.recordId;
|
|
|
|
|
|
+ this.examRecordId = this.$route.params.examRecordId;
|
|
await this.getInvigilateDetail().catch(() => {});
|
|
await this.getInvigilateDetail().catch(() => {});
|
|
await this.getStudentVideo().catch(() => {});
|
|
await this.getStudentVideo().catch(() => {});
|
|
this.holding = false;
|
|
this.holding = false;
|
|
@@ -430,6 +401,18 @@ export default {
|
|
this.loopRunning = false;
|
|
this.loopRunning = false;
|
|
this.clearLoopSetTs();
|
|
this.clearLoopSetTs();
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // 自动应答
|
|
|
|
+ if (this.autoAnswerInfo) {
|
|
|
|
+ this.autoAnswer();
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ clearSubscribeSetTs() {
|
|
|
|
+ if (!this.subscribeSetTs.length) return;
|
|
|
|
+ this.subscribeSetTs.forEach((sett) => {
|
|
|
|
+ clearTimeout(sett);
|
|
|
|
+ });
|
|
|
|
+ this.subscribeSetTs = [];
|
|
},
|
|
},
|
|
clearLoopSetTs() {
|
|
clearLoopSetTs() {
|
|
if (!this.loopSetTs.length) return;
|
|
if (!this.loopSetTs.length) return;
|
|
@@ -451,34 +434,49 @@ export default {
|
|
);
|
|
);
|
|
},
|
|
},
|
|
async getStudentVideo() {
|
|
async getStudentVideo() {
|
|
- const res = await warningStudentDetail({ recordId: this.recordId });
|
|
|
|
- const records = res.data.data.map((item, index) => {
|
|
|
|
- const domain = this.liveDomains[index] || "";
|
|
|
|
|
|
+ const res = await warningStudentDetail({
|
|
|
|
+ examRecordId: this.examRecordId,
|
|
|
|
+ });
|
|
|
|
+ const orderSources = [
|
|
|
|
+ "CLIENT_CAMERA",
|
|
|
|
+ "CLIENT_SCREEN",
|
|
|
|
+ "MOBILE_FIRST",
|
|
|
|
+ "MOBILE_SECOND",
|
|
|
|
+ ];
|
|
|
|
+ const sourceNames = {
|
|
|
|
+ CLIENT_CAMERA: "电脑摄像头",
|
|
|
|
+ CLIENT_SCREEN: "考生屏幕",
|
|
|
|
+ MOBILE_FIRST: "手机主机位",
|
|
|
|
+ MOBILE_SECOND: "手机辅机位",
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ let records = {};
|
|
|
|
+ res.data.data.forEach((item, index) => {
|
|
|
|
+ const domain = this.liveDomains[index] || this.liveDomains[0];
|
|
item.liveUrl = item.liveUrl
|
|
item.liveUrl = item.liveUrl
|
|
? `${domain}/live/${item.liveUrl.toLowerCase()}.flv`
|
|
? `${domain}/live/${item.liveUrl.toLowerCase()}.flv`
|
|
: "";
|
|
: "";
|
|
- item.name = item.source;
|
|
|
|
|
|
+ item.name = sourceNames[item.source];
|
|
item.muted = true;
|
|
item.muted = true;
|
|
- return item;
|
|
|
|
|
|
+ item.ref = snakeToHump(item.source) + "Video";
|
|
|
|
+ records[item.source] = item;
|
|
});
|
|
});
|
|
- const orderSources = {
|
|
|
|
- CLIENT_CAMERA: 1,
|
|
|
|
- MOBILE_FIRST: 2,
|
|
|
|
- CLIENT_SCREEN: 3,
|
|
|
|
- MOBILE_SECOND: 4,
|
|
|
|
- };
|
|
|
|
|
|
|
|
- records.sort((a, b) => {
|
|
|
|
- return orderSources[a.source] - orderSources[b.source];
|
|
|
|
|
|
+ this.viewVideos = orderSources.map((source) => {
|
|
|
|
+ return (
|
|
|
|
+ records[source] || {
|
|
|
|
+ liveUrl: null,
|
|
|
|
+ muted: true,
|
|
|
|
+ source,
|
|
|
|
+ name: sourceNames[source],
|
|
|
|
+ ref: snakeToHump(source) + "Video",
|
|
|
|
+ }
|
|
|
|
+ );
|
|
});
|
|
});
|
|
-
|
|
|
|
- this.firstViewVideo = records[0] || {};
|
|
|
|
- this.secondViewVideo = records[1] || {};
|
|
|
|
-
|
|
|
|
- if (records.length) this.initSubscribeVideo();
|
|
|
|
|
|
+ this.initSubscribeVideo();
|
|
},
|
|
},
|
|
async getInvigilateDetail() {
|
|
async getInvigilateDetail() {
|
|
- const res = await invigilateDetail(this.recordId);
|
|
|
|
|
|
+ const res = await invigilateDetail(this.examRecordId);
|
|
this.detailInfo = res.data.data;
|
|
this.detailInfo = res.data.data;
|
|
this.detailInfo.examStudentLogList = this.parseStudentLogs(
|
|
this.detailInfo.examStudentLogList = this.parseStudentLogs(
|
|
this.detailInfo.examStudentLogList
|
|
this.detailInfo.examStudentLogList
|
|
@@ -574,7 +572,7 @@ export default {
|
|
return logs;
|
|
return logs;
|
|
},
|
|
},
|
|
changeStudent(type) {
|
|
changeStudent(type) {
|
|
- let index = this.detailIds.indexOf(this.recordId);
|
|
|
|
|
|
+ let index = this.detailIds.indexOf(this.examRecordId);
|
|
if (type) {
|
|
if (type) {
|
|
if (index >= this.detailIds.length - 1) {
|
|
if (index >= this.detailIds.length - 1) {
|
|
this.$message.error("当前没有下一个学生了");
|
|
this.$message.error("当前没有下一个学生了");
|
|
@@ -598,7 +596,7 @@ export default {
|
|
this.$router.replace({
|
|
this.$router.replace({
|
|
name: "WarningDetail",
|
|
name: "WarningDetail",
|
|
params: {
|
|
params: {
|
|
- recordId: this.detailIds[index],
|
|
|
|
|
|
+ examRecordId: this.detailIds[index],
|
|
},
|
|
},
|
|
});
|
|
});
|
|
},
|
|
},
|
|
@@ -648,14 +646,6 @@ export default {
|
|
this.getInvigilateDetail();
|
|
this.getInvigilateDetail();
|
|
},
|
|
},
|
|
// video relative
|
|
// video relative
|
|
- initSubscribeVideo() {
|
|
|
|
- if (this.firstViewVideo.liveUrl) this.firstViewVideoReady = true;
|
|
|
|
- if (this.secondViewVideo.liveUrl) this.secondViewVideoReady = true;
|
|
|
|
- },
|
|
|
|
- closeSubscribeVideo() {
|
|
|
|
- this.firstViewVideoReady = false;
|
|
|
|
- this.secondViewVideoReady = false;
|
|
|
|
- },
|
|
|
|
notifyError(content) {
|
|
notifyError(content) {
|
|
this.$notify({
|
|
this.$notify({
|
|
type: "error",
|
|
type: "error",
|
|
@@ -697,6 +687,14 @@ export default {
|
|
});
|
|
});
|
|
return initLocalStreamResult && localStream;
|
|
return initLocalStreamResult && localStream;
|
|
},
|
|
},
|
|
|
|
+ async autoAnswer() {
|
|
|
|
+ await this.answer(this.autoAnswerInfo.isVideo);
|
|
|
|
+ // 更改学生的通话申请状态
|
|
|
|
+ await communicationCalling({
|
|
|
|
+ recordId: this.examRecordId,
|
|
|
|
+ source: this.autoAnswerInfo.source,
|
|
|
|
+ });
|
|
|
|
+ },
|
|
async answer(isVideo) {
|
|
async answer(isVideo) {
|
|
const result = await checkSystemRequirements().catch(() => {
|
|
const result = await checkSystemRequirements().catch(() => {
|
|
this.$message.error(
|
|
this.$message.error(
|
|
@@ -711,9 +709,9 @@ export default {
|
|
// 手机端userId各不同
|
|
// 手机端userId各不同
|
|
if (this.holding) return;
|
|
if (this.holding) return;
|
|
this.holding = true;
|
|
this.holding = true;
|
|
- this.videoAllMute();
|
|
|
|
|
|
+ this.videoAllMuted();
|
|
|
|
|
|
- await this.initClient(this.recordId).catch(() => {});
|
|
|
|
|
|
+ await this.initClient(this.examRecordId).catch(() => {});
|
|
if (!this.client) {
|
|
if (!this.client) {
|
|
this.holding = false;
|
|
this.holding = false;
|
|
return;
|
|
return;
|
|
@@ -736,15 +734,30 @@ export default {
|
|
if (remoteStream.getType() !== "main") return;
|
|
if (remoteStream.getType() !== "main") return;
|
|
console.log(`有效视频${remoteStream.getUserId()},准备订阅`);
|
|
console.log(`有效视频${remoteStream.getUserId()},准备订阅`);
|
|
|
|
|
|
- // 延迟订阅视频
|
|
|
|
- this.subscribeSetT = setTimeout(() => {
|
|
|
|
|
|
+ if (this.autoAnswerInfo) {
|
|
|
|
+ // 存在自动应答信息时,不再延迟订阅学生音视频流
|
|
this.client
|
|
this.client
|
|
.subscribe(remoteStream, { audio: true, video: true })
|
|
.subscribe(remoteStream, { audio: true, video: true })
|
|
.catch((error) => {
|
|
.catch((error) => {
|
|
console.log(`${remoteStream.getUserId()}视频订阅失败!`, error);
|
|
console.log(`${remoteStream.getUserId()}视频订阅失败!`, error);
|
|
this.notifyError("学生视频获取失败!");
|
|
this.notifyError("学生视频获取失败!");
|
|
});
|
|
});
|
|
- }, 5000);
|
|
|
|
|
|
+ } else {
|
|
|
|
+ // 延迟订阅视频
|
|
|
|
+ this.subscribeSetTs.push(
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ this.client
|
|
|
|
+ .subscribe(remoteStream, { audio: true, video: true })
|
|
|
|
+ .catch((error) => {
|
|
|
|
+ console.log(
|
|
|
|
+ `${remoteStream.getUserId()}视频订阅失败!`,
|
|
|
|
+ error
|
|
|
|
+ );
|
|
|
|
+ this.notifyError("学生视频获取失败!");
|
|
|
|
+ });
|
|
|
|
+ }, 5000)
|
|
|
|
+ );
|
|
|
|
+ }
|
|
});
|
|
});
|
|
this.client.on("stream-subscribed", (event) => {
|
|
this.client.on("stream-subscribed", (event) => {
|
|
const remoteStream = event.stream;
|
|
const remoteStream = event.stream;
|
|
@@ -813,10 +826,19 @@ export default {
|
|
async hangup() {
|
|
async hangup() {
|
|
if (this.isHandup) return;
|
|
if (this.isHandup) return;
|
|
this.isHandup = true;
|
|
this.isHandup = true;
|
|
- if (this.subscribeSetT) clearTimeout(this.subscribeSetT);
|
|
|
|
-
|
|
|
|
|
|
+ this.clearSubscribeSetTs();
|
|
this.$refs.SecondTimer.end();
|
|
this.$refs.SecondTimer.end();
|
|
|
|
|
|
|
|
+ if (this.autoAnswerInfo) {
|
|
|
|
+ // 结束学生的通话
|
|
|
|
+ await communicationOver({
|
|
|
|
+ recordId: this.examRecordId,
|
|
|
|
+ source: this.autoAnswerInfo.source,
|
|
|
|
+ }).catch(() => {
|
|
|
|
+ console.log("结束通话状态异常!");
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
// 取消发布本地视频
|
|
// 取消发布本地视频
|
|
await this.client.unpublish(this.localStream).catch((error) => {
|
|
await this.client.unpublish(this.localStream).catch((error) => {
|
|
console.log("取消发布本地视频失败!", error);
|
|
console.log("取消发布本地视频失败!", error);
|
|
@@ -842,39 +864,19 @@ export default {
|
|
this.isWaiting = true;
|
|
this.isWaiting = true;
|
|
// this.initSubscribeVideo();
|
|
// this.initSubscribeVideo();
|
|
},
|
|
},
|
|
- videoMute(type) {
|
|
|
|
- if (type === "first") {
|
|
|
|
- if (this.secondViewVideoReady) {
|
|
|
|
- let res = this.$refs.SecondViewVideo.mutedPlayer(true);
|
|
|
|
- if (res) this.secondViewVideo.muted = true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- const res = this.$refs.FirstViewVideo.mutedPlayer(
|
|
|
|
- !this.firstViewVideo.muted
|
|
|
|
- );
|
|
|
|
- if (res) this.firstViewVideo.muted = !this.firstViewVideo.muted;
|
|
|
|
- } else {
|
|
|
|
- if (this.firstViewVideoReady) {
|
|
|
|
- let res = this.$refs.FirstViewVideo.mutedPlayer(true);
|
|
|
|
- if (res) this.firstViewVideo.muted = true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- const res = this.$refs.SecondViewVideo.mutedPlayer(
|
|
|
|
- !this.secondViewVideo.muted
|
|
|
|
- );
|
|
|
|
-
|
|
|
|
- if (res) this.secondViewVideo.muted = !this.secondViewVideo.muted;
|
|
|
|
- }
|
|
|
|
|
|
+ initSubscribeVideo() {
|
|
|
|
+ this.viewVideoReady = true;
|
|
},
|
|
},
|
|
- videoAllMute() {
|
|
|
|
- if (this.firstViewVideoReady) {
|
|
|
|
- let res = this.$refs.FirstViewVideo.mutedPlayer(true);
|
|
|
|
- if (res) this.firstViewVideo.muted = true;
|
|
|
|
- }
|
|
|
|
- if (this.secondViewVideoReady) {
|
|
|
|
- let res = this.$refs.SecondViewVideo.mutedPlayer(true);
|
|
|
|
- if (res) this.secondViewVideo.muted = true;
|
|
|
|
- }
|
|
|
|
|
|
+ closeSubscribeVideo() {
|
|
|
|
+ this.viewVideoReady = false;
|
|
|
|
+ },
|
|
|
|
+ videoAllMuted() {
|
|
|
|
+ this.viewVideos
|
|
|
|
+ .filter((vv) => vv.liveUrl)
|
|
|
|
+ .forEach((vv) => {
|
|
|
|
+ let res = this.$refs[vv.ref][0].mutedPlayer(true);
|
|
|
|
+ if (res) vv.muted = true;
|
|
|
|
+ });
|
|
},
|
|
},
|
|
toViewImg(photo) {
|
|
toViewImg(photo) {
|
|
this.curImage = { imgSrc: photo };
|
|
this.curImage = { imgSrc: photo };
|
|
@@ -885,9 +887,10 @@ export default {
|
|
},
|
|
},
|
|
},
|
|
},
|
|
beforeDestroy() {
|
|
beforeDestroy() {
|
|
|
|
+ window.sessionStorage.removeItem("autoAnswerInfo");
|
|
this.loopRunning = false;
|
|
this.loopRunning = false;
|
|
this.clearLoopSetTs();
|
|
this.clearLoopSetTs();
|
|
- if (this.subscribeSetT) clearTimeout(this.subscribeSetT);
|
|
|
|
|
|
+ this.clearSubscribeSetTs();
|
|
if (this.client) {
|
|
if (this.client) {
|
|
this.client.leave();
|
|
this.client.leave();
|
|
this.client.off("*");
|
|
this.client.off("*");
|