浏览代码

添加图片作答方式

Michael Wang 6 年之前
父节点
当前提交
ca7a171837
共有 4 个文件被更改,包括 291 次插入5 次删除
  1. 1 0
      package.json
  2. 73 5
      src/features/OnlineExam/Examing/TextQuestionView.vue
  3. 212 0
      src/features/OnlineExam/Examing/UploadPhotos.vue
  4. 5 0
      yarn.lock

+ 1 - 0
package.json

@@ -23,6 +23,7 @@
     "axios": "^0.18.0",
     "core-js": "^2.6.5",
     "iview": "^3.4.1",
+    "js-md5": "^0.7.3",
     "moment": "^2.24.0",
     "register-service-worker": "^1.6.2",
     "vue": "^2.6.10",

+ 73 - 5
src/features/OnlineExam/Examing/TextQuestionView.vue

@@ -1,5 +1,5 @@
 <template>
-  <div v-if="isSyncState" class="question-view">
+  <div v-if="isSyncState" :key="answerDivKey" class="question-view">
     <question-body
       :questionBody="question.body"
       :examQuestion="examQuestion"
@@ -55,8 +55,7 @@
           ondrop="return false"
           @keydown="disableCtrl"
           :contenteditable="true"
-          v-html="studentAnswer"
-          v-once
+          v-html="originalStudentAnswer"
           @input="$event => textInput($event)"
           @blur="$event => textInput($event)"
           class="stu-answer"
@@ -98,8 +97,22 @@
           <span v-else class="audio-answer-line-height">未上传音频</span>
         </div>
       </div>
-      <div class="reset" style="padding-top: 20px">
-        <i-button type="warning" size="large" @click="studentAnswer = null">
+
+      <!-- FIXME -->
+      <div v-if="this.$store.state.user.rootOrgId === 0">
+        <UploadPhotos
+          :defaultList="
+            photoAnswers.map(v => {
+              return { url: v };
+            })
+          "
+          @on-photo-added="photoAdded"
+          @on-photo-removed="photoRemoved"
+          style="margin-top: 20px; width: 350px;"
+        />
+      </div>
+      <div class="reset" style="padding-top: 20px;">
+        <i-button type="warning" size="large" @click="resetAnswer">
           重置答案
         </i-button>
         <span v-if="examShouldShowAnswer()">
@@ -128,13 +141,16 @@ const { mapMutations, mapGetters, mapState } = createNamespacedHelpers(
   "examingHomeModule"
 );
 import VueQrcode from "@chenfengyuan/vue-qrcode";
