Selaa lähdekoodia

cug: offline-exam prepare image upload

Michael Wang 5 vuotta sitten
vanhempi
commit
1c08fcd9b6

+ 4 - 3
src/components/MainLayout/MainLayout.vue

@@ -88,7 +88,7 @@
         </li>
         <li v-if="!isEpcc">
           <router-link class="link" to="/offline-exam">
-            离线考试
+            {{ isCug ? "考查课考核" : "离线考试" }}
           </router-link>
         </li>
         <li v-if="!isEpcc">
@@ -130,7 +130,7 @@ export default {
   },
   computed: {
     ...mapState(["user", "siteMessages", "QECSConfig"]),
-    ...mapGetters(["isEpcc"]),
+    ...mapGetters(["isEpcc", "isCug"]),
     messageUnread() {
       return this.siteMessages.filter(v => v.hasRead === false).length;
     },
@@ -251,7 +251,8 @@ export default {
   background-image: url(./link.png);
   background-repeat: no-repeat;
   background-position: 30px 50%;
-  /* margin-left: -40px; */
+  padding-left: 45px;
+  text-align: left;
   padding-right: 20px;
   line-height: 40px;
   width: 100%;

+ 2 - 0
src/constants/constants.js

@@ -7,3 +7,5 @@ export const VUE_APP_WK_SERVER_SOCKET_FOR_AUDIO =
   process.env.VUE_APP_WK_SERVER_SOCKET_FOR_AUDIO;
 export const FACE_API_MODEL_PATH = "/models/20190620/";
 export const EPCC_DOMAIN = "iepcc-ps.ecs.qmth.com.cn";
+// export const CUG_DOMAIN = "cug.ecs.qmth.com.cn";
+export const CUG_DOMAIN = "ecs-test.qmth.com.cn";

+ 10 - 1
src/features/OfflineExam/OfflineExamList.vue

@@ -61,7 +61,13 @@
                     download
                   >下载答题卡</a>
                 </i-button> -->
+                <ecs-offline-exam-upload-cug
+                  v-if="isCug"
+                  :course="course"
+                  @reloadList="$emit('reloadList')"
+                ></ecs-offline-exam-upload-cug>
                 <ecs-offline-exam-upload
+                  v-else
                   :course="course"
                   @reloadList="$emit('reloadList')"
                 ></ecs-offline-exam-upload>
@@ -100,17 +106,19 @@
 </template>
 
 <script>
-import { mapState as globalMapState } from "vuex";
+import { mapState as globalMapState, mapGetters } from "vuex";
 import {
   TK_SERVER_HTML_URL,
   TK_SERVER_API_URL,
 } from "@/constants/constants.js";
 import OfflineExamUpload from "./OfflineExamUpload.vue";
+import OfflineExamUploadCug from "./OfflineExamUploadCug.vue";
 
 export default {
   name: "EcsOfflineList",
   components: {
     "ecs-offline-exam-upload": OfflineExamUpload,
+    "ecs-offline-exam-upload-cug": OfflineExamUploadCug,
   },
   props: {
     courses: {
@@ -127,6 +135,7 @@ export default {
   },
   computed: {
     ...globalMapState(["user", "timeDifference"]),
+    ...mapGetters(["isCug"]),
   },
   methods: {
     async enterExam(course) {

+ 268 - 0
src/features/OfflineExam/OfflineExamUploadCug.vue

@@ -0,0 +1,268 @@
+<template>
+  <div>
+    <Upload
+      ref="uploadComp"
+      :headers="headers"
+      :data="{ fileType: fileType }"
+      :before-upload="handleBeforeUpload"
+      :action="
+        '/api/ecs_oe_admin/offlineExam/submitPaper?examRecordDataId=' +
+          course.examRecordDataId
+      "
+      :max-size="1024 * 30"
+      :format="uploadFileFormat"
+      :accept="uploadFileAccept"
+      :on-format-error="handleFormatError"
+      :on-exceeded-size="handleMaxSize"
+      :on-success="handleSuccess"
+      :on-error="handleError"
+      :show-upload-list="false"
+    >
+      <i-button
+        icon="ios-cloud-upload-outline"
+        class="qm-primary-button"
+        style="width: 100%;"
+      >
+        上传作答
+      </i-button>
+    </Upload>
+    <div v-if="file !== null && loadingStatus">
+      待上传文件: {{ file.name }}
+      <i-button :loading="loadingStatus">
+        {{ loadingStatus ? "上传中..." : "上传" }}
+      </i-button>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "EcsOfflineUploadCug",
+  props: {
+    course: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      headers: {
+        token: window.sessionStorage.getItem("token"),
+        key: window.localStorage.getItem("key"),
+      },
+      file: null,
+      fileType: null,
+      loadingStatus: false,
+      uploadFileFormat: [],
+      uploadFileAccept: "",
+    };
+  },
+  async created() {
+    const res = await this.$http.get(
+      "/api/ecs_exam_work/exam/getExamPropertyFromCacheByStudentSession/" +
+        this.course.examId +
+        `/OFFLINE_UPLOAD_FILE_TYPE`
+    );
+
+    this.uploadFileFormat =
+      (res.data.OFFLINE_UPLOAD_FILE_TYPE &&
+        JSON.parse(res.data.OFFLINE_UPLOAD_FILE_TYPE)) ||
+      [];
+    this.uploadFileFormat.push(...["jpeg", "jpg"]);
+    this.uploadFileFormat = this.uploadFileFormat.map(v => v.toLowerCase());
+    this.uploadFileAccept =
+      "image/jpeg," + this.uploadFileFormat.map(v => "application/" + v).join();
+    if (this.uploadFileAccept.includes("zip")) {
+      this.uploadFileAccept = ".zip," + this.uploadFileAccept;
+    }
+  },
+  methods: {
+    fileFormatCheck(file, resolve, reject) {
+      function getMimetype(signature) {
+        switch (signature) {
+          case "89504E47":
+            return "image/png";
+          case "47494638":
+            return "image/gif";
+          case "25504446":
+            return "application/pdf";
+          case "FFD8FFDB":
+          case "FFD8FFE0":
+          case "FFD8FFE1":
+            return "image/jpeg";
+          case "504B0304":
+            return "application/zip";
+          case "504B34":
+            return "application/zip";
+          default:
+            return "Unknown filetype";
+        }
+      }
+
+      const filereader = new FileReader();
+      let uploads = [];
+      filereader.onloadend = evt => {
+        if (evt.target.readyState === FileReader.DONE) {
+          const uint = new Uint8Array(evt.target.result);
+          let bytes = [];
+          uint.forEach(byte => {
+            bytes.push(byte.toString(16));
+          });
+          const hex = bytes.join("").toUpperCase();
+          uploads.push({
+            filename: file.name,
+            filetype: file.type ? file.type : "Unknown/Extension missing",
+            binaryFileType: getMimetype(hex),
+            hex: hex,
+          });
+
+          if (["application/pdf"].includes(getMimetype(hex))) {
+            if (!file.name.endsWith(".pdf")) {
+              this.loadingStatus = false;
+              this.$Notice.warning({
+                title: "文件内容与文件的后缀不一致",
+                // desc: file.name + " 文件是pdf文档,但文件名后缀不是.pdf!"
+              });
+              this.file = null;
+              reject("文件内容与文件的后缀不一致,请确认文件是pdf文档!");
+            } else {
+              resolve();
+            }
+          } else if (["application/zip"].includes(getMimetype(hex))) {
+            if (!file.name.endsWith(".zip")) {
+              this.loadingStatus = false;
+              // this.$refs.uploadComp.fileList.splice(0);
+              // this.$refs.uploadComp.fileList = [];
+              this.$Notice.warning({
+                title: "文件内容与文件的后缀不一致",
+                // desc: file.name + " 文件是zip压缩包,但文件名后缀不是.zip!"
+              });
+              this.file = null;
+              reject("文件内容与文件的后缀不一致,请确认文件是zip压缩包!");
+            } else {
+              resolve();
+            }
+          } else if (["image/jpeg"].includes(getMimetype(hex))) {
+            if (file.name.endsWith(".jpeg") || file.name.endsWith(".jpg")) {
+              resolve();
+            } else {
+              this.loadingStatus = false;
+              // this.$refs.uploadComp.fileList.splice(0);
+              // this.$refs.uploadComp.fileList = [];
+              this.$Notice.warning({
+                title: "文件内容与文件的后缀不一致",
+              });
+              this.file = null;
+              reject("文件内容与文件的后缀不一致,请确认文件是jpg文件!");
+            }
+          } else {
+            console.log("binary file type check: not zip or pdf");
+            window._hmt.push([
+              "_trackEvent",
+              "离线考试页面",
+              "上传作答",
+              "文件格式非zip或pdf",
+            ]);
+            this.$Notice.warning({
+              title: "作答文件损坏",
+              desc:
+                file.name +
+                " 文件无法以 " +
+                this.uploadFileFormat.join(" 或 ") +
+                " 格式读取。",
+            });
+            this.file = null;
+            this.loadingStatus = false;
+            reject("作答文件损坏");
+          }
+        }
+      };
+      const blob = file.slice(0, 4);
+      filereader.readAsArrayBuffer(blob);
+    },
+    handleSuccess() {
+      window._hmt.push(["_trackEvent", "离线考试页面", "上传作答", "上传成功"]);
+      this.file = null;
+      this.loadingStatus = false;
+      this.$Message.success({
+        content: "上传成功",
+        duration: 5,
+        closable: true,
+      });
+      this.$emit("reloadList");
+    },
+    handleError(error, file) {
+      window._hmt.push(["_trackEvent", "离线考试页面", "上传作答", "上传失败"]);
+      this.file = null;
+      this.loadingStatus = false;
+      console.log(error);
+      this.$Message.error({
+        content: (file && file.desc) || "上传失败",
+        duration: 15,
+        closable: true,
+      });
+    },
+    handleFormatError(file) {
+      this.file = null;
+      this.loadingStatus = false;
+      this.$Notice.warning({
+        title: "作答文件格式不对",
+        desc:
+          file.name +
+          " 文件格式不对,请选择 " +
+          this.uploadFileFormat.join(" 或 ") +
+          "  文件。",
+      });
+    },
+    handleMaxSize(file) {
+      this.file = null;
+      this.loadingStatus = false;
+      this.$Notice.warning({
+        title: "超出文件大小限制",
+        desc: file.name + " 太大,作答文件不能超过30M.",
+      });
+    },
+    handleBeforeUpload(file) {
+      const suffix = file.name.split(".").pop();
+      if (suffix.toLowerCase() !== suffix) {
+        this.$Notice.error({
+          title: "文件名后缀必须是小写",
+          desc: file.name + " 文件名后缀必须是小写。",
+        });
+        return Promise.reject("file suffix should be lower case");
+      }
+
+      return new Promise((resolve, reject) => {
+        if (this.course.offlineFileUrl) {
+          this.$Modal.confirm({
+            title: "已有作答附件,是否覆盖?",
+            onCancel: () => reject(-1),
+            onOk: () => resolve(),
+          });
+        } else {
+          resolve();
+        }
+      }).then(() => {
+        if (file.type.includes("/pdf")) {
+          this.fileType = "pdf";
+        } else if (file.type.includes("/zip")) {
+          this.fileType = "zip";
+        }
+        this.file = file;
+        this.loadingStatus = true;
+        return new Promise((resolve, reject) =>
+          this.fileFormatCheck(file, resolve, reject)
+        );
+      });
+    },
+  },
+};
+</script>
+
+<style lang="postcss">
+.list .ivu-upload-select {
+  width: 100%;
+}
+</style>

+ 4 - 1
src/store.js

@@ -1,6 +1,6 @@
 import Vue from "vue";
 import Vuex from "vuex";
-import { EPCC_DOMAIN } from "@/constants/constants";
+import { EPCC_DOMAIN, CUG_DOMAIN } from "@/constants/constants";
 
 Vue.use(Vuex);
 
@@ -238,5 +238,8 @@ export default new Vuex.Store({
     isEpcc(state) {
       return state.user.schoolDomain === EPCC_DOMAIN;
     },
+    isCug(state) {
+      return state.user.schoolDomain === CUG_DOMAIN;
+    },
   },
 });