|
|
@@ -1,5 +1,11 @@
|
|
|
<template>
|
|
|
- <div>
|
|
|
+ <div
|
|
|
+ tabIndex="-1"
|
|
|
+ @blur="monitor()"
|
|
|
+ ref="focusOn"
|
|
|
+ @focus="stopTimeCount()"
|
|
|
+ @resize="isFullScreen()"
|
|
|
+ >
|
|
|
<v-navigation-drawer app permanent right class="elevation-5">
|
|
|
<div
|
|
|
class="flex-column d-flex justify-space-between"
|
|
|
@@ -76,16 +82,41 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div style="height: 20vh;width: 80%" class="align-self-center">
|
|
|
+ <div class="d-flex justify-center">
|
|
|
+ <video ref="videos" v-show="true" width="150" height="120"></video>
|
|
|
+ <video ref="video" v-show="false" width="640" height="480"></video>
|
|
|
+ <el-upload
|
|
|
+ class="avatar-uploader"
|
|
|
+ action
|
|
|
+ :show-file-list="false"
|
|
|
+ :with-credentials="false"
|
|
|
+ :http-request="selectPicUpload"
|
|
|
+ >
|
|
|
+ <canvas
|
|
|
+ name="file"
|
|
|
+ v-show="false"
|
|
|
+ width="640"
|
|
|
+ height="480"
|
|
|
+ ref="canvas"
|
|
|
+ ></canvas>
|
|
|
+ </el-upload>
|
|
|
<!-- 预留给反作弊-->
|
|
|
</div>
|
|
|
<div class="flex-shrink-0">
|
|
|
<div class="pa-2 px-6 text--secondary justify-space-between d-flex">
|
|
|
<v-dialog v-model="exitDialog" persistent max-width="290">
|
|
|
<template v-slot:activator="{ on, attrs }">
|
|
|
- <v-btn color="error" v-bind="attrs" v-on="on">退出评测</v-btn>
|
|
|
+ <v-btn
|
|
|
+ color="error"
|
|
|
+ v-bind="attrs"
|
|
|
+ v-on="on"
|
|
|
+ @click="changeFlagTo0"
|
|
|
+ @mousedown="preventBlur($event)"
|
|
|
+ >
|
|
|
+ 退出评测
|
|
|
+ </v-btn>
|
|
|
</template>
|
|
|
- <v-card>
|
|
|
+ <v-card @mousedown="preventBlur($event)">
|
|
|
<v-card-title class="headline">
|
|
|
确认离开评测?
|
|
|
</v-card-title>
|
|
|
@@ -95,11 +126,21 @@
|
|
|
<v-btn
|
|
|
color="success darken-1"
|
|
|
text
|
|
|
- @click="exitDialog = false"
|
|
|
+ @click="
|
|
|
+ exitDialog = false
|
|
|
+ changeFlagTo1()
|
|
|
+ focusAuto()
|
|
|
+ "
|
|
|
+ @mousedown="preventBlur($event)"
|
|
|
>
|
|
|
否
|
|
|
</v-btn>
|
|
|
- <v-btn color="error darken-1" text @click="leaveExam">
|
|
|
+ <v-btn
|
|
|
+ color="error darken-1"
|
|
|
+ text
|
|
|
+ @click="leaveExam"
|
|
|
+ @mousedown="preventBlur($event)"
|
|
|
+ >
|
|
|
是
|
|
|
</v-btn>
|
|
|
</v-card-actions>
|
|
|
@@ -107,9 +148,17 @@
|
|
|
</v-dialog>
|
|
|
<v-dialog v-model="submitDialog" persistent max-width="290">
|
|
|
<template v-slot:activator="{ on, attrs }">
|
|
|
- <v-btn color="success" v-bind="attrs" v-on="on">提交试卷</v-btn>
|
|
|
+ <v-btn
|
|
|
+ color="success"
|
|
|
+ v-bind="attrs"
|
|
|
+ v-on="on"
|
|
|
+ @click="changeFlagTo0"
|
|
|
+ @mousedown="preventBlur($event)"
|
|
|
+ >
|
|
|
+ 提交试卷
|
|
|
+ </v-btn>
|
|
|
</template>
|
|
|
- <v-card>
|
|
|
+ <v-card @mousedown="preventBlur($event)">
|
|
|
<v-card-title class="headline">
|
|
|
确认提交试卷?
|
|
|
</v-card-title>
|
|
|
@@ -119,11 +168,21 @@
|
|
|
<v-btn
|
|
|
color="success darken-1"
|
|
|
text
|
|
|
- @click="submitDialog = false"
|
|
|
+ @click="
|
|
|
+ submitDialog = false
|
|
|
+ changeFlagTo1()
|
|
|
+ focusAuto()
|
|
|
+ "
|
|
|
+ @mousedown="preventBlur($event)"
|
|
|
>
|
|
|
否
|
|
|
</v-btn>
|
|
|
- <v-btn color="error darken-1" text @click="submitAnswer">
|
|
|
+ <v-btn
|
|
|
+ color="error darken-1"
|
|
|
+ text
|
|
|
+ @click="submitAnswer"
|
|
|
+ @mousedown="preventBlur($event)"
|
|
|
+ >
|
|
|
是
|
|
|
</v-btn>
|
|
|
</v-card-actions>
|
|
|
@@ -146,6 +205,7 @@
|
|
|
:total-question-num="codeQuestionNum"
|
|
|
@nextQuestion="nextQuestion"
|
|
|
@prevQuestion="prevQuestion"
|
|
|
+ @mousedown="preventBlur($event)"
|
|
|
>
|
|
|
<template #index>
|
|
|
<v-chip class="align-self-center"
|
|
|
@@ -194,7 +254,11 @@
|
|
|
v-if="currentQuestionIndex >= 0"
|
|
|
class="d-flex justify-center px-10 py-10"
|
|
|
>
|
|
|
- <v-btn color="info" @click="nextQuestion" :disabled="fetchingStem"
|
|
|
+ <v-btn
|
|
|
+ color="info"
|
|
|
+ @click="nextQuestion"
|
|
|
+ :disabled="fetchingStem"
|
|
|
+ @mousedown="preventBlur($event)"
|
|
|
>下一题</v-btn
|
|
|
>
|
|
|
</div>
|
|
|
@@ -214,6 +278,78 @@
|
|
|
提交失败,请重试
|
|
|
</v-snackbar>
|
|
|
<v-snackbar v-model="endTimeChanged" absolute>评测已延长</v-snackbar>
|
|
|
+ <template>
|
|
|
+ <div v-if="visible">
|
|
|
+ <v-dialog v-model="visible" persistent max-width="390">
|
|
|
+ <v-card>
|
|
|
+ <v-card-title class="headline"> </v-card-title>
|
|
|
+ <v-card-text
|
|
|
+ >您已切屏或改变页面大小,违规三次或持续十秒后将被判定为作弊!请立刻点击确认按钮返回考试,否则15s后也将按作弊论处。</v-card-text
|
|
|
+ >
|
|
|
+ <v-card-actions>
|
|
|
+ <v-spacer></v-spacer>
|
|
|
+ <v-btn
|
|
|
+ color="success darken-1"
|
|
|
+ text
|
|
|
+ @click="
|
|
|
+ changeVisibleFalse()
|
|
|
+ changeFlagTo1()
|
|
|
+ stopTimeCount()
|
|
|
+ focusAuto()
|
|
|
+ "
|
|
|
+ @mousedown="preventBlur($event)"
|
|
|
+ >
|
|
|
+ 确认
|
|
|
+ </v-btn>
|
|
|
+ </v-card-actions>
|
|
|
+ </v-card>
|
|
|
+ </v-dialog>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template>
|
|
|
+ <div>
|
|
|
+ <v-dialog v-model="visible1" persistent max-width="390">
|
|
|
+ <v-card>
|
|
|
+ <v-card-title class="headline"> </v-card-title>
|
|
|
+ <v-card-text>您已违反考试纪律,您已被认定为作弊</v-card-text>
|
|
|
+ <v-card-actions>
|
|
|
+ <v-spacer></v-spacer>
|
|
|
+ <v-btn
|
|
|
+ color="success darken-1"
|
|
|
+ text
|
|
|
+ @click="leaveExam()"
|
|
|
+ @mousedown="preventBlur($event)"
|
|
|
+ >
|
|
|
+ 确认
|
|
|
+ </v-btn>
|
|
|
+ </v-card-actions>
|
|
|
+ </v-card>
|
|
|
+ </v-dialog>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template>
|
|
|
+ <div>
|
|
|
+ <v-dialog v-model="visible2" persistent max-width="390">
|
|
|
+ <v-card>
|
|
|
+ <v-card-title class="headline"> </v-card-title>
|
|
|
+ <v-card-text
|
|
|
+ >您已切换ip地址,违反考试纪律,已被强制退出考试!</v-card-text
|
|
|
+ >
|
|
|
+ <v-card-actions>
|
|
|
+ <v-spacer></v-spacer>
|
|
|
+ <v-btn
|
|
|
+ color="success darken-1"
|
|
|
+ text
|
|
|
+ @click="leaveExam()"
|
|
|
+ @mousedown="preventBlur($event)"
|
|
|
+ >
|
|
|
+ 确认
|
|
|
+ </v-btn>
|
|
|
+ </v-card-actions>
|
|
|
+ </v-card>
|
|
|
+ </v-dialog>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
@@ -238,6 +374,9 @@ import { getQuestionStem } from '@/api/question'
|
|
|
import { objectiveJudge } from '@/api/judge'
|
|
|
import dayjs from 'dayjs'
|
|
|
import { TIME_FORMAT } from '@/utils/common'
|
|
|
+import InputFocus from '@/views/exam/components/InputFocus.vue'
|
|
|
+import axios from 'axios'
|
|
|
+import qs from 'qs'
|
|
|
|
|
|
export default Vue.extend({
|
|
|
name: 'ExamPane',
|
|
|
@@ -274,7 +413,23 @@ export default Vue.extend({
|
|
|
objectiveQuestionNum: 0,
|
|
|
codeQuestionNum: 0,
|
|
|
codeQuestionsReady: false,
|
|
|
- endTimeChanged: false
|
|
|
+ endTimeChanged: false,
|
|
|
+
|
|
|
+ time: 0,
|
|
|
+ allowNum: 3,
|
|
|
+ interval: 0,
|
|
|
+ flag: 1,
|
|
|
+ vCardFlag: 1,
|
|
|
+ visible: false,
|
|
|
+ visible1: false,
|
|
|
+ visible2: false,
|
|
|
+ isCameraFlag: true,
|
|
|
+ photoNums: 0,
|
|
|
+ notMatchNum: 0,
|
|
|
+ hrep: 'http://localhost:8080/detect',
|
|
|
+ gethrep: 'http://localhost:8080/matching',
|
|
|
+ ipSave: 'http://localhost:8080/saveIP',
|
|
|
+ ipDetect: 'http://localhost:8080/detectIP'
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
@@ -286,6 +441,294 @@ export default Vue.extend({
|
|
|
}
|
|
|
},
|
|
|
methods: {
|
|
|
+ test() {
|
|
|
+ alert(this.allowNum)
|
|
|
+ },
|
|
|
+
|
|
|
+ getUserIp() {
|
|
|
+ let ip = localStorage.getItem('Ip')
|
|
|
+ let id = this.$store.getters.getId
|
|
|
+ ip = id + '~~' + ip
|
|
|
+ console.log('ipget')
|
|
|
+ axios
|
|
|
+ .get(this.ipDetect, {
|
|
|
+ params: { ip },
|
|
|
+ withCredentials: false
|
|
|
+ })
|
|
|
+ .then((res) => {
|
|
|
+ console.log(res)
|
|
|
+ if (res.data == 0) {
|
|
|
+ this.visible2 = true
|
|
|
+ }
|
|
|
+ //this.imageUrl = res.data.url;
|
|
|
+ // this.backgroundUrl = res.data.backgroundUrl;
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ console.log(err)
|
|
|
+ })
|
|
|
+ //console.log(localStorage.getItem('Ip'));
|
|
|
+ },
|
|
|
+
|
|
|
+ saveIP() {
|
|
|
+ let ip = localStorage.getItem('Ip')
|
|
|
+ let id = this.$store.getters.getId
|
|
|
+ ip = id + '~~' + ip
|
|
|
+
|
|
|
+ axios
|
|
|
+ .post(
|
|
|
+ this.ipSave,
|
|
|
+ qs.stringify({
|
|
|
+ key: 'file',
|
|
|
+ value: ip
|
|
|
+ }),
|
|
|
+ {
|
|
|
+ withCredentials: false
|
|
|
+ }
|
|
|
+ )
|
|
|
+ .then((res) => {
|
|
|
+ //console.log(res.data)
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ console.log(err)
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ getUserIpAuto() {
|
|
|
+ let i = setInterval(this.getUserIp, 1000)
|
|
|
+ },
|
|
|
+
|
|
|
+ preventBlur(event: any) {
|
|
|
+ event.preventDefault()
|
|
|
+ },
|
|
|
+
|
|
|
+ changeFlagTo1() {
|
|
|
+ this.vCardFlag = 1
|
|
|
+ },
|
|
|
+
|
|
|
+ changeFlagTo0() {
|
|
|
+ this.vCardFlag = 0
|
|
|
+ },
|
|
|
+
|
|
|
+ changeExamFlagTo1() {
|
|
|
+ ;(InputFocus as any).ExamFlag = 1
|
|
|
+ },
|
|
|
+
|
|
|
+ changeExamFlagTo0() {
|
|
|
+ ;(InputFocus as any).ExamFlag = 0
|
|
|
+ },
|
|
|
+
|
|
|
+ focusAuto() {
|
|
|
+ if ((InputFocus as any).cheatFlag == 1) {
|
|
|
+ ;(InputFocus as any).cheatFlag = 0
|
|
|
+ this.monitor()
|
|
|
+ }
|
|
|
+ const fo = this.$refs.focusOn
|
|
|
+ let intval = setInterval(function() {
|
|
|
+ if (
|
|
|
+ (InputFocus as any).InputFlag == 1 &&
|
|
|
+ (InputFocus as any).codeFlag == 1
|
|
|
+ ) {
|
|
|
+ ;(fo as any).focus()
|
|
|
+ }
|
|
|
+ }, 100)
|
|
|
+ },
|
|
|
+
|
|
|
+ changeVisibleFalse() {
|
|
|
+ this.visible = false
|
|
|
+ this.focusAuto()
|
|
|
+ },
|
|
|
+
|
|
|
+ timeWatch() {
|
|
|
+ this.time++
|
|
|
+ if (this.time > 10) {
|
|
|
+ this.time = 0
|
|
|
+ clearInterval(this.interval)
|
|
|
+ this.visible1 = true
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ timeCount() {
|
|
|
+ this.interval = setInterval(this.timeWatch, 1000)
|
|
|
+ },
|
|
|
+
|
|
|
+ stopTimeCount() {
|
|
|
+ clearInterval(this.interval)
|
|
|
+ this.time = 0
|
|
|
+ this.focusAuto()
|
|
|
+ ;(InputFocus as any).ExamFlag = 1
|
|
|
+ },
|
|
|
+
|
|
|
+ monitor() {
|
|
|
+ let _this = this
|
|
|
+ if (
|
|
|
+ this.allowNum < 1 &&
|
|
|
+ this.flag == 1 &&
|
|
|
+ this.vCardFlag == 1 &&
|
|
|
+ (InputFocus as any).InputFlag == 1
|
|
|
+ ) {
|
|
|
+ setTimeout(function() {
|
|
|
+ if ((InputFocus as any).codeFlag == 1) {
|
|
|
+ _this.flag = _this.flag - 1
|
|
|
+ _this.visible1 = true
|
|
|
+ }
|
|
|
+ }, 100)
|
|
|
+ } else if (
|
|
|
+ this.flag == 1 &&
|
|
|
+ this.vCardFlag == 1 &&
|
|
|
+ (InputFocus as any).InputFlag == 1
|
|
|
+ ) {
|
|
|
+ setTimeout(function() {
|
|
|
+ if ((InputFocus as any).codeFlag == 1) {
|
|
|
+ _this.allowNum = _this.allowNum - 1
|
|
|
+ //alert(y);
|
|
|
+ _this.visible = true
|
|
|
+ _this.changeFlagTo0()
|
|
|
+ _this.timeCount()
|
|
|
+ }
|
|
|
+ }, 100)
|
|
|
+ }
|
|
|
+ //document.getElementById("demo").innerHTML = y;
|
|
|
+ },
|
|
|
+
|
|
|
+ isFullScreen() {
|
|
|
+ if (window.outerHeight == screen.availHeight) {
|
|
|
+ if (window.outerWidth == screen.availWidth) {
|
|
|
+ this.stopTimeCount()
|
|
|
+ this.visible = false
|
|
|
+ this.flag = 1
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.flag = 0
|
|
|
+ if (this.allowNum < 1) {
|
|
|
+ this.visible1 = true
|
|
|
+ } else {
|
|
|
+ this.allowNum = this.allowNum - 1
|
|
|
+ this.visible = true
|
|
|
+ this.timeCount()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ getMedia() {
|
|
|
+ // H5调用电脑摄像头API
|
|
|
+ navigator.mediaDevices
|
|
|
+ .getUserMedia({
|
|
|
+ audio: true, //开启音频
|
|
|
+ video: true //开启视频
|
|
|
+ })
|
|
|
+ .then((success) => {
|
|
|
+ var videoTracks = success.getVideoTracks()
|
|
|
+ console.log(success)
|
|
|
+ console.log(videoTracks)
|
|
|
+ // 摄像头开启成功
|
|
|
+ ;(this.$refs['video'] as any).srcObject = success
|
|
|
+ ;(this.$refs['videos'] as any).srcObject = success
|
|
|
+ this.isCameraFlag = false
|
|
|
+ // 实时拍照效果
|
|
|
+ ;(this.$refs['video'] as any).play()
|
|
|
+ ;(this.$refs['videos'] as any).play()
|
|
|
+ this.interval = setInterval(this.photoCount, 1000)
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ console.error('摄像头开启失败,请检查摄像头是否可用!')
|
|
|
+ this.isCameraFlag = true
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ photoCount() {
|
|
|
+ this.photoNums = this.photoNums + 1
|
|
|
+ if (this.photoNums <= 10) {
|
|
|
+ this.selectPicUpload()
|
|
|
+ } else {
|
|
|
+ this.photoNums = 0
|
|
|
+ this.getMatchingResult()
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ getMatchingResult() {
|
|
|
+ let b = -1
|
|
|
+ let id = this.$store.getters.getId
|
|
|
+ axios
|
|
|
+ .get(this.gethrep, {
|
|
|
+ withCredentials: false,
|
|
|
+ params: {
|
|
|
+ id
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .then((res) => {
|
|
|
+ console.log('res' + res)
|
|
|
+ b = res.data
|
|
|
+ console.log('b:' + b)
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ console.log('err')
|
|
|
+ console.log(err)
|
|
|
+ console.log(err.data)
|
|
|
+ b = err.data
|
|
|
+ /*不知道为什么数据会出现在error里,目前就直接从中获取了。
|
|
|
+ */
|
|
|
+ console.log('eb:' + b)
|
|
|
+ if (b == 0) {
|
|
|
+ this.notMatchNum = this.notMatchNum + 1
|
|
|
+ console.log('nmn:' + this.notMatchNum)
|
|
|
+ if (this.notMatchNum >= 5) {
|
|
|
+ this.leaveExam()
|
|
|
+ }
|
|
|
+ } else if (b == 1) {
|
|
|
+ console.log('nmn:' + this.notMatchNum)
|
|
|
+ this.notMatchNum = 0
|
|
|
+ }
|
|
|
+ })
|
|
|
+ //console.log("b:"+b);
|
|
|
+ /*if(b==0){
|
|
|
+ this.notMatchNum = this.notMatchNum+1;
|
|
|
+ console.log("nmn:"+this.notMatchNum);
|
|
|
+ if(this.notMatchNum>=5){
|
|
|
+ this.leaveExam();
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ else if(b==1){
|
|
|
+ console.log("nmn:"+this.notMatchNum);
|
|
|
+ this.notMatchNum = 0;*/
|
|
|
+ //}
|
|
|
+ },
|
|
|
+
|
|
|
+ selectPicUpload() {
|
|
|
+ let ctx = (this.$refs['canvas'] as any).getContext('2d')
|
|
|
+ // 把当前视频帧内容渲染到canvas上
|
|
|
+ ctx.drawImage(this.$refs['video'], 0, 0, 640, 480)
|
|
|
+ let imgBase64 = (this.$refs['canvas'] as any).toDataURL('image/png', 0.7)
|
|
|
+ let id = this.$store.getters.getId
|
|
|
+ imgBase64 = id + '~~' + this.photoNums + '~~' + imgBase64
|
|
|
+ //let fd = new FormData(); //参数的格式是formData格式的
|
|
|
+ //fd.append("image", imgBase64); //参数
|
|
|
+ //for (var value of fd.values()) {
|
|
|
+ // console.log(value);
|
|
|
+ //}
|
|
|
+ axios
|
|
|
+ .post(
|
|
|
+ this.hrep,
|
|
|
+ qs.stringify({
|
|
|
+ key: 'file',
|
|
|
+ value: imgBase64
|
|
|
+ }),
|
|
|
+ {
|
|
|
+ withCredentials: false
|
|
|
+ //headers: {'Content-Type': 'multipart/form-data;boundary = ' + new Date().getTime()
|
|
|
+ //}
|
|
|
+ }
|
|
|
+ )
|
|
|
+ .then((res) => {
|
|
|
+ //console.log(res.data)
|
|
|
+ //this.imageUrl = res.data.url;
|
|
|
+ // this.backgroundUrl = res.data.backgroundUrl;
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ console.log(err)
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
questionType(): QuestionType {
|
|
|
return this.currentQuestion.type
|
|
|
},
|
|
|
@@ -436,6 +879,15 @@ export default Vue.extend({
|
|
|
},
|
|
|
mounted() {
|
|
|
this.fetchData()
|
|
|
+ ;(this.$refs.focusOn as any).focus()
|
|
|
+ this.getUserIp()
|
|
|
+ this.isFullScreen()
|
|
|
+ this.getMedia()
|
|
|
+ this.saveIP()
|
|
|
+ this.getUserIpAuto()
|
|
|
+ window.onresize = () => {
|
|
|
+ this.isFullScreen()
|
|
|
+ }
|
|
|
}
|
|
|
})
|
|
|
</script>
|