|
@@ -0,0 +1,571 @@
|
|
|
+<template>
|
|
|
+ <div class="warning-detail">
|
|
|
+ <div class="warning-detail-head">
|
|
|
+ <div class="warning-detail-title">
|
|
|
+ <h2>预警详情</h2>
|
|
|
+ <el-button size="mini" icon="el-icon-arrow-left">返回列表</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>张龙龙</span></p>
|
|
|
+ <p><span>证件号:</span><span>000000000000000008</span></p>
|
|
|
+ <p><span>科目(代码):</span><span>大学语文(1001)</span></p>
|
|
|
+ </div>
|
|
|
+ <div class="student-head-right">
|
|
|
+ <el-button
|
|
|
+ size="mini"
|
|
|
+ type="primary"
|
|
|
+ icon="el-icon-arrow-left"
|
|
|
+ ></el-button>
|
|
|
+ <el-button
|
|
|
+ size="mini"
|
|
|
+ type="primary"
|
|
|
+ icon="el-icon-arrow-right"
|
|
|
+ ></el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="student-views">
|
|
|
+ <div class="student-avatar">
|
|
|
+ <img :src="curStudent.stdAvatar" alt="" />
|
|
|
+ </div>
|
|
|
+ <div class="student-video">
|
|
|
+ <div class="student-video-item">
|
|
|
+ <video
|
|
|
+ src=""
|
|
|
+ ref="FirstViewVideo"
|
|
|
+ v-show="firstViewVideoReady"
|
|
|
+ ></video>
|
|
|
+ <div class="student-video-none" v-show="!firstViewVideoReady">
|
|
|
+ <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">
|
|
|
+ <i class="el-icon-video-camera-solid"></i>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="student-exception">
|
|
|
+ <ul>
|
|
|
+ <li v-for="(item, index) in exceptionList" :key="index">
|
|
|
+ <i>{{ index + 1 }}</i>
|
|
|
+ <h4>{{ item.title }}</h4>
|
|
|
+ <p>{{ item.desc }}</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 type="primary" icon="icon icon-view"></el-button>
|
|
|
+ <el-button type="primary" icon="icon icon-text"></el-button>
|
|
|
+ <el-button type="primary" icon="icon icon-audio"></el-button>
|
|
|
+ <el-popover
|
|
|
+ class="warning-body-head-call"
|
|
|
+ placement="bottom-start"
|
|
|
+ trigger="click"
|
|
|
+ >
|
|
|
+ <el-button type="success" @click="answer(0)">语音通话</el-button>
|
|
|
+ <el-button type="primary" @click="answer(1)">视频通话</el-button>
|
|
|
+ <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>
|
|
|
+ <el-button type="danger" icon="icon icon-stop">违纪处理</el-button>
|
|
|
+ <el-button type="warning" icon="icon icon-forbide"
|
|
|
+ >强制收卷</el-button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="warning-body-main"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 通话弹出层 -->
|
|
|
+ <el-dialog
|
|
|
+ custom-class="communication-dialog"
|
|
|
+ :visible.sync="dialogVisible"
|
|
|
+ width="600px"
|
|
|
+ :show-close="false"
|
|
|
+ :close-on-press-escape="false"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ 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-action">
|
|
|
+ <el-button round type="danger" @click="hangup">结束通话</el-button>
|
|
|
+ </div>
|
|
|
+ <div class="communication-info">
|
|
|
+ <span>当前网络状态良好</span>
|
|
|
+ <span>持续时长:{{ durationTime }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <span slot="footer" class="dialog-footer"> </span>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { initRongRTC, connectIM } from "@/plugins/RongIM";
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: "warning-detail",
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ exceptionList: [
|
|
|
+ {
|
|
|
+ title: "身份验证不通过",
|
|
|
+ desc: "系统发现该考生身份识别多次不通过,请监考老师关注!",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "疑似:有违规动作",
|
|
|
+ desc:
|
|
|
+ "系统发现该考生至少有多次以上持续1分钟以上的违规动作,且违规动作的持续时间已超出合理范围,请监考老师关注!",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "疑似:启用虚拟摄像头",
|
|
|
+ desc: "系统检测到考生可能使用了虚拟摄像头,请监考老师关注!",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ curStudent: {
|
|
|
+ stdAvatar: "/img/avatars/2.jpg",
|
|
|
+ stdCardNo: "000000000000000008",
|
|
|
+ stdName: "张龙龙",
|
|
|
+ subjectName: "大学英语",
|
|
|
+ subjectCode: "10006",
|
|
|
+ liveUrlFirst: "",
|
|
|
+ liveUrlThree: "",
|
|
|
+ roomId: "1",
|
|
|
+ },
|
|
|
+ firstViewVideoReady: false,
|
|
|
+ threeViewVideoReady: false,
|
|
|
+ // communication
|
|
|
+ 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,
|
|
|
+ dialogVisible: false,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ async initRong() {
|
|
|
+ this.rongUserId = await connectIM(this.token).catch((error) => {
|
|
|
+ console.log(error);
|
|
|
+ });
|
|
|
+ if (!this.rongUserId) {
|
|
|
+ this.$message.error("初始化错误!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.rongRTC = initRongRTC({ debug: true });
|
|
|
+ },
|
|
|
+ initVideo() {
|
|
|
+ const { Stream } = this.rongRTC;
|
|
|
+ // 初始化stream
|
|
|
+ this.stream = new Stream({
|
|
|
+ published: (user) => {
|
|
|
+ console.log(user);
|
|
|
+ },
|
|
|
+ unpublished: (user) => {
|
|
|
+ this.stream.unsubscribe(user);
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ this.stream.subscribe({ liveUrl: this.liveUrlFirst }).then((user) => {
|
|
|
+ let {
|
|
|
+ stream: { mediaStream },
|
|
|
+ } = user;
|
|
|
+ this.$refs.FirstViewVideo.srcObject = mediaStream;
|
|
|
+ this.$refs.FirstViewVideo.autoplay = true;
|
|
|
+ this.firstViewVideoReady = true;
|
|
|
+ });
|
|
|
+
|
|
|
+ this.stream.subscribe({ liveUrl: this.liveUrlThree }).then((user) => {
|
|
|
+ let {
|
|
|
+ stream: { mediaStream },
|
|
|
+ } = user;
|
|
|
+ this.$refs.ThreeViewVideo.srcObject = mediaStream;
|
|
|
+ this.$refs.ThreeViewVideo.autoplay = true;
|
|
|
+ this.threeViewVideoReady = true;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ closeVideo() {
|
|
|
+ this.stream.unsubscribe({ liveUrl: this.liveUrlFirst });
|
|
|
+ this.stream.unsubscribe({ liveUrl: this.liveUrlThree });
|
|
|
+ this.firstViewVideoReady = false;
|
|
|
+ this.threeViewVideoReady = false;
|
|
|
+ },
|
|
|
+ async answer(isVideo) {
|
|
|
+ this.closeVideo();
|
|
|
+ this.dialogVisible = true;
|
|
|
+
|
|
|
+ const { Room, Stream, StreamType } = this.rongRTC;
|
|
|
+ // 初始化房间
|
|
|
+ this.room = new Room({
|
|
|
+ id: this.curStudent.roomId,
|
|
|
+ joined: (user) => {
|
|
|
+ console.log(user);
|
|
|
+ },
|
|
|
+ left: (user) => {
|
|
|
+ console.log(user);
|
|
|
+ },
|
|
|
+ });
|
|
|
+ // 加入房间
|
|
|
+ let joinRoomSuccess = true;
|
|
|
+ await this.room.join({ id: this.rongUserId }).catch((error) => {
|
|
|
+ console.log(error);
|
|
|
+ joinRoomSuccess = false;
|
|
|
+ });
|
|
|
+ if (!joinRoomSuccess) return;
|
|
|
+
|
|
|
+ // 初始化stream
|
|
|
+ // TODO:后加入房间的人,是否会执行先加入房间的人的published回调
|
|
|
+ this.stream = new Stream({
|
|
|
+ published: (user) => {
|
|
|
+ console.log(user);
|
|
|
+ this.stream.subscribe(user).then((user) => {
|
|
|
+ let {
|
|
|
+ stream: { mediaStream },
|
|
|
+ } = user;
|
|
|
+ this.$refs.VideoGuest.srcObject = mediaStream;
|
|
|
+ this.$refs.VideoGuest.autoplay = true;
|
|
|
+ this.videoGuestReady = true;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ unpublished: (user) => {
|
|
|
+ this.stream.unsubscribe(user);
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ // 按需发布本地音视频
|
|
|
+ this.curStreamType = isVideo
|
|
|
+ ? StreamType.AUDIO_AND_VIDEO
|
|
|
+ : StreamType.AUDIO;
|
|
|
+ const localStream = await this.stream.get().catch((error) => {
|
|
|
+ console.log(error);
|
|
|
+ this.$message.error("获取本地音视频失败!");
|
|
|
+ });
|
|
|
+ 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);
|
|
|
+ });
|
|
|
+ if (isVideo && streamPublishResult) {
|
|
|
+ this.$refs.VideoHost.autoplay = true;
|
|
|
+ this.$refs.VideoHost.srcObject = localStream.mediaStream;
|
|
|
+ this.videoHostReady = true;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async hangup() {
|
|
|
+ const streamUser = {
|
|
|
+ id: this.rongUserId,
|
|
|
+ stream: {
|
|
|
+ tag: "RongCloudRTC",
|
|
|
+ type: this.curStreamType,
|
|
|
+ },
|
|
|
+ };
|
|
|
+ await this.stream.unpublish(streamUser).catch((error) => {
|
|
|
+ console.log(error);
|
|
|
+ });
|
|
|
+ await this.room.leave().catch((error) => {
|
|
|
+ console.log(error);
|
|
|
+ });
|
|
|
+
|
|
|
+ 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.initVideo();
|
|
|
+ },
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ if (this.rongRTC) this.rongRTC.destroy();
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.warning-detail {
|
|
|
+ &-head {
|
|
|
+ margin: -30px -30px 0;
|
|
|
+ padding: 30px;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #f0f4f9;
|
|
|
+ }
|
|
|
+ &-title {
|
|
|
+ overflow: hidden;
|
|
|
+ padding-bottom: 20px;
|
|
|
+ border-bottom: 1px solid #f0f4f9;
|
|
|
+ > h2 {
|
|
|
+ float: left;
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 18px;
|
|
|
+ line-height: 28px;
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
+ > .el-button {
|
|
|
+ float: right;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+.student-head {
|
|
|
+ padding: 20px 0;
|
|
|
+ overflow: hidden;
|
|
|
+ &-left {
|
|
|
+ float: left;
|
|
|
+ > p {
|
|
|
+ display: inline-block;
|
|
|
+ vertical-align: middle;
|
|
|
+ margin: 0;
|
|
|
+ line-height: 28px;
|
|
|
+ height: 28px;
|
|
|
+ margin-right: 15px;
|
|
|
+
|
|
|
+ &:first-child {
|
|
|
+ margin-right: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ > span {
|
|
|
+ color: #626a82;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ color: #202b4b;
|
|
|
+ margin-left: 5px;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &-right {
|
|
|
+ float: right;
|
|
|
+ }
|
|
|
+}
|
|
|
+.student-views {
|
|
|
+ height: 240px;
|
|
|
+ margin: 0 -15px;
|
|
|
+}
|
|
|
+.student-avatar {
|
|
|
+ padding: 0 15px;
|
|
|
+ width: 210px;
|
|
|
+ height: 100%;
|
|
|
+ float: left;
|
|
|
+ > img {
|
|
|
+ display: block;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ border-radius: 6px;
|
|
|
+ }
|
|
|
+}
|
|
|
+.student-video {
|
|
|
+ margin-left: 210px;
|
|
|
+ height: 240px;
|
|
|
+ font-size: 0;
|
|
|
+
|
|
|
+ &-item {
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &-none {
|
|
|
+ height: 100%;
|
|
|
+ background: #606060;
|
|
|
+ border-radius: 6px;
|
|
|
+ font-size: 50px;
|
|
|
+ text-align: center;
|
|
|
+ padding-top: 90px;
|
|
|
+ color: #202b4b;
|
|
|
+ }
|
|
|
+}
|
|
|
+.student-exception {
|
|
|
+ padding-top: 30px;
|
|
|
+ overflow: auto;
|
|
|
+ ul,
|
|
|
+ li {
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+ }
|
|
|
+ ul {
|
|
|
+ white-space: nowrap;
|
|
|
+ font-size: 0;
|
|
|
+ }
|
|
|
+ li {
|
|
|
+ display: inline-block;
|
|
|
+ vertical-align: top;
|
|
|
+ font-size: 14px;
|
|
|
+ position: relative;
|
|
|
+ padding-left: 38px;
|
|
|
+ width: 400px;
|
|
|
+
|
|
|
+ > i {
|
|
|
+ display: block;
|
|
|
+ position: absolute;
|
|
|
+ font-size: 29px;
|
|
|
+ line-height: 1;
|
|
|
+ color: #d9dfe8;
|
|
|
+ top: 3px;
|
|
|
+ left: 5px;
|
|
|
+ z-index: 9;
|
|
|
+ font-style: normal;
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ content: "";
|
|
|
+ display: block;
|
|
|
+ position: absolute;
|
|
|
+ background: #fff;
|
|
|
+ width: 15px;
|
|
|
+ height: 15px;
|
|
|
+ transform: rotate(45deg);
|
|
|
+ bottom: -2px;
|
|
|
+ right: -10px;
|
|
|
+ z-index: 9;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &:not(:last-child):after {
|
|
|
+ content: "";
|
|
|
+ display: block;
|
|
|
+ position: absolute;
|
|
|
+ background-image: url(../../../assets/bg-split-line.png);
|
|
|
+ background-position: 100% 100%;
|
|
|
+ width: 30px;
|
|
|
+ height: 52px;
|
|
|
+ top: 6px;
|
|
|
+ right: 10px;
|
|
|
+ z-index: 9;
|
|
|
+ }
|
|
|
+ h4 {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #202b4b;
|
|
|
+ line-height: 22px;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+ p {
|
|
|
+ margin: 0;
|
|
|
+ width: 304px;
|
|
|
+ font-weight: 400;
|
|
|
+ color: #626a82;
|
|
|
+ line-height: 20px;
|
|
|
+ white-space: normal;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+.warning-detail-body {
|
|
|
+ margin-top: 30px;
|
|
|
+}
|
|
|
+.warning-body-head {
|
|
|
+ background: #fff;
|
|
|
+ border-bottom: 1px solid #f0f4f9;
|
|
|
+ padding: 20px;
|
|
|
+ &-action {
|
|
|
+ float: left;
|
|
|
+ > h3 {
|
|
|
+ line-height: 32px;
|
|
|
+ display: inline-block;
|
|
|
+ vertical-align: middle;
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ margin: 0 30px 0 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ i.icon {
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &-call {
|
|
|
+ margin: 0 10px;
|
|
|
+ }
|
|
|
+ &-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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+.warning-body-main {
|
|
|
+ min-height: 400px;
|
|
|
+ background: #fff;
|
|
|
+}
|
|
|
+</style>
|