+import UploadPhotos from "./UploadPhotos";
 
 export default {
   name: "TextQuestionView",
   data() {
     return {
       studentAnswer: this.examQuestion.studentAnswer,
+      originalStudentAnswer: this.examQuestion.studentAnswer,
       isShowAnswer: false,
+      answerDivKey: Math.random(),
       qrValue: "",
       qrScanned: false
     };
@@ -202,10 +218,33 @@ export default {
     showAnswer() {
       this.isShowAnswer = !this.isShowAnswer;
     },
+    resetAnswer() {
+      this.studentAnswer = null;
+      this.originalStudentAnswer = null;
+      // this.$nextTick(() => {
+      //   // Object.assign(this.$data, this.$options.data());
+      //   // this.$refs.answerDiv.$forceUpdate();
+      //   this.answerDivKey = Math.random();
+      // });
+      this.answerDivKey = Math.random();
+    },
     fetchQRCode() {
       if (this.examQuestion.answerType === "SINGLE_AUDIO") {
         getQRCode(this.examQuestion.order);
       }
+    },
+    photoAdded(url) {
+      // console.log(url);
+      this.photoAnswers = [...this.photoAnswers, url];
+    },
+    photoRemoved(url) {
+      // console.log("to remove: ", url, " from: ", this.photoAnswers);
+      // console.log(
+      //   "this.photoAnswers.filter(v => v !== url)",
+      //   this.photoAnswers.filter(v => v !== url)
+      // );
+      this.photoAnswers = this.photoAnswers.filter(v => v !== url);
+      // console.log(this.photoAnswers);
     }
   },
   watch: {
@@ -255,10 +294,34 @@ export default {
     },
     rightAnswerTransform() {
       return this.question.rightAnswer.join("");
+    },
+    photoAnswers: {
+      get() {
+        if (!this.studentAnswer) return [];
+        const ele = document.createElement("div");
+        ele.innerHTML = this.studentAnswer;
+        const imgs = ele.querySelectorAll(".photo-answer");
+        // if()
+        return [...imgs].map(e => e.src);
+      },
+      set(pSrcs) {
+        let imageStr = pSrcs.map(
+          v =>
+            `<a href='${v}' target='_blank' ><img class='photo-answer' src='${v}' /></a>`
+        );
+        const ele = document.createElement("div");
+        ele.innerHTML = this.studentAnswer || "";
+        const pEle = ele.querySelectorAll(".photo-answers-block");
+        if (pEle) [...pEle].forEach(v => v.remove());
+        console.log(ele.innerHTML);
+        this.studentAnswer =
+          ele.innerHTML + `<div class='photo-answers-block'>${imageStr}</div>`;
+      }
     }
   },
   components: {
     QuestionBody,
+    UploadPhotos,
     qrcode: VueQrcode
   }
 };
@@ -300,3 +363,8 @@ export default {
   vertical-align: text-bottom;
 }
 </style>
+<style>
+.photo-answers-block {
+  display: none;
+}
+</style>

+ 212 - 0
src/features/OnlineExam/Examing/UploadPhotos.vue

@@ -0,0 +1,212 @@
+<template>
+  <div>
+    <div class="demo-upload-list" v-for="item in uploadList" :key="item.url">
+      <template v-if="item.status === 'finished'">
+        <img :src="item.url + '!/both/100x100'" />
+        <div class="demo-upload-list-cover">
+          <Icon
+            type="ios-eye-outline"
+            size="30"
+            @click.native="handleView(item.url)"
+          ></Icon>
+          <Icon
+            type="ios-trash-outline"
+            size="30"
+            @click.native="handleRemove(item)"
+          ></Icon>
+        </div>
+      </template>
+      <template v-else>
+        <Progress
+          v-if="item.showProgress"
+          :percent="item.percentage"
+          hide-info
+        ></Progress>
+      </template>
+    </div>
+    <Upload
+      ref="upload"
+      v-show="this.uploadList.length < 6"
+      :data="headers"
+      :show-upload-list="false"
+      :default-file-list="defaultList2"
+      :on-success="handleSuccess"
+      :format="['jpg', 'jpeg', 'png']"
+      :max-size="5 * 1024"
+      :on-format-error="handleFormatError"
+      :on-exceeded-size="handleMaxSize"
+      :before-upload="handleBeforeUpload"
+      multiple
+      type="drag"
+      :action="this.uploadUrl"
+      style="display: inline-block;width:100px;"
+    >
+      <div style="width: 100px;height:100px;line-height: 100px;">
+        <Icon type="ios-camera" size="40"></Icon>
+      </div>
+    </Upload>
+    <Modal title="View Image" v-model="visible">
+      <img :src="imgUrl" v-if="visible" style="width: 100%" />
+    </Modal>
+    <div>最多上传6张图片</div>
+  </div>
+</template>
+
+<script>
+import MD5 from "js-md5";
+
+export default {
+  name: "UploadPhotos",
+  props: ["defaultList"],
+  data() {
+    return {
+      // defaultList: [
+      //   {
+      //     name: "a42bdcc1178e62b4694c830f028db5c0",
+      //     url:
+      //       "https://o5wwk8baw.qnssl.com/a42bdcc1178e62b4694c830f028db5c0/avatar"
+      //   },
+      //   {
+      //     name: "bc7521e033abdd1e92222d733590f104",
+      //     url:
+      //       "https://o5wwk8baw.qnssl.com/bc7521e033abdd1e92222d733590f104/avatar"
+      //   }
+      // ],
+      imgUrl: "",
+      visible: false,
+      defaultList2: [...this.defaultList],
+      uploadList: [],
+      uploadUrl: "",
+      headers: {}
+    };
+  },
+  methods: {
+    handleView(name) {
+      this.imgUrl = name;
+      this.visible = true;
+    },
+    handleRemove(file) {
+      const fileList = this.$refs.upload.fileList;
+      this.$emit("on-photo-removed", file.url);
+      this.$refs.upload.fileList.splice(fileList.indexOf(file), 1);
+    },
+    handleSuccess(res, file) {
+      file.url = this.resultUrl;
+      // file.name = "7eb99afb9d5f317c912f08b5212fd69a";
+      this.$emit("on-photo-added", this.resultUrl);
+    },
+    handleFormatError(file) {
+      this.$Notice.warning({
+        title: "只接受jpg/jpeg/png图片文件",
+        desc: file.name
+      });
+    },
+    handleMaxSize(file) {
+      const MAX_UPLOAD_SIZE = 5;
+      this.$Notice.warning({
+        title: "文件过大",
+        desc: file.name + `超过${MAX_UPLOAD_SIZE}M.`
+      });
+    },
+    async handleBeforeUpload(file) {
+      const MAX_UPLOADS_NUM = 6;
+      const check = this.uploadList.length < MAX_UPLOADS_NUM;
+      if (!check) {
+        this.$Notice.warning({
+          title: `最多上传${MAX_UPLOADS_NUM}张照片。`
+        });
+        // return false;
+        return Promise.resolve(false);
+      }
+
+      function readAsArrayBuffer(file) {
+        return new Promise(function(resolve) {
+          var reader = new FileReader();
+          reader.readAsArrayBuffer(file);
+          reader.onload = function(e) {
+            resolve(e.target.result);
+          };
+        });
+      }
+
+      const buffer = await readAsArrayBuffer(file);
+
+      // console.log(buffer);
+      // var view1 = new Uint8Array(buffer);
+      // console.log(buffer[0], buffer[1], buffer[429721]);
+      const fileMd5 = MD5(buffer);
+      // console.log(fileMd5);
+
+      const examRecordDataId = this.$route.params.examRecordDataId - 0;
+      const order = this.$route.params.order - 0;
+      const fileSuffix = file.name.split(".").pop();
+      const params = new URLSearchParams();
+      params.append("examRecordDataId", examRecordDataId);
+      params.append("order", order);
+      params.append("fileSuffix", fileSuffix);
+      params.append("fileMd5", fileMd5);
+      const res = await this.$http.post(
+        "/api/ecs_oe_student/examControl/upyunSignature",
+        params,
+        {
+          examRecordDataId,
+          order,
+          fileSuffix,
+          fileMd5
+        },
+        { headers: { "content-type": "application/x-www-form-urlencoded" } }
+      );
+
+      // console.log(res);
+      this.headers = {
+        policy: res.data.policy,
+        authorization: res.data.signature
+      };
+      this.uploadUrl = res.data.uploadUrl;
+      this.resultUrl = res.data.upyunFileDomain + res.data.filePath;
+      return check;
+    }
+  },
+  mounted() {
+    this.uploadList = this.$refs.upload.fileList;
+  }
+};
+</script>
+<style scoped>
+.demo-upload-list {
+  display: inline-block;
+  width: 100px;
+  height: 100px;
+  text-align: center;
+  line-height: 100px;
+  border: 1px solid transparent;
+  border-radius: 4px;
+  overflow: hidden;
+  background: #fff;
+  position: relative;
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
+  margin-right: 4px;
+}
+.demo-upload-list img {
+  width: 100%;
+  height: 100%;
+}
+.demo-upload-list-cover {
+  display: none;
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: rgba(0, 0, 0, 0.6);
+}
+.demo-upload-list:hover .demo-upload-list-cover {
+  display: block;
+}
+.demo-upload-list-cover i {
+  color: #fff;
+  font-size: 20px;
+  cursor: pointer;
+  margin: 0 2px;
+}
+</style>

+ 5 - 0
yarn.lock

@@ -6256,6 +6256,11 @@ js-levenshtein@^1.1.3:
   resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
   integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==
 
+js-md5@^0.7.3:
+  version "0.7.3"
+  resolved "https://registry.yarnpkg.com/js-md5/-/js-md5-0.7.3.tgz#b4f2fbb0b327455f598d6727e38ec272cd09c3f2"
+  integrity sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ==
+
 js-message@1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/js-message/-/js-message-1.0.5.tgz#2300d24b1af08e89dd095bc1a4c9c9cfcb892d15"