Browse Source

添加语音作答题

Michael Wang 6 years ago
parent
commit
84f05684f6

+ 10 - 0
src/features/OnlineExam/Examing/ExamingHome.vue

@@ -56,6 +56,7 @@ import QuestionNavView from "./QuestionNavView.vue";
 import FaceTracking from "./FaceTracking.vue";
 import FaceTracking from "./FaceTracking.vue";
 import FaceId from "./FaceId.vue";
 import FaceId from "./FaceId.vue";
 import FaceRecognition from "../../../components/FaceRecognition/FaceRecognition";
 import FaceRecognition from "../../../components/FaceRecognition/FaceRecognition";
+import { openWS, closeWsWithoutReconnect } from "./ws.js";
 import { createNamespacedHelpers } from "vuex";
 import { createNamespacedHelpers } from "vuex";
 const { mapState, mapMutations } = createNamespacedHelpers("examingHomeModule");
 const { mapState, mapMutations } = createNamespacedHelpers("examingHomeModule");
 
 
@@ -229,6 +230,7 @@ export default {
     clearInterval(this.snapInterval);
     clearInterval(this.snapInterval);
     clearTimeout(this.faceIdMsgTimeout);
     clearTimeout(this.faceIdMsgTimeout);
     clearTimeout(this.faceIdDivTimeout);
     clearTimeout(this.faceIdDivTimeout);
+    closeWsWithoutReconnect();
     this.updateExamState({
     this.updateExamState({
       exam: null,
       exam: null,
       paperStruct: null,
       paperStruct: null,
@@ -352,6 +354,13 @@ export default {
         examQuestionList: examQuestionList,
         examQuestionList: examQuestionList,
         allAudioPlayTimes: JSON.parse(examQuestionList[0].audioPlayTimes) || []
         allAudioPlayTimes: JSON.parse(examQuestionList[0].audioPlayTimes) || []
       });
       });
+      // console.log(examQuestionList);
+      // console.log(examQuestionList.find(v => v.answerType === "SINGLE_AUDIO"));
+      if (examQuestionList.find(v => v.answerType === "SINGLE_AUDIO")) {
+        // console.log("have single");
+        const examRecordDataId = this.$route.params.examRecordDataId;
+        openWS({ examRecordDataId });
+      }
     },
     },
     updateQuestion: async function(next) {
     updateQuestion: async function(next) {
       // 初始化套题的答案,为回填部分选项做准备
       // 初始化套题的答案,为回填部分选项做准备
@@ -500,6 +509,7 @@ export default {
       }
       }
       this.submitLock = false;
       this.submitLock = false;
       this.$Spin.hide();
       this.$Spin.hide();
+      closeWsWithoutReconnect();
     },
     },
     examQuestion() {
     examQuestion() {
       return (
       return (

+ 81 - 4
src/features/OnlineExam/Examing/TextQuestionView.vue

@@ -10,7 +10,7 @@
     <div class="option">
     <div class="option">
       <!-- <textarea v-model="studentAnswer" maxlength="5000" type="text" /> -->
       <!-- <textarea v-model="studentAnswer" maxlength="5000" type="text" /> -->
 
 
-      <div>
+      <div v-if="examQuestion.answerType !== 'SINGLE_AUDIO'">
         <div class="menu">
         <div class="menu">
           <i-button type="info" class="text-ops" size="small" @click="textCopy">
           <i-button type="info" class="text-ops" size="small" @click="textCopy">
             复制
             复制
@@ -62,6 +62,20 @@
           class="stu-answer"
           class="stu-answer"
         ></div>
         ></div>
       </div>
       </div>
+      <div v-else>
+        <div v-if="!this.examQuestion.studentAnswer">
+          <div v-if="qrValue && !this.examQuestion.studentAnswer">
+            <qrcode :value="qrValue" :options="{ width: 200 }"></qrcode>
+            <div>请使用微信扫码二维码后,在微信小程序上录音,并上传录音</div>
+            <div v-if="qrScanned">已扫描 <Icon type="md-checkmark" /></div>
+          </div>
+          <div v-else>正在获取二维码...</div>
+        </div>
+
+        <div v-else>
+          <audio controls :src="this.examQuestion.studentAnswer" />
+        </div>
+      </div>
       <div class="reset" style="padding-top: 20px">
       <div class="reset" style="padding-top: 20px">
         <i-button type="warning" size="large" @click="studentAnswer = null">
         <i-button type="warning" size="large" @click="studentAnswer = null">
           重置答案
           重置答案
@@ -86,23 +100,30 @@
 
 
 <script>
 <script>
 import QuestionBody from "./QuestionBody";
 import QuestionBody from "./QuestionBody";
+import { getQRCode } from "./ws.js";
 import { createNamespacedHelpers } from "vuex";
 import { createNamespacedHelpers } from "vuex";
-const { mapMutations, mapGetters } = createNamespacedHelpers(
+const { mapMutations, mapGetters, mapState } = createNamespacedHelpers(
   "examingHomeModule"
   "examingHomeModule"
 );
 );
+import VueQrcode from "@chenfengyuan/vue-qrcode";
 
 
 export default {
 export default {
   name: "TextQuestionView",
   name: "TextQuestionView",
   data() {
   data() {
     return {
     return {
       studentAnswer: this.examQuestion.studentAnswer,
       studentAnswer: this.examQuestion.studentAnswer,
-      isShowAnswer: false
+      isShowAnswer: false,
+      qrValue: "",
+      qrScanned: false
     };
     };
   },
   },
   props: {
   props: {
     question: Object,
     question: Object,
     examQuestion: Object
     examQuestion: Object
   },
   },
+  created() {
+    this.fetchQRCode();
+  },
   methods: {
   methods: {
     ...mapMutations(["updateExamQuestion"]),
     ...mapMutations(["updateExamQuestion"]),
     ...mapGetters(["examShouldShowAnswer"]),
     ...mapGetters(["examShouldShowAnswer"]),
@@ -158,6 +179,11 @@ export default {
     },
     },
     showAnswer() {
     showAnswer() {
       this.isShowAnswer = !this.isShowAnswer;
       this.isShowAnswer = !this.isShowAnswer;
+    },
+    fetchQRCode() {
+      if (this.examQuestion.answerType === "SINGLE_AUDIO") {
+        getQRCode(this.examQuestion.questionId);
+      }
     }
     }
   },
   },
   watch: {
   watch: {
@@ -165,6 +191,51 @@ export default {
       // console.log(this.examQuestion.studentAnswer);
       // console.log(this.examQuestion.studentAnswer);
       this.studentAnswer = this.examQuestion.studentAnswer;
       this.studentAnswer = this.examQuestion.studentAnswer;
     },
     },
+    questionQrCode(value) {
+      // console.log(this.examQuestion.studentAnswer);
+      // console.log("watch", value);
+      if (value.questionId === this.examQuestion.questionId) {
+        this.qrValue = value.qrCode;
+      }
+    },
+    questionQrCodeScanned(value) {
+      // console.log(this.examQuestion.studentAnswer);
+      // console.log("watch", value);
+      if (value.questionId === this.examQuestion.questionId) {
+        this.qrScanned = true;
+      }
+    },
+    questionAudioFileUrl(value) {
+      // console.log(this.examQuestion.studentAnswer);
+      // console.log("watch", value);
+      const examRecordDataId = this.$route.params.examRecordDataId;
+      const that = this;
+      for (const q of value) {
+        if (q.questionId === this.examQuestion.questionId) {
+          this.$http
+            .post(
+              "/api/ecs_oe_student/examControl/confirmAudioUploadSaveStatus",
+              {
+                examRecordDataId,
+                filePath: q.audioFileUrl,
+                questionId: this.examQuestion.questionId
+              }
+            )
+            .then(() => {
+              that.updateExamQuestion({
+                order: that.examQuestion.order,
+                studentAnswer: q.audioFileUrl
+              });
+            })
+            .catch(() => {
+              this.$Message.error({
+                content: "更新音频题答案失败!",
+                duration: 10
+              });
+            });
+        }
+      }
+    },
     studentAnswer() {
     studentAnswer() {
       let realAnswer = null;
       let realAnswer = null;
       if (this.studentAnswer) {
       if (this.studentAnswer) {
@@ -187,6 +258,11 @@ export default {
     }
     }
   },
   },
   computed: {
   computed: {
+    ...mapState([
+      "questionQrCode",
+      "questionQrCodeScanned",
+      "questionAudioFileUrl"
+    ]),
     isSyncState() {
     isSyncState() {
       return this.examQuestion.order == this.$route.params.order;
       return this.examQuestion.order == this.$route.params.order;
     },
     },
@@ -195,7 +271,8 @@ export default {
     }
     }
   },
   },
   components: {
   components: {
-    QuestionBody
+    QuestionBody,
+    qrcode: VueQrcode
   }
   }
 };
 };
 </script>
 </script>

+ 116 - 0
src/features/OnlineExam/Examing/ws.js

@@ -0,0 +1,116 @@
+import store from "@/store";
+import { Message } from "iview";
+
+let ws;
+let shouldReconnect = true;
+let heartbeatId = null;
+const RECONNECT_INTERVAL = 3000;
+const HEARTBEAT_INTERVAL = 50 * 1000;
+
+export function openWS({ examRecordDataId }) {
+  console.log("in openWS", examRecordDataId);
+  console.log("in openWS", store);
+  ws = new WebSocket(
+    "ws://192.168.10.39:8010" +
+      `/audioAnswerWebSocket/${examRecordDataId}/${store.state.user.key}/${
+        store.state.user.token
+      }`
+  );
+
+  ws.onopen = event => {
+    console.log("open ws", event);
+
+    ws.onmessage = processWSMessage;
+
+    ws.onclose = () => {
+      console.log("ws close by server");
+      clearInterval(heartbeatId);
+      if (shouldReconnect) {
+        console.log("close -> reconnect");
+        setTimeout(() => {
+          // tryWSReconnect();
+          openWS({ examRecordDataId });
+        }, RECONNECT_INTERVAL);
+      } else {
+        shouldReconnect = true; // reset shouldReconnect
+      }
+    };
+
+    heartbeat();
+  };
+}
+
+// function tryWSReconnect() {
+//   // socket.close();
+//   openWS();
+// }
+
+function heartbeat() {
+  heartbeatId = setInterval(() => {
+    ws.send(
+      JSON.stringify({
+        eventType: "HEARTBEAT"
+      })
+    );
+  }, HEARTBEAT_INTERVAL);
+}
+
+export function closeWsWithoutReconnect() {
+  shouldReconnect = false;
+  ws.close();
+}
+
+export function getQRCode(questionId) {
+  if (ws.readyState === ws.OPEN) {
+    ws.send(
+      JSON.stringify({
+        eventType: "GET_QR_CODE",
+        questionId
+      })
+    );
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function processWSMessage(event) {
+  console.log("get ws msg: ", event);
+  const res = JSON.parse(event.data);
+  if (res.eventType !== "HEARTBEAT" && !res.isSuccess) {
+    Message.error({
+      content: res.errorMessage,
+      duration: 10,
+      closable: true
+    });
+    return;
+  }
+  switch (res.eventType) {
+    case "HEARTBEAT":
+      console.log("ws heartbeat response from server");
+      break;
+    case "GET_QR_CODE":
+      console.log("get qrcode", res);
+      store.commit("examingHomeModule/setQuestionQrCode", {
+        qrCode: res.data.qrCode,
+        questionId: res.data.questionId
+      });
+      break;
+    case "SCAN_QR_CODE":
+      console.log("wx scanned qrcode", res);
+      store.commit("examingHomeModule/setQuestionQrCodeScanned", {
+        questionId: res.data.questionId
+      });
+      break;
+    case "GET_AUDIO_ANSWER":
+      console.log("get audio url", res);
+      store.commit("examingHomeModule/setQuestionAudioFileUrl", {
+        questionId: res.data.questionId,
+        audioFileUrl: res.data.audioFileUrl
+      });
+      break;
+    case "SYSTEM_ERROR":
+      console.log("ws get error", res);
+      break;
+  }
+}

+ 26 - 1
src/store.js

@@ -30,7 +30,10 @@ const examingHomeModule = {
     remainTime: null,
     remainTime: null,
     snapProcessingCount: 0,
     snapProcessingCount: 0,
     shouldSubmitPaper: false,
     shouldSubmitPaper: false,
-    allAudioPlayTimes: []
+    allAudioPlayTimes: [],
+    questionQrCode: null,
+    questionQrCodeScanned: null,
+    questionAudioFileUrl: []
   },
   },
   mutations: {
   mutations: {
     updateRemainTime(state, remainTime) {
     updateRemainTime(state, remainTime) {
@@ -144,6 +147,28 @@ const examingHomeModule = {
     },
     },
     setShouldSubmitPaper(state) {
     setShouldSubmitPaper(state) {
       state.shouldSubmitPaper = !state.shouldSubmitPaper;
       state.shouldSubmitPaper = !state.shouldSubmitPaper;
+    },
+    setQuestionQrCode(state, payload) {
+      state.questionQrCode = payload;
+    },
+    setQuestionQrCodeScanned(state, payload) {
+      state.questionQrCodeScanned = payload;
+    },
+    setQuestionAudioFileUrl(state, payload) {
+      let ary = state.questionAudioFileUrl;
+      let found = false;
+      for (const i of ary) {
+        if (i.questionId === payload.questionId) {
+          i.audioFileUrl = payload.audioFileUrl;
+          found = true;
+          break;
+        }
+      }
+      if (found) {
+        state.questionAudioFileUrl = [...ary];
+      } else {
+        state.questionAudioFileUrl.push(payload);
+      }
     }
     }
   },
   },
   actions: {},
   actions: {},