|
@@ -0,0 +1,993 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import moment from "moment";
|
|
|
+import VueQrcode from "@chenfengyuan/vue-qrcode";
|
|
|
+import { onMounted, onUnmounted } from "vue";
|
|
|
+import { useTimers } from "@/setups/useTimers";
|
|
|
+// import PulseLoader from "vue-spinner/src/PulseLoader.vue";
|
|
|
+// import {
|
|
|
+// openWS,
|
|
|
+// closeWsWithoutReconnect,
|
|
|
+// } from "@/features/OnlineExam/Examing/ws";
|
|
|
+
|
|
|
+// FIXME: 开启摄像头和websocket库
|
|
|
+
|
|
|
+const emit = defineEmits<{ (e: "on-close"): void }>();
|
|
|
+
|
|
|
+const show = $ref(true);
|
|
|
+// const CLOCK_RATE_TIMEOUT = 10;
|
|
|
+
|
|
|
+let current = $ref(1);
|
|
|
+// @ts-expect-error chrome支持,但还有浏览器不支持,所以没进类型定义
|
|
|
+let downlink = navigator.connection.downlink;
|
|
|
+// @ts-expect-error
|
|
|
+let rtt = navigator.connection.rtt;
|
|
|
+const network = $ref({
|
|
|
+ downlink,
|
|
|
+ downlinkStatus: downlink > 0.5,
|
|
|
+ rrt: rtt,
|
|
|
+ rrtStatus: rtt < 1000,
|
|
|
+});
|
|
|
+
|
|
|
+const time = $ref({
|
|
|
+ currentTimeZone: moment().format("Z"),
|
|
|
+ timeZoneStatus: new Date().getTimezoneOffset() / 60 === -8,
|
|
|
+ clockRateDiff: null,
|
|
|
+ clockRateStateResolved: false,
|
|
|
+ clockRateStatus: false,
|
|
|
+});
|
|
|
+
|
|
|
+const camera = $ref({
|
|
|
+ openCameraResolved: false,
|
|
|
+ openCameraStatus: false,
|
|
|
+ identityStatus: false,
|
|
|
+ identityResolved: false,
|
|
|
+});
|
|
|
+
|
|
|
+const sound = $ref({
|
|
|
+ downloadResolved: false,
|
|
|
+ downloadStatus: false,
|
|
|
+ playedStatusResolved: false,
|
|
|
+ playedStatus: false,
|
|
|
+});
|
|
|
+const wechat = $ref({
|
|
|
+ qrValue: " ",
|
|
|
+ qrScannedResolved: false,
|
|
|
+ qrScanned: false,
|
|
|
+ uploadResolved: false,
|
|
|
+ uploadStatus: false,
|
|
|
+ studentAnswer: null,
|
|
|
+ examRecordDataId: null,
|
|
|
+});
|
|
|
+
|
|
|
+// ...mapState([
|
|
|
+// "questionQrCode",
|
|
|
+// "questionQrCodeScanned",
|
|
|
+// "questionAnswerFileUrl",
|
|
|
+// ]),
|
|
|
+
|
|
|
+// timeCurrent() {
|
|
|
+// return moment(this.nowDate)
|
|
|
+// .utcOffset("+08:00")
|
|
|
+// .format("YYYY-MM-DD HH:mm:ssZZ");
|
|
|
+// },
|
|
|
+
|
|
|
+const step1Status = $computed(() => {
|
|
|
+ return network.downlinkStatus && network.rrtStatus;
|
|
|
+});
|
|
|
+const step2StatusResolved = $computed(() => {
|
|
|
+ return time.clockRateStateResolved;
|
|
|
+});
|
|
|
+const step2Status = $computed(() => {
|
|
|
+ return time.timeZoneStatus && time.clockRateStatus;
|
|
|
+});
|
|
|
+const step3StatusResolved = $computed(() => {
|
|
|
+ return camera.identityResolved && camera.openCameraResolved;
|
|
|
+});
|
|
|
+const step3Status = $computed(() => {
|
|
|
+ return camera.identityStatus && camera.openCameraStatus;
|
|
|
+});
|
|
|
+const step4StatusResolved = $computed(() => {
|
|
|
+ return sound.downloadResolved && sound.playedStatusResolved;
|
|
|
+});
|
|
|
+const step4Status = $computed(() => {
|
|
|
+ return sound.downloadStatus && sound.playedStatus;
|
|
|
+});
|
|
|
+const step5StatusResolved = $computed(() => {
|
|
|
+ return wechat.qrScannedResolved && wechat.uploadResolved;
|
|
|
+});
|
|
|
+const step5Status = $computed(() => {
|
|
|
+ return wechat.qrScanned && wechat.uploadStatus;
|
|
|
+});
|
|
|
+
|
|
|
+// watch(
|
|
|
+// () => questionQrCodeScanned,
|
|
|
+// () => {
|
|
|
+// this.wechat.qrScanned = true;
|
|
|
+// this.wechat.qrScannedResolved = true;
|
|
|
+// }
|
|
|
+// );
|
|
|
+// watch(
|
|
|
+// () => questionAnswerFileUrl,
|
|
|
+// (value) => {
|
|
|
+// const examRecordDataId = this.wechat.examRecordDataId;
|
|
|
+// for (const q of value) {
|
|
|
+// if (!q.saved) {
|
|
|
+// let acknowledgeStatus = "CONFIRMED";
|
|
|
+
|
|
|
+// this.$http
|
|
|
+// .post(
|
|
|
+// "/api/ecs_oe_student/examControl/saveUploadedFileAcknowledgeStatus",
|
|
|
+// {
|
|
|
+// examRecordDataId,
|
|
|
+// filePath: q.fileUrl,
|
|
|
+// order: q.order,
|
|
|
+// acknowledgeStatus,
|
|
|
+// }
|
|
|
+// )
|
|
|
+// .then(() => {
|
|
|
+// this.wechat.studentAnswer = q.fileUrl;
|
|
|
+// this.wechat.uploadResolved = true;
|
|
|
+// this.wechat.uploadStatus = true;
|
|
|
+// q.saved = true;
|
|
|
+// if (acknowledgeStatus === "CONFIRMED")
|
|
|
+// this.$Message.info({
|
|
|
+// content: "小程序作答已更新",
|
|
|
+// duration: 5,
|
|
|
+// closable: true,
|
|
|
+// });
|
|
|
+// })
|
|
|
+// .catch(() => {
|
|
|
+// this.$Message.error({
|
|
|
+// content: "更新小程序答案失败!",
|
|
|
+// duration: 15,
|
|
|
+// closable: true,
|
|
|
+// });
|
|
|
+// });
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+// );
|
|
|
+
|
|
|
+let nowDate: number = $ref(0);
|
|
|
+const { addInterval } = useTimers();
|
|
|
+addInterval(() => (nowDate = Date.now()), 1000);
|
|
|
+console.log(nowDate);
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ // openWS({});
|
|
|
+ // const fetchQR = async () => {
|
|
|
+ // const examRecordDataId = this.$store.state.user.id;
|
|
|
+ // const response = await this.$http.post(
|
|
|
+ // "/api/ecs_oe_student/examControl/getQrCode",
|
|
|
+ // {
|
|
|
+ // examRecordDataId,
|
|
|
+ // order: 1,
|
|
|
+ // transferFileType: "AUDIO",
|
|
|
+ // testEnv: true,
|
|
|
+ // }
|
|
|
+ // );
|
|
|
+ // this.wechat.qrValue = response.data;
|
|
|
+ // const trueExamRecordDataId = decodeURIComponent(response.data).match(
|
|
|
+ // /&examRecordDataId=(\d+)/
|
|
|
+ // )[1];
|
|
|
+ // this.wechat.examRecordDataId = trueExamRecordDataId;
|
|
|
+ // };
|
|
|
+ // if (this.wechat.qrValue) {
|
|
|
+ // this.getQRCodeTimeout = setTimeout(() => {
|
|
|
+ // fetchQR();
|
|
|
+ // }, 3000);
|
|
|
+ // }
|
|
|
+ // let start, end;
|
|
|
+ // fetch("/oe-web/login", { Method: "HEAD" }).then((e) => {
|
|
|
+ // start = moment(e.headers.get("date"));
|
|
|
+ // });
|
|
|
+ // this.checkClockRateTimeout = setTimeout(() => {
|
|
|
+ // fetch("/oe-web/login", { Method: "HEAD" }).then((e) => {
|
|
|
+ // // 可能已经离开这个页面了
|
|
|
+ // if (!this.time) return;
|
|
|
+ // end = moment(e.headers.get("date"));
|
|
|
+ // this.time.clockRateStateResolved = true;
|
|
|
+ // this.time.clockRateDiff = end.diff(start, "seconds") - CLOCK_RATE_TIMEOUT;
|
|
|
+ // this.time.clockRateStatus =
|
|
|
+ // end.diff(start, "seconds") < CLOCK_RATE_TIMEOUT + 2;
|
|
|
+ // });
|
|
|
+ // }, CLOCK_RATE_TIMEOUT * 1000);
|
|
|
+ // await this.openCamera();
|
|
|
+});
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ // clearTimeout(this.checkClockRateTimeout);
|
|
|
+ // clearTimeout(this.getQRCodeTimeout);
|
|
|
+ // if (this.$refs.video.srcObject) {
|
|
|
+ // this.$refs.video.srcObject.getTracks().forEach(function (track) {
|
|
|
+ // track.stop();
|
|
|
+ // });
|
|
|
+ // this.$refs.video.srcObject = null;
|
|
|
+ // }
|
|
|
+ // closeWsWithoutReconnect();
|
|
|
+});
|
|
|
+
|
|
|
+function previous() {
|
|
|
+ if (current > 1) {
|
|
|
+ current -= 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+function next() {
|
|
|
+ if (current < 6) {
|
|
|
+ current += 1;
|
|
|
+ }
|
|
|
+ if (current === 6) {
|
|
|
+ // window._hmt.push([
|
|
|
+ // "_trackEvent",
|
|
|
+ // "环境检测",
|
|
|
+ // `网络: ${this.step1Status}; 时间: ${this.step2Status}; 摄像头: ${this.step3Status}; 声音: ${this.step4Status}; 小程序: ${this.step5Status};`,
|
|
|
+ // ]);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// async function openCamera() {
|
|
|
+// const video = this.$refs.video;
|
|
|
+// if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
|
|
+// try {
|
|
|
+// console.log("启动摄像头");
|
|
|
+// const stream = await navigator.mediaDevices.getUserMedia({
|
|
|
+// video: {
|
|
|
+// facingMode: "user",
|
|
|
+// resizeMode: "crop-and-scale",
|
|
|
+// width: 400,
|
|
|
+// height: 300,
|
|
|
+// },
|
|
|
+// });
|
|
|
+// if (stream) {
|
|
|
+// video.srcObject = stream;
|
|
|
+// try {
|
|
|
+// await video.play();
|
|
|
+// this.camera.openCameraStatus = true;
|
|
|
+// } catch (error) {
|
|
|
+// console.log("摄像头没有正常启用", error);
|
|
|
+// this.$Message.error({
|
|
|
+// content: "摄像头没有正常启用: " + error,
|
|
|
+// duration: 15,
|
|
|
+// closable: true,
|
|
|
+// });
|
|
|
+// window._hmt.push([
|
|
|
+// "_trackEvent",
|
|
|
+// "摄像头框-环境检测",
|
|
|
+// "摄像头状态",
|
|
|
+// "摄像头没有正常启用: " + error,
|
|
|
+// ]);
|
|
|
+// }
|
|
|
+// } else {
|
|
|
+// this.$Message.error({
|
|
|
+// content: "没有可用的视频流",
|
|
|
+// duration: 15,
|
|
|
+// closable: true,
|
|
|
+// });
|
|
|
+// window._hmt.push([
|
|
|
+// "_trackEvent",
|
|
|
+// "摄像头框-环境检测",
|
|
|
+// "摄像头状态",
|
|
|
+// "没有可用的视频流",
|
|
|
+// ]);
|
|
|
+// }
|
|
|
+// } catch (error) {
|
|
|
+// console.log("无法启用摄像头", error);
|
|
|
+// let errMsg;
|
|
|
+// if (error.name || error.message) {
|
|
|
+// errMsg = `${error.name} ${error.message}`;
|
|
|
+// } else {
|
|
|
+// errMsg = error;
|
|
|
+// }
|
|
|
+// this.$Message.error({
|
|
|
+// content: "无法启用摄像头: " + errMsg,
|
|
|
+// duration: 15,
|
|
|
+// closable: true,
|
|
|
+// });
|
|
|
+// window._hmt.push([
|
|
|
+// "_trackEvent",
|
|
|
+// "摄像头框-环境检测",
|
|
|
+// "摄像头状态",
|
|
|
+// "无法启用摄像头" + errMsg,
|
|
|
+// ]);
|
|
|
+// } finally {
|
|
|
+// this.camera.openCameraResolved = true;
|
|
|
+// }
|
|
|
+// } else {
|
|
|
+// this.$Message.error({
|
|
|
+// content: "没有找到可用的摄像头",
|
|
|
+// duration: 15,
|
|
|
+// closable: true,
|
|
|
+// });
|
|
|
+// window._hmt.push([
|
|
|
+// "_trackEvent",
|
|
|
+// "摄像头框-环境检测",
|
|
|
+// "摄像头状态",
|
|
|
+// "没有找到可用的摄像头",
|
|
|
+// ]);
|
|
|
+// }
|
|
|
+// this.camera.openCameraResolved = true;
|
|
|
+// }
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <n-modal
|
|
|
+ title="环境检测"
|
|
|
+ :closable="false"
|
|
|
+ :show="show"
|
|
|
+ preset="card"
|
|
|
+ style="width: 800px"
|
|
|
+ >
|
|
|
+ <div style="max-width: 800px; margin: 30px auto">
|
|
|
+ <n-steps :current="current" size="small">
|
|
|
+ <n-step title="网速"></n-step>
|
|
|
+ <n-step title="时钟"></n-step>
|
|
|
+ <n-step title="摄像头"></n-step>
|
|
|
+ <n-step title="声音"></n-step>
|
|
|
+ <n-step title="微信小程序"></n-step>
|
|
|
+ <n-step title="检测结果"></n-step>
|
|
|
+ </n-steps>
|
|
|
+
|
|
|
+ <div v-if="current === 1" key="1" class="section">
|
|
|
+ <div class="list">
|
|
|
+ <table>
|
|
|
+ <tbody class="list-row">
|
|
|
+ <tr class="list-header qm-primary-strong-text">
|
|
|
+ <td class="first-td">检查项</td>
|
|
|
+ <td>值</td>
|
|
|
+ <td>状态</td>
|
|
|
+ </tr>
|
|
|
+
|
|
|
+ <tr>
|
|
|
+ <td>电脑当前下载速度</td>
|
|
|
+ <td>{{ network.downlink }}Mb</td>
|
|
|
+ <td>
|
|
|
+ <div v-if="network.downlinkStatus">
|
|
|
+ <n-icon class="pass-check" type="md-checkmark" />
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-icon
|
|
|
+ class="fail-cross"
|
|
|
+ title="下载速度不佳"
|
|
|
+ type="md-close"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td>电脑当前网络延迟</td>
|
|
|
+ <td>{{ network.rrt }}毫秒</td>
|
|
|
+ <td>
|
|
|
+ <div v-if="network.rrtStatus">
|
|
|
+ <n-icon class="pass-check" type="md-checkmark" />
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-icon
|
|
|
+ class="fail-cross"
|
|
|
+ title="网络延迟较大"
|
|
|
+ type="md-close"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="current === 2" key="2" class="section">
|
|
|
+ <div class="list">
|
|
|
+ <table>
|
|
|
+ <tbody class="list-row">
|
|
|
+ <tr class="list-header qm-primary-strong-text">
|
|
|
+ <td class="first-td">检查项</td>
|
|
|
+ <td>值</td>
|
|
|
+ <td>状态</td>
|
|
|
+ </tr>
|
|
|
+
|
|
|
+ <!-- <tr>
|
|
|
+ <td>电脑当前时间</td>
|
|
|
+ <td>{{ timeCurrent }} (北京时间)</td>
|
|
|
+ <td></td>
|
|
|
+ </tr> -->
|
|
|
+ <!-- <tr>
|
|
|
+ <td>电脑是否时间准确</td>
|
|
|
+ <td>{{ timeDifference }}</td>
|
|
|
+ </tr> -->
|
|
|
+ <tr>
|
|
|
+ <td>电脑时区</td>
|
|
|
+ <td>{{ time.currentTimeZone }}</td>
|
|
|
+ <td>
|
|
|
+ <div v-if="time.timeZoneStatus">
|
|
|
+ <n-icon class="pass-check" type="md-checkmark" />
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-icon
|
|
|
+ class="fail-cross"
|
|
|
+ title="请将电脑设置为北京时区"
|
|
|
+ type="md-close"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td>电脑时钟频率</td>
|
|
|
+ <td>
|
|
|
+ <div v-if="time.clockRateStateResolved">
|
|
|
+ {{ (time.clockRateDiff ?? 0) > 3 ? "时钟过慢" : "正常" }}
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-spin size="medium" />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ <td>
|
|
|
+ <div v-if="time.clockRateStateResolved">
|
|
|
+ <n-icon
|
|
|
+ v-if="time.clockRateStatus"
|
|
|
+ class="pass-check"
|
|
|
+ type="md-checkmark"
|
|
|
+ />
|
|
|
+ <n-icon
|
|
|
+ v-else
|
|
|
+ class="fail-cross"
|
|
|
+ title="请更换电脑"
|
|
|
+ type="md-close"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-spin size="medium" />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-show="current === 3" key="3" class="section">
|
|
|
+ <div>
|
|
|
+ <div style="display: flex">
|
|
|
+ <video
|
|
|
+ id="video"
|
|
|
+ ref="video"
|
|
|
+ width="400"
|
|
|
+ height="300"
|
|
|
+ autoplay
|
|
|
+ ></video>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-if="camera.openCameraResolved && camera.openCameraStatus"
|
|
|
+ style="margin-left: 50px; margin-top: 100px"
|
|
|
+ >
|
|
|
+ <n-button
|
|
|
+ type="warning"
|
|
|
+ @click="
|
|
|
+ camera.identityResolved = true;
|
|
|
+ camera.identityStatus = false;
|
|
|
+ "
|
|
|
+ >
|
|
|
+ 图像中不是电脑操作者本人
|
|
|
+ </n-button>
|
|
|
+ <div style="width: 30px; height: 30px"></div>
|
|
|
+ <n-button
|
|
|
+ type="primary"
|
|
|
+ @click="
|
|
|
+ camera.identityResolved = true;
|
|
|
+ camera.identityStatus = true;
|
|
|
+ "
|
|
|
+ >
|
|
|
+ 图像中是电脑操作者本人
|
|
|
+ </n-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="list">
|
|
|
+ <table>
|
|
|
+ <tbody class="list-row">
|
|
|
+ <tr class="list-header qm-primary-strong-text">
|
|
|
+ <td class="first-td">检查项</td>
|
|
|
+ <td>值</td>
|
|
|
+ <td>状态</td>
|
|
|
+ </tr>
|
|
|
+
|
|
|
+ <tr>
|
|
|
+ <td>摄像头正常启用</td>
|
|
|
+ <td>
|
|
|
+ <div v-if="camera.openCameraResolved">
|
|
|
+ {{ camera.openCameraStatus ? "正常" : "请检查摄像头" }}
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-spin size="medium" />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ <td>
|
|
|
+ <div v-if="camera.openCameraResolved">
|
|
|
+ <div v-if="camera.openCameraStatus">
|
|
|
+ <n-icon class="pass-check" type="md-checkmark" />
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-icon
|
|
|
+ class="fail-cross"
|
|
|
+ title="请检查摄像头"
|
|
|
+ type="md-close"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-spin size="medium" />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td>视频显示的是电脑操作者本人</td>
|
|
|
+ <td>
|
|
|
+ <div
|
|
|
+ v-if="
|
|
|
+ (camera.openCameraResolved && !camera.openCameraStatus) ||
|
|
|
+ camera.identityResolved
|
|
|
+ "
|
|
|
+ >
|
|
|
+ {{ camera.identityStatus ? "正常" : "请检查摄像头" }}
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-spin size="medium" />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ <td>
|
|
|
+ <div
|
|
|
+ v-if="
|
|
|
+ (camera.openCameraResolved && !camera.openCameraStatus) ||
|
|
|
+ camera.identityResolved
|
|
|
+ "
|
|
|
+ >
|
|
|
+ <div v-if="camera.identityStatus">
|
|
|
+ <n-icon class="pass-check" type="md-checkmark" />
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-icon
|
|
|
+ class="fail-cross"
|
|
|
+ title="请检查摄像头"
|
|
|
+ type="md-close"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-spin size="medium" />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-show="current === 4"
|
|
|
+ key="4"
|
|
|
+ class="section"
|
|
|
+ style="text-align: center"
|
|
|
+ >
|
|
|
+ <div>
|
|
|
+ <div style="display: flex; margin-bottom: 30px">
|
|
|
+ <audio
|
|
|
+ src="https://ecs-static.qmth.com.cn/check-audio.mp3"
|
|
|
+ controls
|
|
|
+ nodownload
|
|
|
+ @loadeddata="
|
|
|
+ sound.downloadResolved = true;
|
|
|
+ sound.downloadStatus = true;
|
|
|
+ "
|
|
|
+ @error="
|
|
|
+ sound.downloadResolved = true;
|
|
|
+ sound.downloadStatus = false;
|
|
|
+ "
|
|
|
+ />
|
|
|
+
|
|
|
+ <div style="margin-left: 30px; display: flex">
|
|
|
+ <n-button
|
|
|
+ type="warning"
|
|
|
+ title="或者听不到声音"
|
|
|
+ @click="
|
|
|
+ sound.playedStatusResolved = true;
|
|
|
+ sound.playedStatus = false;
|
|
|
+ "
|
|
|
+ >
|
|
|
+ 不能播放声音
|
|
|
+ </n-button>
|
|
|
+ <div style="width: 30px; height: 30px"></div>
|
|
|
+ <n-button
|
|
|
+ type="primary"
|
|
|
+ @click="
|
|
|
+ sound.playedStatusResolved = true;
|
|
|
+ sound.playedStatus = true;
|
|
|
+ "
|
|
|
+ >
|
|
|
+ 能够播放声音
|
|
|
+ </n-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="list">
|
|
|
+ <table>
|
|
|
+ <tbody class="list-row">
|
|
|
+ <tr class="list-header qm-primary-strong-text">
|
|
|
+ <td class="first-td">检查项</td>
|
|
|
+ <td>值</td>
|
|
|
+ <td>状态</td>
|
|
|
+ </tr>
|
|
|
+
|
|
|
+ <tr>
|
|
|
+ <td>文件下载</td>
|
|
|
+ <td>
|
|
|
+ <div v-if="sound.downloadResolved">
|
|
|
+ {{ sound.downloadStatus ? "正常" : "出错" }}
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-spin size="medium" />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ <td>
|
|
|
+ <div v-if="sound.downloadResolved">
|
|
|
+ <div v-if="sound.downloadStatus">
|
|
|
+ <n-icon class="pass-check" type="md-checkmark" />
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-icon
|
|
|
+ class="fail-cross"
|
|
|
+ title="下载出错"
|
|
|
+ type="md-close"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-spin size="medium" />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td>声音播放</td>
|
|
|
+ <td>
|
|
|
+ <div v-if="sound.playedStatusResolved">
|
|
|
+ {{ sound.playedStatus ? "正常" : "出错" }}
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-spin size="medium" />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ <td>
|
|
|
+ <div v-if="sound.playedStatusResolved">
|
|
|
+ <div v-if="sound.playedStatus">
|
|
|
+ <n-icon class="pass-check" type="md-checkmark" />
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-icon
|
|
|
+ class="fail-cross"
|
|
|
+ title="不能播放声音"
|
|
|
+ type="md-close"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-spin size="medium" />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-show="current === 5" key="5" class="section">
|
|
|
+ <div>
|
|
|
+ <div style="display: flex">
|
|
|
+ <div>
|
|
|
+ <div v-if="wechat.qrValue" style="display: flex">
|
|
|
+ <VueQrcode
|
|
|
+ :value="wechat.qrValue"
|
|
|
+ :options="{ width: 200 }"
|
|
|
+ style="margin-left: -10px"
|
|
|
+ ></VueQrcode>
|
|
|
+ <div style="margin-top: 10px">
|
|
|
+ <div style="font-size: 30px">
|
|
|
+ 请使用<span style="font-weight: 900; color: #1e90ff"
|
|
|
+ >微信</span
|
|
|
+ >扫描二维码后,在微信小程序上录音,并上传文件。
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-if="wechat.qrScanned"
|
|
|
+ style="margin-top: 30px; font-size: 30px"
|
|
|
+ >
|
|
|
+ {{ wechat.studentAnswer ? "已上传" : "已扫描" }}
|
|
|
+ <n-icon type="md-checkmark" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else>正在获取二维码...</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ class="audio-answer audio-answer-line-height"
|
|
|
+ style="margin-top: 20px; text-align: left"
|
|
|
+ >
|
|
|
+ <span class="audio-answer-line-height">上传文件:</span>
|
|
|
+ <audio
|
|
|
+ v-if="wechat.studentAnswer"
|
|
|
+ class="audio-answer-line-height"
|
|
|
+ controls
|
|
|
+ controlsList="nodownload"
|
|
|
+ :src="wechat.studentAnswer"
|
|
|
+ />
|
|
|
+ <span v-else class="audio-answer-line-height">未上传文件</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div style="margin-top: 30px; display: flex; margin-bottom: 30px">
|
|
|
+ <n-button
|
|
|
+ type="warning"
|
|
|
+ title="扫码不成功"
|
|
|
+ @click="
|
|
|
+ wechat.qrScannedResolved = true;
|
|
|
+ wechat.qrScanned = false;
|
|
|
+ "
|
|
|
+ >
|
|
|
+ 不能正确扫描二维码
|
|
|
+ </n-button>
|
|
|
+ <div style="width: 30px; height: 30px"></div>
|
|
|
+ <n-button
|
|
|
+ type="warning"
|
|
|
+ title="上传不成功"
|
|
|
+ @click="
|
|
|
+ wechat.uploadResolved = true;
|
|
|
+ wechat.uploadStatus = false;
|
|
|
+ "
|
|
|
+ >
|
|
|
+ 上传不成功
|
|
|
+ </n-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="list">
|
|
|
+ <table>
|
|
|
+ <tbody class="list-row">
|
|
|
+ <tr class="list-header qm-primary-strong-text">
|
|
|
+ <td class="first-td">检查项</td>
|
|
|
+ <td>值</td>
|
|
|
+ <td>状态</td>
|
|
|
+ </tr>
|
|
|
+
|
|
|
+ <tr>
|
|
|
+ <td>扫描二维码</td>
|
|
|
+ <td>
|
|
|
+ <div v-if="wechat.qrScannedResolved">
|
|
|
+ {{ wechat.qrScanned ? "正常" : "出错" }}
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-spin size="medium" />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ <td>
|
|
|
+ <div v-if="wechat.qrScannedResolved">
|
|
|
+ <div v-if="wechat.qrScanned">
|
|
|
+ <n-icon class="pass-check" type="md-checkmark" />
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-icon
|
|
|
+ class="fail-cross"
|
|
|
+ title="扫描出错"
|
|
|
+ type="md-close"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-spin size="medium" />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+
|
|
|
+ <tr>
|
|
|
+ <td>上传录音</td>
|
|
|
+ <td>
|
|
|
+ <div v-if="wechat.uploadResolved">
|
|
|
+ {{ wechat.uploadStatus ? "正常" : "出错" }}
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-spin size="medium" />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ <td>
|
|
|
+ <div v-if="wechat.uploadResolved">
|
|
|
+ <div v-if="wechat.uploadStatus">
|
|
|
+ <n-icon class="pass-check" type="md-checkmark" />
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-icon class="fail-cross" type="md-close" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-spin size="medium" />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-show="current === 6" key="6" class="section">
|
|
|
+ <div class="list">
|
|
|
+ <table>
|
|
|
+ <tbody class="list-row">
|
|
|
+ <tr class="list-header qm-primary-strong-text">
|
|
|
+ <td class="first-td">检查项</td>
|
|
|
+ <td>结果</td>
|
|
|
+ </tr>
|
|
|
+
|
|
|
+ <tr>
|
|
|
+ <td>网速</td>
|
|
|
+ <td>
|
|
|
+ <div v-if="step1Status">
|
|
|
+ <n-icon class="pass-check" type="md-checkmark" />
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-icon class="fail-cross" type="md-close" />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td>时钟</td>
|
|
|
+ <td>
|
|
|
+ <div v-if="step2StatusResolved">
|
|
|
+ <div v-if="step2Status">
|
|
|
+ <n-icon class="pass-check" type="md-checkmark" />
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-icon class="fail-cross" type="md-close" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-spin size="medium" />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td>摄像头</td>
|
|
|
+ <td>
|
|
|
+ <div v-if="step3StatusResolved">
|
|
|
+ <div v-if="step3Status">
|
|
|
+ <n-icon class="pass-check" type="md-checkmark" />
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-icon class="fail-cross" type="md-close" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else class="fail-cross">
|
|
|
+ 请在“摄像头”步骤进行人工确认!
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td>声音</td>
|
|
|
+ <td>
|
|
|
+ <div v-if="step4StatusResolved">
|
|
|
+ <div v-if="step4Status">
|
|
|
+ <n-icon class="pass-check" type="md-checkmark" />
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-icon class="fail-cross" type="md-close" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else class="fail-cross">
|
|
|
+ 请在“声音”步骤进行人工确认!
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td>微信小程序</td>
|
|
|
+ <td>
|
|
|
+ <div v-if="step5StatusResolved">
|
|
|
+ <div v-if="step5Status">
|
|
|
+ <n-icon class="pass-check" type="md-checkmark" />
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <n-icon class="fail-cross" type="md-close" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else class="fail-cross">
|
|
|
+ 请在“微信小程序”步骤进行人工确认!
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div style="color: red">
|
|
|
+ <div v-if="!step1Status" key="a">
|
|
|
+ 检查网络是否连接,路由器是否正常工作。
|
|
|
+ </div>
|
|
|
+ <div v-if="step2StatusResolved && !step2Status" key="b">
|
|
|
+ 请调整电脑时间和社区与北京时间一致。
|
|
|
+ </div>
|
|
|
+ <div v-if="step3StatusResolved && !step3Status" key="c">
|
|
|
+ 请确认摄像头连接线正常,能正常工作,关闭杀毒软件、关闭摄像头滤镜软件;请确认您的电脑是否为双摄摄像头,启用的摄像头是否正确。
|
|
|
+ </div>
|
|
|
+ <div v-if="step4StatusResolved && !step4Status" key="d">
|
|
|
+ 请确认音箱连接正常,调整音量开关及大小。
|
|
|
+ </div>
|
|
|
+ <div v-if="step5StatusResolved && !step5Status" key="e">
|
|
|
+ 请确认微信已登录并连接网络。
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-if="
|
|
|
+ !step1Status ||
|
|
|
+ (step2StatusResolved && !step2Status) ||
|
|
|
+ (step3StatusResolved && !step3Status) ||
|
|
|
+ (step4StatusResolved && !step4Status) ||
|
|
|
+ (step5StatusResolved && !step5Status)
|
|
|
+ "
|
|
|
+ key="f"
|
|
|
+ >
|
|
|
+ 请按提示检查并调试,调试后可再次进行环境检测。
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div style="margin-top: 30px; text-align: center">
|
|
|
+ <n-button type="primary" :disabled="current === 1" @click="previous">
|
|
|
+ 上一步
|
|
|
+ </n-button>
|
|
|
+ <div style="width: 30px; height: 1px; display: inline-block"></div>
|
|
|
+ <n-button type="primary" :disabled="current === 6" @click="next">
|
|
|
+ 下一步
|
|
|
+ </n-button>
|
|
|
+ <div style="width: 30px; height: 1px; display: inline-block"></div>
|
|
|
+ <n-button
|
|
|
+ v-if="current === 6"
|
|
|
+ key="xxx"
|
|
|
+ type="primary"
|
|
|
+ @click="() => emit('on-close')"
|
|
|
+ >
|
|
|
+ 进入考试
|
|
|
+ </n-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </n-modal>
|
|
|
+</template>
|
|
|
+<style scoped>
|
|
|
+.section {
|
|
|
+ margin-top: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.list {
|
|
|
+ border: 1px solid #eeeeee;
|
|
|
+ border-radius: 6px;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.list table {
|
|
|
+ width: 100%;
|
|
|
+ border-collapse: collapse !important;
|
|
|
+ border-spacing: 0;
|
|
|
+}
|
|
|
+.list td {
|
|
|
+ border: 1px solid #eeeeee;
|
|
|
+ border-radius: 6px;
|
|
|
+ border-collapse: separate !important;
|
|
|
+ padding: 10px;
|
|
|
+}
|
|
|
+.list .first-td {
|
|
|
+ width: 50%;
|
|
|
+}
|
|
|
+
|
|
|
+.pass-check {
|
|
|
+ font-size: 20px;
|
|
|
+ color: green;
|
|
|
+}
|
|
|
+
|
|
|
+.fail-cross {
|
|
|
+ font-size: 20px;
|
|
|
+ color: red;
|
|
|
+}
|
|
|
+</style>
|