OfflineExamUpload.vue 7.6 KB

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