Browse Source

离线考试上传作答文件

Michael Wang 7 năm trước cách đây
mục cha
commit
6b0b9ccd9d

+ 10 - 48
src/features/OfflineExam/OfflineExamHome.vue

@@ -6,62 +6,18 @@
     </Breadcrumb>
 
     <div class="home">
-      <i-table border :columns="columns" :data="courses"></i-table>
-
+      <ecs-offline-list :courses="courses"></ecs-offline-list>
     </div>
   </main-layout>
 </template>
 
 <script>
+import EcsOfflineList from "./OfflineExamList.vue";
+
 export default {
   name: "OnlineExamHome",
   data() {
     return {
-      columns: [
-        {
-          title: "课程",
-          key: "courseName"
-        },
-        {
-          title: "专业",
-          key: "specialtyName"
-        },
-        {
-          title: "考试开放时间",
-          key: "start2end"
-        },
-        {
-          title: "状态",
-          key: "fileUrl",
-          render: (h, params) => {
-            if (params.row.fileUrl) {
-              return h(
-                "a",
-                {
-                  attrs: {
-                    href: params.row.fileUrl
-                  },
-                  style: { fontSize: "16px" }
-                },
-                [
-                  h("Icon", {
-                    props: {
-                      type: "ios-cloud-download"
-                    }
-                  }),
-                  h("strong", "下载")
-                ]
-              );
-            } else {
-              return h("div", [h("strong", "未上传")]);
-            }
-          }
-        },
-        {
-          title: "操作",
-          key: "operations"
-        }
-      ],
       courses: []
     };
   },
@@ -69,12 +25,18 @@ export default {
     const res = await this.$http.get("/api/offline_exam/getOfflineCourse");
 
     this.courses = res.data.map(c => ({
+      examRecordId: c.examRecordId,
       courseName: c.courseName,
       specialtyName: c.specialtyName,
-      start2end: c.startTime + " ~ " + c.endTime,
+      startTime: c.startTime,
+      endTime: c.endTime,
       fileUrl: c.studentSubjectiveHtml,
+      paperId: c.paperId,
       operations: ""
     }));
+  },
+  components: {
+    "ecs-offline-list": EcsOfflineList
   }
 };
 </script>

+ 202 - 0
src/features/OfflineExam/OfflineExamList.vue

