|
@@ -0,0 +1,324 @@
|
|
|
+<template>
|
|
|
+ <div class="student-track-record">
|
|
|
+ <div class="student-track-head box-justify">
|
|
|
+ <h2 class="student-track-title">轨迹回放</h2>
|
|
|
+ <el-button size="mini" icon="el-icon-arrow-left" @click="goBack"
|
|
|
+ >返回列表</el-button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="student-track-info box-justify">
|
|
|
+ <div class="student-track-info-left">
|
|
|
+ <i class="icon icon-theme"></i>{{ info.examName }}
|
|
|
+ </div>
|
|
|
+ <div class="student-track-info-list">
|
|
|
+ <span class="student-track-info-item">
|
|
|
+ <span>姓名:</span>
|
|
|
+ <span class="color-primary">{{ info.name }}</span>
|
|
|
+ </span>
|
|
|
+ <span class="student-track-info-item">
|
|
|
+ <span>证件号:</span>
|
|
|
+ <span class="color-primary">{{ info.identity }}</span>
|
|
|
+ </span>
|
|
|
+ <span class="student-track-info-item">
|
|
|
+ <span>科目(代码):</span>
|
|
|
+ <span class="color-primary"
|
|
|
+ >{{ info.courseName }}({{ info.courseCode }})</span
|
|
|
+ >
|
|
|
+ </span>
|
|
|
+ <span class="student-track-info-item">
|
|
|
+ <span>考试时间:</span>
|
|
|
+ <span class="color-primary">{{
|
|
|
+ info.firstStartTime | datetimeFilter
|
|
|
+ }}</span>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="student-track-body">
|
|
|
+ <el-row :gutter="40" type="flex">
|
|
|
+ <el-col :span="12">
|
|
|
+ <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 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>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form inline>
|
|
|
+ <el-form-item>
|
|
|
+ <el-select v-model="filter.monitorRecord" placeholder="视频源">
|
|
|
+ <el-option
|
|
|
+ v-for="item in monitorRecordList"
|
|
|
+ :key="item.code"
|
|
|
+ :value="item.code"
|
|
|
+ :label="item.name"
|
|
|
+ >
|
|
|
+ </el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" @click="toPage(1)">查询</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <el-table :data="videoRocordList" border>
|
|
|
+ <el-table-column prop="startTime" label="录制起点">
|
|
|
+ <span slot-scope="scope"
|
|
|
+ >{{ scope.row.startTime | datetimeFilter }}
|
|
|
+ </span>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="endTime" label="录制终点">
|
|
|
+ <span slot-scope="scope"
|
|
|
+ >{{ scope.row.endTime | datetimeFilter }}
|
|
|
+ </span>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="100">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-button
|
|
|
+ class="btn-table-icon"
|
|
|
+ type="text"
|
|
|
+ @click="toDetail(scope.row)"
|
|
|
+ >视频回放</el-button
|
|
|
+ >
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- image-preview -->
|
|
|
+ <simple-image-preview
|
|
|
+ :cur-image="curImage"
|
|
|
+ ref="SimpleImagePreview"
|
|
|
+ ></simple-image-preview>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { VIDEO_SOURCE_TYPE } from "@/constant/constants";
|
|
|
+import { searchStudentTrackRecord } from "@/api/examwork-student";
|
|
|
+import SimpleImagePreview from "@/components/imagePreview/SimpleImagePreview";
|
|
|
+import {
|
|
|
+ formatDate,
|
|
|
+ timeNumberToText,
|
|
|
+ objTypeOf,
|
|
|
+ objAssign,
|
|
|
+} from "@/utils/utils";
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: "StudentTrackRecord",
|
|
|
+ components: { SimpleImagePreview },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ filter: {
|
|
|
+ examRecordId: this.$route.params.examRecordId,
|
|
|
+ monitorRecord: "",
|
|
|
+ log: true,
|
|
|
+ },
|
|
|
+ info: {
|
|
|
+ examId: "",
|
|
|
+ examName: "",
|
|
|
+ examStudentId: "",
|
|
|
+ examRecordId: "",
|
|
|
+ courseCode: "",
|
|
|
+ courseName: "",
|
|
|
+ identity: "",
|
|
|
+ name: "",
|
|
|
+ firstStartTime: null,
|
|
|
+ },
|
|
|
+ VIDEO_SOURCE_TYPE,
|
|
|
+ monitorRecordList: [],
|
|
|
+ videoRocordList: [],
|
|
|
+ current: 1,
|
|
|
+ total: 0,
|
|
|
+ size: 24,
|
|
|
+ examStudentLogList: [],
|
|
|
+ curImage: { imgSrc: "" },
|
|
|
+ };
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ const studentTrackMonitorRecord = window.sessionStorage.getItem(
|
|
|
+ "studentTrackMonitorRecord"
|
|
|
+ );
|
|
|
+ if (!studentTrackMonitorRecord) {
|
|
|
+ this.$message.error("数据丢失,请退出本页");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.monitorRecordList = studentTrackMonitorRecord
|
|
|
+ .split(",")
|
|
|
+ .map((item) => {
|
|
|
+ return {
|
|
|
+ name: VIDEO_SOURCE_TYPE[item],
|
|
|
+ code: item,
|
|
|
+ };
|
|
|
+ });
|
|
|
+ this.filter.monitorRecord = this.monitorRecordList[0].code;
|
|
|
+ this.initData();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ async initData() {
|
|
|
+ await this.getList();
|
|
|
+ this.filter.log = false;
|
|
|
+ },
|
|
|
+ async getList() {
|
|
|
+ const datas = {
|
|
|
+ ...this.filter,
|
|
|
+ pageNumber: this.current,
|
|
|
+ pageSize: this.size,
|
|
|
+ };
|
|
|
+
|
|
|
+ const res = await searchStudentTrackRecord(datas);
|
|
|
+ this.info = objAssign(this.info, res.data.data);
|
|
|
+
|
|
|
+ const {
|
|
|
+ records,
|
|
|
+ total,
|
|
|
+ } = res.data.data.teStudentExamRecordVideoMessageIPage;
|
|
|
+ this.videoRocordList = records;
|
|
|
+ this.total = total;
|
|
|
+
|
|
|
+ if (datas.log) {
|
|
|
+ this.examStudentLogList = this.parseStudentLogs(
|
|
|
+ res.data.data.teExamStudentLogList
|
|
|
+ );
|
|
|
+ }
|
|
|
+ },
|
|
|
+ toPage(page) {
|
|
|
+ this.current = page;
|
|
|
+ this.getList();
|
|
|
+ },
|
|
|
+ parseStudentLogs(examStudentLogList) {
|
|
|
+ const statusTypes = {
|
|
|
+ common: [
|
|
|
+ "FIRST_START",
|
|
|
+ "RESUME_START",
|
|
|
+ "IN_PROCESS",
|
|
|
+ "PREPARE",
|
|
|
+ "ANSWERING",
|
|
|
+ "BREAK_OFF",
|
|
|
+ "RESUME_PREPARE",
|
|
|
+ "FINISHED",
|
|
|
+ "FIRST_PREPARE",
|
|
|
+ ],
|
|
|
+ warning: [
|
|
|
+ "FACE_COUNT_ERROR",
|
|
|
+ "FACE_COMPARE_ERROR",
|
|
|
+ "EYE_CLOSE_ERROR",
|
|
|
+ "LIVENESS_ACTION_ERROR",
|
|
|
+ "REALNESS",
|
|
|
+ "NONE",
|
|
|
+ // exception:
|
|
|
+ "NET_TIME_OUT",
|
|
|
+ "MACHING_STOP",
|
|
|
+ "NET_TIME_BREAK",
|
|
|
+ "SOFTWARE_STOP",
|
|
|
+ "POWER_CUT",
|
|
|
+ "BREACH_HANDLE",
|
|
|
+ "BREACH_REVOKE",
|
|
|
+ ],
|
|
|
+ };
|
|
|
+ const transformInfo = {
|
|
|
+ FIRST_START: "身份识别",
|
|
|
+ ANSWERING: "进入考试",
|
|
|
+ RESUME_START: "身份识别",
|
|
|
+ };
|
|
|
+ let statusTypeMap = {};
|
|
|
+ Object.keys(statusTypes).map((k) => {
|
|
|
+ statusTypes[k].map((item) => {
|
|
|
+ statusTypeMap[item] = k;
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ const logs = examStudentLogList.map((item) => {
|
|
|
+ let info = { ...item };
|
|
|
+ info.endTime = formatDate("HH:mm:ss", new Date(info.createTime));
|
|
|
+ info.viewType = statusTypeMap[info.type] || "common";
|
|
|
+ const content = info.info.split(/【|】/);
|
|
|
+ if (content.length === 3) {
|
|
|
+ info.title = content[1];
|
|
|
+ info.desc = content[2];
|
|
|
+ } else {
|
|
|
+ info.title = transformInfo[info.type] || content[0];
|
|
|
+ }
|
|
|
+ if (info.remark && info.remark.includes('{"')) {
|
|
|
+ info.remark = JSON.parse(info.remark);
|
|
|
+ if (info.remark["MIN_CREATE_TIME"]) {
|
|
|
+ info.startTime = formatDate(
|
|
|
+ "HH:mm:ss",
|
|
|
+ new Date(info.remark["MIN_CREATE_TIME"])
|
|
|
+ );
|
|
|
+ info.durationTime = timeNumberToText(
|
|
|
+ info.createTime - info.remark["MIN_CREATE_TIME"]
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ let photos = [];
|
|
|
+ Object.keys(info.remark).map((key) => {
|
|
|
+ if (key.includes("PHOTO")) {
|
|
|
+ const kPhotos =
|
|
|
+ objTypeOf(info.remark[key]) === "array"
|
|
|
+ ? info.remark[key]
|
|
|
+ : [info.remark[key]];
|
|
|
+ photos = [...photos, ...kPhotos];
|
|
|
+ }
|
|
|
+ });
|
|
|
+ info.photos = photos;
|
|
|
+ } else if (info.updateTime) {
|
|
|
+ info.startTime = formatDate("HH:mm:ss", new Date(info.createTime));
|
|
|
+ info.endTime = formatDate("HH:mm:ss", new Date(info.updateTime));
|
|
|
+ info.durationTime = timeNumberToText(
|
|
|
+ info.updateTime - info.createTime
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return info;
|
|
|
+ });
|
|
|
+ return logs;
|
|
|
+ },
|
|
|
+ toViewImg(photo) {
|
|
|
+ this.curImage = { imgSrc: photo };
|
|
|
+ this.$refs.SimpleImagePreview.open();
|
|
|
+ },
|
|
|
+ toDetail(row) {
|
|
|
+ console.log(row);
|
|
|
+ },
|
|
|
+ goBack() {
|
|
|
+ window.history.go(-1);
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|