Переглянути джерело

rewrite monitor code,make monitor independent,details in monitor.md

171250600 5 роки тому
батько
коміт
ebb4c4ef86

+ 26 - 0
monitor.md

@@ -6,3 +6,29 @@
 
 在beforExam页面加入了新的判断,只有当后端传来确定的回复时(传到后端的含人脸照片已有200张),此时才可进入下一页面,mock测试时,可以刷新本页面到没监考的状态,之后继续进入下一页面。
 
+2021/5/7
+
+修改了焦点监控的实现方式
+
+将基本全部监考内容移到了monitor.vue中
+
+调用monitor.vue的方法:
+
+import monitor.vue
+
+import blurListen.ts
+
+在mounted中加入:
+
+initMonitor() {
+
+   if (this.examInfo.antiCheating) {
+
+​    	blurListen.cheatNum = 3;
+
+​    	blurListen.anticheating = this.examInfo.antiCheating;
+
+​    }
+
+  }
+

+ 20 - 435
src/views/exam/ExamPane.vue

@@ -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>
 

+ 1 - 31
src/views/exam/components/BlankPane.vue

@@ -14,11 +14,6 @@
       v-model="answer"
       class="pa-10"
       label="请输入答案"
-      @mousedown="
-        changeInputFlagTo0()
-        changeExamFlagTo0()
-      "
-      @blur="InputMonitor()"
     ></v-text-field>
   </div>
 </template>
@@ -27,7 +22,6 @@
 import Vue, { PropType } from 'vue'
 import MarkdownText from '@/views/exam/components/MarkdownText.vue'
 import { QuestionSerializer } from '@/api/types'