@@ -0,0 +1,202 @@
+<template>
+  <div class="list">
+    <table>
+
+      <tbody class="list-row">
+        <tr class="list-header qm-primary-strong-text">
+          <td>课程</td>
+          <td>专业</td>
+          <td>考试开放时间</td>
+          <td>状态</td>
+          <td style="max-width: 200px">操作</td>
+        </tr>
+
+        <tr v-for="(course) in courses" :key="course.examId">
+          <td>{{ course.courseName }}</td>
+          <td>{{ course.specialtyName }}</td>
+          <td>{{ course.startTime }} <br> ~ <br> {{ course.endTime }}</td>
+          <td>
+            <div v-if="course.fileUrl">
+              <a :href="course.fileUrl">
+                <i-icon type="ios-cloud-download"></i-icon>下载作答</a>
+            </div>
+            <div v-else>
+              未上传
+            </div>
+          </td>
+          <td style="min-width: 180px">
+            <div v-if="course.paperId" style="display: grid; grid-gap: 10px">
+              <i-button class="qm-primary-button">查看试卷</i-button>
+              <i-button class="qm-primary-button">下载试卷</i-button>
+              <i-button class="qm-primary-button">下载答题卡</i-button>
+              <div>
+                <Upload :headers="headers" :data="{fileType: fileType}" :before-upload="handleBeforeUpload" :action="'/api/offline_exam/'+course.examRecordId+'/submit'" :max-size="1024*30" :format="['pdf','zip']" :on-format-error="handleFormatError" :on-exceeded-size="handleMaxSize" :on-success="handleSuccess" :on-error="handleError" :show-upload-list="false">
+                  <Button type="ghost" icon="ios-cloud-upload-outline" class="qm-primary-button" style="width: 100%;">上传作答</Button>
+                </Upload>
+                <div v-if="file !== null">待上传文件: {{ file.name }}
+                  <Button type="text" @click="upload" :loading="loadingStatus">{{ loadingStatus ? '上传中...' : '上传' }}</Button>
+                </div>
+              </div>
+            </div>
+
+            <div v-else style="display: grid; grid-gap: 10px">
+              <i-button class="qm-primary-button">抽取试卷</i-button>
+            </div>
+
+          </td>
+        </tr>
+      </tbody>
+    </table>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "EcsOfflineList",
+  data() {
+    return {
+      headers: {
+        token: window.localStorage.getItem("token"),
+        key: window.localStorage.getItem("key")
+      },
+      file: null,
+      fileType: null,
+      loadingStatus: false
+    };
+  },
+  props: {
+    courses: Array
+  },
+  methods: {
+    fileFormatCheck(file, resolve, reject) {
+      console.time("FileOpen");
+      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";
+          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
+          });
+          // console.log(uploads);
+          if (
+            ["application/zip", "application/pdf"].includes(getMimetype(hex))
+          ) {
+            // console.log("true");
+            resolve();
+          } else {
+            console.log("binary file type check: not zip or pdf");
+            this.$Notice.warning({
+              title: "作答文件损坏",
+              desc: file.name + " 文件无法以 .zip 或 .pdf 读取。"
+            });
+            this.file = null;
+            this.loadingStatus = false;
+            reject();
+          }
+        }
+        console.timeEnd("FileOpen");
+      };
+      const blob = file.slice(0, 4);
+      filereader.readAsArrayBuffer(blob);
+    },
+    upload(file) {
+      this.file = file;
+      return false;
+    },
+    handleSuccess(res, file) {
+      this.file = null;
+      this.loadingStatus = false;
+      this.$Message.success("上传成功");
+    },
+    handleError(res, file) {
+      this.file = null;
+      this.loadingStatus = false;
+      this.$Message.error("上传失败");
+    },
+    handleFormatError(file) {
+      this.$Notice.warning({
+        title: "作答文件格式不对",
+        desc: file.name + " 文件格式不对,请选择 .zip 或 .pdf 文件。"
+      });
+    },
+    handleMaxSize(file) {
+      this.$Notice.warning({
+        title: "超出文件大小限制",
+        desc: file.name + " 太大,作答文件不能超过30M."
+      });
+    },
+    handleBeforeUpload(file) {
+      if (file.type.includes("/pdf")) {
+        this.fileType = "pdf";
+      } else if (file.type.includes("/zip")) {
+        this.fileType = "zip";
+      }
+      this.file = file;
+      this.loadingStatus = true;
+      // const check = this.uploadList.length < 5;
+      // if (!check) {
+      //   this.$Notice.warning({
+      //     title: "Up to five pictures can be uploaded."
+      //   });
+      // }
+      return new Promise((resolve, reject) =>
+        this.fileFormatCheck(file, resolve, reject)
+      );
+      // return true;
+    }
+  }
+};
+</script>
+
+<style scoped>
+.list {
+  border: 1px solid #eeeeee;
+  border-radius: 6px;
+}
+
+.list table {
+  width: 100%;
+  border-collapse: collapse !important;
+  border-spacing: 0;
+}
+.list td {
+  border: 1px solid #eeeeee;
+  border-radius: 6px;
+  border-collapse: separate !important;
+  padding: 10px;
+}
+</style>
+
+<style lang="postcss">
+.list .ivu-upload-select {
+  width: 100%;
+}
+</style>

+ 6 - 2
src/features/OnlineExam/OnlineExamHome.vue

@@ -28,7 +28,11 @@ export default {
         },
         {
           title: "考试开放时间",
-          key: "start2end"
+          key: "start2end",
+          render: (h, params) => {
+            // return h("div", params.row.start2end);
+            return <div domPropsInnerHTML={params.row.start2end} />;
+          }
         },
         {
           title: "剩余考试次数",
@@ -48,7 +52,7 @@ export default {
     this.courses = res.data.map(c => ({
       courseName: c.courseName,
       specialtyName: c.specialtyName,
-      start2end: c.startTime + " ~ " + c.endTime,
+      start2end: c.startTime + "<br> ~ <br>" + c.endTime,
       times: c.allowExamCount,
       operations: ""
     }));

+ 6 - 2
src/features/OnlinePractice/OnlinePracticeHome.vue

@@ -35,7 +35,11 @@ export default {
         },
         {
           title: "考试开放时间",
-          key: "start2end"
+          key: "start2end",
+          render: (h, params) => {
+            // return h("div", params.row.start2end);
+            return <div domPropsInnerHTML={params.row.start2end} />;
+          }
         },
         {
           title: "剩余考试次数",
@@ -55,7 +59,7 @@ export default {
     this.courses = res.data.map(c => ({
       courseName: c.courseName,
       specialtyName: c.specialtyName,
-      start2end: c.startTime + " ~ " + c.endTime,
+      start2end: c.startTime + "<br> ~ <br>" + c.endTime,
       times: c.allowExamCount,
       operations: ""
     }));