|
|
@@ -1,11 +1,5 @@
|
|
|
<template>
|
|
|
- <div
|
|
|
- tabIndex="-1"
|
|
|
- @blur="monitor()"
|
|
|
- ref="focusOn"
|
|
|
- @focus="stopTimeCount()"
|
|
|
- @resize="isFullScreen()"
|
|
|
- >
|
|
|
+ <div>
|
|
|
<v-navigation-drawer app permanent right class="elevation-5">
|
|
|
<div
|
|
|
class="flex-column d-flex justify-space-between"
|
|
|
@@ -83,18 +77,12 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
<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>
|
|
|
- <canvas
|
|
|
- name="file"
|
|
|
- v-show="false"
|
|
|
- width="640"
|
|
|
- height="480"
|
|
|
- ref="canvas"
|
|
|
- ></canvas>
|
|
|
+ <monitor></monitor>
|
|
|
+
|
|
|
<!-- 预留给反作弊-->
|
|
|
</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 }">
|
|
|
@@ -102,13 +90,11 @@
|
|
|
color="error"
|
|
|
v-bind="attrs"
|
|
|
v-on="on"
|
|
|
- @click="changeFlagTo0"
|
|
|
- @mousedown="preventBlur($event)"
|
|
|
>
|
|
|
退出评测
|
|
|
</v-btn>
|
|
|
</template>
|
|
|
- <v-card @mousedown="preventBlur($event)">
|
|
|
+ <v-card>
|
|
|
<v-card-title class="headline">
|
|
|
确认离开评测?
|
|
|
</v-card-title>
|
|
|
@@ -118,12 +104,7 @@
|
|
|
<v-btn
|
|
|
color="success darken-1"
|
|
|
text
|
|
|
- @click="
|
|
|
- exitDialog = false
|
|
|
- changeFlagTo1()
|
|
|
- focusAuto()
|
|
|
- "
|
|
|
- @mousedown="preventBlur($event)"
|
|
|
+ @click="exitDialog = false"
|
|
|
>
|
|
|
否
|
|
|
</v-btn>
|
|
|
@@ -131,7 +112,6 @@
|
|
|
color="error darken-1"
|
|
|
text
|
|
|
@click="leaveExam"
|
|
|
- @mousedown="preventBlur($event)"
|
|
|
>
|
|
|
是
|
|
|
</v-btn>
|
|
|
@@ -144,8 +124,6 @@
|
|
|
color="success"
|
|
|
v-bind="attrs"
|
|
|
v-on="on"
|
|
|
- @click="changeFlagTo0"
|
|
|
- @mousedown="preventBlur($event)"
|
|
|
>
|
|
|
提交试卷
|
|
|
</v-btn>
|
|
|
@@ -160,12 +138,7 @@
|
|
|
<v-btn
|
|
|
color="success darken-1"
|
|
|
text
|
|
|
- @click="
|
|
|
- submitDialog = false
|
|
|
- changeFlagTo1()
|
|
|
- focusAuto()
|
|
|
- "
|
|
|
- @mousedown="preventBlur($event)"
|
|
|
+ @click="submitDialog = false"
|
|
|
>
|
|
|
否
|
|
|
</v-btn>
|
|
|
@@ -173,7 +146,6 @@
|
|
|
color="error darken-1"
|
|
|
text
|
|
|
@click="submitAnswer"
|
|
|
- @mousedown="preventBlur($event)"
|
|
|
>
|
|
|
是
|
|
|
</v-btn>
|
|
|
@@ -197,7 +169,6 @@
|
|
|
:total-question-num="codeQuestionNum"
|
|
|
@nextQuestion="nextQuestion"
|
|
|
@prevQuestion="prevQuestion"
|
|
|
- @mousedown="preventBlur($event)"
|
|
|
>
|
|
|
<template #index>
|
|
|
<v-chip class="align-self-center"
|
|
|
@@ -270,78 +241,6 @@
|
|
|
提交失败,请重试
|
|
|
</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>
|
|
|
|
|
|
@@ -366,9 +265,10 @@ 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.ts'
|
|
|
+import { blurListen } from '@/views/exam/components/blurListen.ts'
|
|
|
import axios from 'axios'
|
|
|
import qs from 'qs'
|
|
|
+import Monitor from './components/monitor.vue'
|
|
|
|
|
|
export default Vue.extend({
|
|
|
name: 'ExamPane',
|
|
|
@@ -376,7 +276,8 @@ export default Vue.extend({
|
|
|
CountDown,
|
|
|
CodePane,
|
|
|
ChoicePane,
|
|
|
- BlankPane
|
|
|
+ BlankPane,
|
|
|
+ Monitor
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
@@ -407,25 +308,6 @@ export default Vue.extend({
|
|
|
codeQuestionsReady: false,
|
|
|
endTimeChanged: false,
|
|
|
|
|
|
- mediaTracks: [] as MediaStreamTrack[],
|
|
|
- time: 0,
|
|
|
- allowNum: 3,
|
|
|
- videoInterval: 0,
|
|
|
- focusAutoInterval: 0,
|
|
|
- timeCountInterval: 0,
|
|
|
- ipInterval: 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: {
|
|
|
@@ -437,297 +319,10 @@ export default Vue.extend({
|
|
|
}
|
|
|
},
|
|
|
methods: {
|
|
|
- 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() {
|
|
|
- this.ipInterval = setInterval(this.getUserIp, 1000)
|
|
|
- },
|
|
|
-
|
|
|
- preventBlur(event: any) {
|
|
|
- if (this.examInfo.antiCheating) {
|
|
|
- event.preventDefault()
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- changeFlagTo1() {
|
|
|
- this.vCardFlag = 1
|
|
|
- },
|
|
|
-
|
|
|
- changeFlagTo0() {
|
|
|
- this.vCardFlag = 0
|
|
|
- },
|
|
|
-
|
|
|
- changeExamFlagTo1() {
|
|
|
- ;(InputFocus as any).ExamFlag = 1
|
|
|
- },
|
|
|
-
|
|
|
- changeExamFlagTo0() {
|
|
|
- ;(InputFocus as any).ExamFlag = 0
|
|
|
- },
|
|
|
-
|
|
|
- focusAuto() {
|
|
|
- if (this.examInfo.antiCheating) {
|
|
|
- if ((InputFocus as any).cheatFlag == 1) {
|
|
|
- ;(InputFocus as any).cheatFlag = 0
|
|
|
- this.monitor()
|
|
|
- }
|
|
|
- const fo = this.$refs.focusOn
|
|
|
- this.focusAutoInterval = 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.timeCountInterval)
|
|
|
- this.visible1 = true
|
|
|
- }
|
|
|
- },
|
|
|
+
|
|
|
|
|
|
- timeCount() {
|
|
|
- this.timeCountInterval = setInterval(this.timeWatch, 1000)
|
|
|
- },
|
|
|
|
|
|
- stopTimeCount() {
|
|
|
- clearInterval(this.timeCountInterval)
|
|
|
- this.time = 0
|
|
|
- this.focusAuto()
|
|
|
- ;(InputFocus as any).ExamFlag = 1
|
|
|
- },
|
|
|
-
|
|
|
- monitor() {
|
|
|
- if (this.examInfo.antiCheating) {
|
|
|
- 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
|
|
|
- }
|
|
|
- }, 1)
|
|
|
- } 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
|
|
|
- _this.visible = true
|
|
|
- _this.changeFlagTo0()
|
|
|
- _this.timeCount()
|
|
|
- }
|
|
|
- }, 10)
|
|
|
- }
|
|
|
- //document.getElementById("demo").innerHTML = y;
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- isFullScreen() {
|
|
|
- if (this.examInfo.antiCheating) {
|
|
|
- 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()
|
|
|
- this.mediaTracks = success.getTracks()
|
|
|
- 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.videoInterval = 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
|
|
|
@@ -829,6 +424,8 @@ export default Vue.extend({
|
|
|
}
|
|
|
},
|
|
|
fetchData() {
|
|
|
+ console.log(this)
|
|
|
+
|
|
|
this.examId = this.$route.params.examId
|
|
|
|
|
|
getExamById(this.examId).then(({ data }) => {
|
|
|
@@ -879,30 +476,18 @@ export default Vue.extend({
|
|
|
},
|
|
|
initMonitor() {
|
|
|
if (this.examInfo.antiCheating) {
|
|
|
- ;(this.$refs.focusOn as any).focus()
|
|
|
- this.getUserIp()
|
|
|
- this.isFullScreen()
|
|
|
- this.getMedia()
|
|
|
- this.saveIP()
|
|
|
- this.getUserIpAuto()
|
|
|
- window.onresize = () => {
|
|
|
+ blurListen.cheatNum = 3;
|
|
|
+ blurListen.anticheating = this.examInfo.antiCheating;
|
|
|
+ /*window.onresize = () => {
|
|
|
this.isFullScreen()
|
|
|
- }
|
|
|
+ }*/
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
mounted() {
|
|
|
this.fetchData()
|
|
|
},
|
|
|
- beforeDestroy() {
|
|
|
- clearInterval(this.videoInterval)
|
|
|
- clearInterval(this.timeCountInterval)
|
|
|
- clearInterval(this.focusAutoInterval)
|
|
|
- clearInterval(this.ipInterval)
|
|
|
- this.mediaTracks.forEach((val) => {
|
|
|
- val.stop()
|
|
|
- })
|
|
|
- }
|
|
|
+
|
|
|
})
|
|
|
</script>
|
|
|
|