-import { InputFocus } from '@/views/exam/components/InputFocus.ts'
 export default Vue.extend({
   name: 'BlankPane',
   components: {
@@ -50,31 +44,7 @@ export default Vue.extend({
       this.answer = ''
     }
   },
-  methods: {
-    changeInputFlagTo0() {
-      ;(InputFocus as any).InputFlag = 0
-      //alert(InputFocus.InputFlag);
-    },
-    changeInputFlagTo1() {
-      ;(InputFocus as any).InputFlag = 1
-    },
-    changeExamFlagTo0() {
-      ;(InputFocus as any).ExamFlag = 0
-    },
-
-    InputMonitor() {
-      let _this = this
-      setTimeout(function() {
-        if ((InputFocus as any).ExamFlag == 0) {
-          ;(InputFocus as any).InputFlag = 1
-          ;(InputFocus as any).cheatFlag = 1
-          _this.$emit('focusAuto')
-        } else {
-          ;(InputFocus as any).InputFlag = 1
-        }
-      }, 2)
-    }
-  },
+  methods: {},
   beforeUpdate() {
     this.$emit('saveAnswer', this.answer)
   }

+ 3 - 29
src/views/exam/components/ChoicePane.vue

@@ -32,14 +32,10 @@
                   :input-value="active"
                   on-icon="mdi-radiobox-marked"
                   off-icon="mdi-radiobox-blank"
-                  @mousedown="changeInputFlagTo0()"
                 >
                 </v-checkbox>
               </v-list-item-action>
-              <v-list-item-content
-                class="py-0"
-                @mousedown="changeInputFlagTo0()"
-              >
+              <v-list-item-content class="py-0">
                 <markdown-text :raw="choice.labelText"></markdown-text>
               </v-list-item-content>
             </template>
@@ -53,17 +49,9 @@
           <v-list-item :key="choice.label" :value="choice.label">
             <template v-slot:default="{ active }">
               <v-list-item-action>
-                <v-checkbox
-                  hide-details
-                  :input-value="active"
-                  @mousedown="changeInputFlagTo0()"
-                >
-                </v-checkbox>
+                <v-checkbox hide-details :input-value="active"> </v-checkbox>
               </v-list-item-action>
-              <v-list-item-content
-                class="py-0"
-                @mousedown="changeInputFlagTo0()"
-              >
+              <v-list-item-content class="py-0">
                 <markdown-text :raw="choice.labelText"></markdown-text>
               </v-list-item-content>
             </template>
@@ -81,7 +69,6 @@ import MarkdownText from '@/views/exam/components/MarkdownText.vue'
 import { QuestionSerializer, OptionLabelTextPair } from '@/api/types'
 
 import { clone } from 'ramda'
-import { InputFocus } from '@/views/exam/components/InputFocus.ts'
 export default Vue.extend({
   name: 'ChoicePane',
   components: {
@@ -124,19 +111,6 @@ export default Vue.extend({
     }
   },
   methods: {
-    changeInputFlagTo0() {
-      //alert((InputFocus as any).InputFlag);
-      ;(InputFocus as any).InputFlag = 0
-      setTimeout(this.changeInputFlagTo1, 50)
-    },
-    changeInputFlagTo1() {
-      ;(InputFocus as any).InputFlag = 1
-    },
-
-    preventBlur(event: any) {
-      event.preventDefault()
-    },
-
     init() {
       if (this.question.type === 'true_false') {
         this.question.options?.set('true', '')

+ 1 - 43
src/views/exam/components/CodeEditor.vue

@@ -1,11 +1,5 @@
 <template>
-  <div
-    style="height: 100%; width: 100%;"
-    @mousedown="
-      changeCodeFlagTo0()
-      changeExamFlagTo0()
-    "
-  ></div>
+  <div style="height: 100%; width: 100%;"></div>
 </template>
 
 <script lang="ts">
@@ -14,7 +8,6 @@ import * as Monaco from 'monaco-editor'
 import { editor } from 'monaco-editor'
 import ICodeEditor = editor.ICodeEditor
 import ITextModel = editor.ITextModel
-import { InputFocus } from '@/views/exam/components/InputFocus.ts'
 
 export default Vue.extend({
   name: 'CodeEditor',
@@ -76,32 +69,6 @@ export default Vue.extend({
   },
 
   methods: {
-    changeCodeFlagTo0() {
-      ;(InputFocus as any).codeFlag = 0
-      console.log('change to 0')
-      //alert(InputFocus.codeFlag);
-    },
-    changeCodeFlagTo1() {
-      ;(InputFocus as any).codeFlag = 1
-      console.log('change to 1')
-    },
-    changeExamFlagTo0() {
-      ;(InputFocus as any).ExamFlag = 0
-    },
-
-    CodeMonitor() {
-      let _this = this
-      setTimeout(function() {
-        if ((InputFocus as any).ExamFlag == 0&&InputFocus.InputFlag == 1) {
-          _this.changeCodeFlagTo1();
-          (InputFocus as any).cheatFlag = 1
-          _this.$emit('focusAuto')
-        } else {
-          ;(InputFocus as any).codeFlag = 1
-        }
-      }, 100)
-    },
-
     initMonaco() {
       const options = {
         value: this.value,
@@ -118,15 +85,6 @@ export default Vue.extend({
           this.$emit('change', value)
         }
       })
-
-      this.editor.onDidBlurEditorText(() => {
-        this.CodeMonitor()
-      })
-
-      this.editor.onDidFocusEditorText(() => {
-        this.changeCodeFlagTo0()
-        this.changeExamFlagTo0()
-      })
     },
     setValue(val: string) {
       this.editor.setValue(val)

+ 12 - 166
src/views/exam/components/CodePane.vue

@@ -4,27 +4,9 @@
       <div class="flex-grow-1 d-flex flex-column justify-space-between">
         <div>
           <v-tabs v-model="tab" class="pa-0 ma-0 elevation-1" grow>
-            <v-tab
-              @mousedown="
-                changeExamFlagTo1()
-                preventBlur($event)
-              "
-              >题目描述</v-tab
-            >
-            <v-tab
-              @mousedown="
-                changeExamFlagTo1()
-                preventBlur($event)
-              "
-              >运行结果</v-tab
-            >
-            <v-tab
-              @mousedown="
-                changeExamFlagTo1()
-                preventBlur($event)
-              "
-              >提交记录</v-tab
-            >
+            <v-tab>题目描述</v-tab>
+            <v-tab>运行结果</v-tab>
+            <v-tab>提交记录</v-tab>
           </v-tabs>
           <v-tabs-items v-model="tab">
             <v-tab-item>
@@ -102,14 +84,9 @@
                           small
                           outlined
                           @click="onShowCode(item.submitAnswer)"
-                          @mousedown="
-                            changeExamFlagTo1()
-                            changeInputFlagTo0()
-                            changeExamFlagTo1()                         
-                          "
                           >详情</v-btn
                         >
-                        <v-tooltip top >
+                        <v-tooltip top>
                           <template v-slot:activator="{ on, attrs }">
                             <v-btn
                               id="rollbackBtn"
@@ -119,11 +96,6 @@
                               x-small
                               outlined
                               @click="onOpenRollbackDialog(item.submitAnswer)"
-                              @mousedown="
-                                changeExamFlagTo1()
-                                changeInputFlagTo0()
-                                changeExamFlagTo1()   
-                              "
                               v-bind="attrs"
                               v-on="on"
                             >
@@ -145,26 +117,8 @@
             <span class="mx-2"> 编程题</span>
             <slot name="index"></slot>
           </span>
-          <v-btn
-            color="info"
-            small
-            @click="prevQuestion"
-            @mousedown="
-              changeExamFlagTo1()
-              preventBlur($event)
-            "
-            >上一题</v-btn
-          >
-          <v-btn
-            color="info"
-            small
-            @click="nextQuestion"
-            @mousedown="
-              changeExamFlagTo1()
-              preventBlur($event)
-            "
-            >下一题</v-btn
-          >
+          <v-btn color="info" small @click="prevQuestion">上一题</v-btn>
+          <v-btn color="info" small @click="nextQuestion">下一题</v-btn>
         </div>
       </div>
       <v-divider vertical class="mx-1"></v-divider>
@@ -182,15 +136,6 @@
                 style="width: 120px"
                 class="ma-0 pa-0"
                 solo
-                @mousedown="
-                  changeInputFlagTo0()
-                  changeExamFlagTo0()
-                "
-                @change="
-                  changeExamFlagTo1()
-                  InputMonitor()
-                "
-                @blur="InputMonitor()"
               >
               </v-select>
             </div>
@@ -202,10 +147,6 @@
             :disabled="running"
             :loading="running"
             outlined
-            @mousedown="
-              changeExamFlagTo1()
-              preventBlur($event)
-            "
             >保存测试</v-btn
           >
         </div>
@@ -215,59 +156,26 @@
         ></code-editor>
       </div>
     </div>
-    <v-snackbar
-      v-model="error"
-      top
-      color="warning"
-      absolute
-      @mousedown="
-        changeExamFlagTo1()
-        preventBlur($event)
-      "
+    <v-snackbar v-model="error" top color="warning" absolute
       >提交失败</v-snackbar
     >
-    <v-dialog v-if="showCode" v-model="showCode" max-width="800" persistent>
+    <v-dialog v-if="showCode" v-model="showCode" max-width="800">
       <v-card class="pt-3">
-        <v-card-title
-          >代码详情
-          <v-btn @click="showCode = false" 
-              @mousedown="
-              changeExamFlagTo1()
-              MonitorLater()" 
-              x-small class="mx-2" tile plain
-            >CLOSE</v-btn
-          >
-        </v-card-title>
+        <v-card-title>代码详情</v-card-title>
         <markdown-text :raw="codeDetailContent" class="mx-5"></markdown-text>
       </v-card>
     </v-dialog>
-    <v-dialog
-      max-width="300"
-      v-model="rollbackCodeDialog"
-    >
+    <v-dialog max-width="300" v-model="rollbackCodeDialog">
       <v-card>
         <v-card-title>确认恢复代码</v-card-title>
         <v-card-actions class="justify-end">
-          <v-btn
-            outlined
-            color="success darken-1"
-            @click="onRollback"
-            @mousedown="
-              changeExamFlagTo1()
-              MonitorLater()
-            "
-          >
+          <v-btn outlined color="success darken-1" @click="onRollback">
             确定
           </v-btn>
           <v-btn
             outlined
             color="error darken-1"
             @click="rollbackCodeDialog = false"
-            @mousedown="
-              preventBlur($event)
-              changeExamFlagTo1()
-              MonitorLater()
-            "
           >
             取消
           </v-btn>
@@ -293,7 +201,6 @@ import {
 import { codeJudge } from '@/api/judge'
 import { getExamQuestionSubmitRecord } from '@/api/record'
 import dayjs from 'dayjs'
-import { InputFocus } from '@/views/exam/components/InputFocus.ts'
 
 interface CodeJudgeInfo extends CodeJudgeResult {
   id: ID
@@ -342,19 +249,10 @@ export default Vue.extend({
       showCode: false,
       codeDetailContent: '',
       rollbackCodeDialog: false,
-      rollbackAnswer: '',
-
-      editFlag: 0
+      rollbackAnswer: ''
     }
   },
   watch: {
-    dialogVisible() {
-      if (this.rollbackCodeDialog) {
-        this.changeInputFlagTo0()
-        this.changeExamFlagTo0()
-      }
-    },
-
     currentLang(val, oldVal) {
       if (oldVal !== '' && val !== oldVal) {
         const newCode =
@@ -384,50 +282,6 @@ export default Vue.extend({
     this.updateSubmitRecords()
   },
   methods: {
-    test() {
-      alert('???')
-    },
-
-    preventBlur(event: any) {
-      event.preventDefault()
-    },
-
-    changeInputFlagTo0() {
-      ;(InputFocus as any).InputFlag = 0
-      //alert((InputFocus as any).InputFlag);
-    },
-    changeInputFlagTo1() {
-      ;(InputFocus as any).InputFlag = 1      
-      //alert("who")
-    },
-    changeExamFlagTo0() {
-      ;(InputFocus as any).ExamFlag = 0
-    },
-    changeExamFlagTo1() {
-      ;(InputFocus as any).ExamFlag = 1
-    },
-
-    InputMonitor() {
-      //this.test();
-      let _this = this
-      setTimeout(function() {
-        if ((InputFocus as any).ExamFlag == 0) {
-          ;(InputFocus as any).InputFlag = 1
-          ;(InputFocus as any).cheatFlag = 1
-          //alert("cheat")
-          _this.$emit('focusAuto')
-        } else {
-          ;(InputFocus as any).InputFlag = 1
-          //_this.$emit('focusAuto')
-          //alert("to1")
-        }
-      }, 5)
-    },
-
-    MonitorLater(){
-      setTimeout(this.InputMonitor,500);
-    },
-
     onShowCode(content: string) {
       content = '```\n' + content.replace('\r', '') + '\n```'
       this.codeDetailContent = content
@@ -478,14 +332,6 @@ export default Vue.extend({
       this.currentCode = this.rollbackAnswer
       this.rollbackCodeDialog = false
     },
-    resetOriginCode() {
-      // const originCodes = this.question.codeTuples ?? []
-      // const originTuple =
-      //   originCodes.find((val) => {
-      //     return val.lang === this.currentLang
-      //   }) ?? {}
-      // this.currentCode = originTuple.code ?? ''
-    },
     prevQuestion() {
       this.$emit('prevQuestion')
     },

+ 0 - 6
src/views/exam/components/InputFocus.ts

@@ -1,6 +0,0 @@
-export const InputFocus = {
-  InputFlag: 1,
-  codeFlag: 1,
-  ExamFlag: 0,
-  cheatFlag: 0
-}

+ 5 - 0
src/views/exam/components/blurListen.ts

@@ -0,0 +1,5 @@
+export const blurListen = {
+    listen: true,
+    cheatNum: 3,
+    anticheating:false
+}

+ 390 - 0
src/views/exam/components/monitor.vue

@@ -0,0 +1,390 @@
+<template>
+  <div>
+      <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>
+
+      <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()"
+              >
+                确认
+              </v-btn>
+            </v-card-actions>
+          </v-card>
+        </v-dialog>
+      </div>
+    </template>
+
+    <template>
+      <div>
+        <v-dialog v-model="cheatVisible" 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="quit()"
+              >
+                确认
+              </v-btn>
+            </v-card-actions>
+          </v-card>
+        </v-dialog>
+      </div>
+    </template>
+
+    <template>
+      <div>
+        <v-dialog v-model="IPChangeVisible" 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>
+
+<script lang="ts">
+import Vue from 'vue'
+import {blurListen} from '@/views/exam/components/blurListen.ts'
+import axios from 'axios'
+import qs from 'qs'
+
+export default Vue.extend({
+  name: 'Monitor',
+
+  data() {
+    return {
+        visible: false,
+        cheatVisible: false,
+        IPChangeVisible:false,
+        time:0,
+        timeCountInterval:0,
+        ipInterval:0,
+        isCameraFlag: true,
+        photoNums: 0,
+        notMatchNum: 0,
+        videoInterval: 0,
+        mediaTracks: [] as MediaStreamTrack[],
+        hrep: 'http://localhost:8080/detect',
+        gethrep: 'http://localhost:8080/matching',
+        ipSave: 'http://localhost:8080/saveIP',
+        ipDetect: 'http://localhost:8080/detectIP'
+    }
+  },
+  props: {
+  },
+
+  watch: {
+    
+  },
+
+  mounted() {
+      this.InitMonitor();
+  },
+
+  beforeDestroy() {
+    clearInterval(this.videoInterval)
+    clearInterval(this.timeCountInterval)
+    clearInterval(this.ipInterval)
+    this.mediaTracks.forEach((val) => {
+      val.stop()
+    })
+  },
+
+  methods: {
+
+    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.quit()
+            }
+          } 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)
+        })
+    },
+
+    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.IPChangeVisible = 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)
+    },
+
+    quit(){
+        blurListen.cheatNum = 3;
+        console.log(this.$parent);
+        (this.$parent.$parent as any).leaveExam();
+    },
+
+    changeVisibleFalse(){
+        this.visible = false;
+    },
+
+    test(){
+      if(blurListen.listen){
+        alert(">>/")
+      } 
+    },
+
+    timeWatch() {
+      this.time++
+      if (this.time > 10) {
+        this.time = 0
+        clearInterval(this.timeCountInterval)
+        this.cheatVisible = true
+      }
+    },
+
+    timeCount() {
+      this.timeCountInterval = setInterval(this.timeWatch, 1000)
+    },
+
+    stopTimeCount() {
+      clearInterval(this.timeCountInterval)
+      this.time = 0
+    },
+
+    monitor(){
+        if(blurListen.listen){
+            if(blurListen.cheatNum==0){
+                this.cheatVisible = true;
+            }
+            else{
+                this.visible = true;
+                blurListen.cheatNum = blurListen.cheatNum - 1;
+                this.timeCount();
+            }
+        }   
+    },
+
+    isFullScreen() {
+        if (window.outerHeight == screen.availHeight) {
+          if (window.outerWidth == screen.availWidth) {
+            this.stopTimeCount()
+            this.visible = false
+          }
+        } else {
+          if ( blurListen.cheatNum< 1) {
+            this.cheatVisible = true
+          } else {
+            blurListen.cheatNum = blurListen.cheatNum - 1;
+            this.visible = true
+            this.timeCount()
+          }
+        }
+    },
+
+    InitMonitor(){
+      if(blurListen.anticheating){
+        window.focus;
+        window.onblur = this.monitor; 
+        window.onfocus = this.stopTimeCount;
+        this.isFullScreen();
+        window.onresize = this.isFullScreen;
+        this.getUserIp()
+        this.getMedia()
+        this.saveIP()
+        this.getUserIpAuto()
+      }        
+    },
+  },
+
+  
+})
+</script>
+
+<style></style>