OfflineExamUpload.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. <template>
  2. <div>
  3. <Upload
  4. ref="uploadComp"
  5. :headers="headers"
  6. :data="{ fileType: fileType }"
  7. :before-upload="handleBeforeUpload"
  8. :action="
  9. '/api/ecs_oe_student/offlineExam/submitPaper?examRecordDataId=' +
  10. course.examRecordDataId
  11. "
  12. :max-size="1024 * 30"
  13. :format="uploadFileFormat"
  14. :accept="uploadFileAccept"
  15. :on-format-error="handleFormatError"
  16. :on-exceeded-size="handleMaxSize"
  17. :on-success="handleSuccess"
  18. :on-error="handleError"
  19. :show-upload-list="false"
  20. >
  21. <i-button
  22. icon="ios-cloud-upload-outline"
  23. class="qm-primary-button"
  24. style="width: 100%;"
  25. >
  26. 上传作答
  27. </i-button>
  28. </Upload>
  29. <div v-if="file !== null && loadingStatus">
  30. 待上传文件: {{ file.name }}
  31. <i-button :loading="loadingStatus">
  32. {{ loadingStatus ? "上传中..." : "上传" }}
  33. </i-button>
  34. </div>
  35. </div>
  36. </template>
  37. <script>
  38. export default {
  39. name: "EcsOfflineUpload",
  40. data() {
  41. return {
  42. headers: {
  43. token: window.sessionStorage.getItem("token"),
  44. key: window.localStorage.getItem("key"),
  45. },
  46. file: null,
  47. fileType: null,
  48. loadingStatus: false,
  49. uploadFileFormat: [],
  50. uploadFileAccept: "",
  51. };
  52. },
  53. props: {
  54. course: Object,
  55. },
  56. async created() {
  57. const res = await this.$http.get(
  58. "/api/ecs_exam_work/exam/examOrgPropertyFromCache4StudentSession/" +
  59. this.course.examId +
  60. `/OFFLINE_UPLOAD_FILE_TYPE`
  61. );
  62. this.uploadFileFormat = res.data.OFFLINE_UPLOAD_FILE_TYPE || [];
  63. this.uploadFileFormat = this.uploadFileFormat.map(v => v.toLowerCase());
  64. this.uploadFileAccept = this.uploadFileFormat
  65. .map(v => "application/" + v)
  66. .join();
  67. },
  68. methods: {
  69. fileFormatCheck(file, resolve, reject) {
  70. function getMimetype(signature) {
  71. switch (signature) {
  72. case "89504E47":
  73. return "image/png";
  74. case "47494638":
  75. return "image/gif";
  76. case "25504446":
  77. return "application/pdf";
  78. case "FFD8FFDB":
  79. case "FFD8FFE0":
  80. case "FFD8FFE1":
  81. return "image/jpeg";
  82. case "504B0304":
  83. return "application/zip";
  84. case "504B34":
  85. return "application/zip";
  86. default:
  87. return "Unknown filetype";
  88. }
  89. }
  90. const filereader = new FileReader();
  91. let uploads = [];
  92. filereader.onloadend = evt => {
  93. if (evt.target.readyState === FileReader.DONE) {
  94. const uint = new Uint8Array(evt.target.result);
  95. let bytes = [];
  96. uint.forEach(byte => {
  97. bytes.push(byte.toString(16));
  98. });
  99. const hex = bytes.join("").toUpperCase();
  100. uploads.push({
  101. filename: file.name,
  102. filetype: file.type ? file.type : "Unknown/Extension missing",
  103. binaryFileType: getMimetype(hex),
  104. hex: hex,
  105. });
  106. if (["application/pdf"].includes(getMimetype(hex))) {
  107. if (!file.name.endsWith(".pdf")) {
  108. this.loadingStatus = false;
  109. this.$Notice.warning({
  110. title: "文件内容与文件的后缀不一致",
  111. // desc: file.name + " 文件是pdf文档,但文件名后缀不是.pdf!"
  112. });
  113. this.file = null;
  114. reject("文件内容与文件的后缀不一致,请确认文件是pdf文档!");
  115. } else {
  116. resolve();
  117. }
  118. } else if (["application/zip"].includes(getMimetype(hex))) {
  119. if (!file.name.endsWith(".zip")) {
  120. this.loadingStatus = false;
  121. // this.$refs.uploadComp.fileList.splice(0);
  122. // this.$refs.uploadComp.fileList = [];
  123. this.$Notice.warning({
  124. title: "文件内容与文件的后缀不一致",
  125. // desc: file.name + " 文件是zip压缩包,但文件名后缀不是.zip!"
  126. });
  127. this.file = null;
  128. reject("文件内容与文件的后缀不一致,请确认文件是zip压缩包!");
  129. } else {
  130. resolve();
  131. }
  132. } else {
  133. console.log("binary file type check: not zip or pdf");
  134. window._hmt.push([
  135. "_trackEvent",
  136. "离线考试页面",
  137. "上传作答",
  138. "文件格式非zip或pdf",
  139. ]);
  140. this.$Notice.warning({
  141. title: "作答文件损坏",
  142. desc:
  143. file.name +
  144. " 文件无法以 " +
  145. this.uploadFileFormat.join(" 或 ") +
  146. " 格式读取。",
  147. });
  148. this.file = null;
  149. this.loadingStatus = false;
  150. reject("作答文件损坏");
  151. }
  152. }
  153. };
  154. const blob = file.slice(0, 4);
  155. filereader.readAsArrayBuffer(blob);
  156. },
  157. handleSuccess() {
  158. window._hmt.push(["_trackEvent", "离线考试页面", "上传作答", "上传成功"]);
  159. this.file = null;
  160. this.loadingStatus = false;
  161. this.$Message.success({
  162. content: "上传成功",
  163. duration: 5,
  164. closable: true,
  165. });
  166. this.$emit("reloadList");
  167. },
  168. handleError(error, file) {
  169. window._hmt.push(["_trackEvent", "离线考试页面", "上传作答", "上传失败"]);
  170. this.file = null;
  171. this.loadingStatus = false;
  172. console.log(error);
  173. this.$Message.error({
  174. content: (file && file.desc) || "上传失败",
  175. duration: 15,
  176. closable: true,
  177. });
  178. },
  179. handleFormatError(file) {
  180. this.file = null;
  181. this.loadingStatus = false;
  182. this.$Notice.warning({
  183. title: "作答文件格式不对",
  184. desc:
  185. file.name +
  186. " 文件格式不对,请选择 " +
  187. this.uploadFileFormat.join(" 或 ") +
  188. " 文件。",
  189. });
  190. },
  191. handleMaxSize(file) {
  192. this.file = null;
  193. this.loadingStatus = false;
  194. this.$Notice.warning({
  195. title: "超出文件大小限制",
  196. desc: file.name + " 太大,作答文件不能超过30M.",
  197. });
  198. },
  199. handleBeforeUpload(file) {
  200. const suffix = file.name.split(".").pop();
  201. if (suffix.toLowerCase() !== suffix) {
  202. this.$Notice.error({
  203. title: "文件名后缀必须是小写",
  204. desc: file.name + " 文件名后缀必须是小写。",
  205. });
  206. return Promise.reject("file suffix should be lower case");
  207. }
  208. return new Promise((resolve, reject) => {
  209. if (this.course.offlineFileUrl) {
  210. this.$Modal.confirm({
  211. title: "已有作答附件,是否覆盖?",
  212. onCancel: () => reject(-1),
  213. onOk: () => resolve(),
  214. });
  215. } else {
  216. resolve();
  217. }
  218. }).then(() => {
  219. if (file.type.includes("/pdf")) {
  220. this.fileType = "pdf";
  221. } else if (file.type.includes("/zip")) {
  222. this.fileType = "zip";
  223. }
  224. this.file = file;
  225. this.loadingStatus = true;
  226. return new Promise((resolve, reject) =>
  227. this.fileFormatCheck(file, resolve, reject)
  228. );
  229. });
  230. },
  231. },
  232. };
  233. </script>
  234. <style lang="postcss">
  235. .list .ivu-upload-select {
  236. width: 100%;
  237. }
  238. </style>