ImageDownload.vue 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. <template>
  2. <div class="title title_grey cl">
  3. <h2>图片下载中 …</h2>
  4. </div>
  5. <div class="progress-box">
  6. <h3>正在下载图片,请耐心等候 ~</h3>
  7. <div class="progress">
  8. <a-progress :percent="Math.round((finishedCount / totalCount) * 100)" />
  9. </div>
  10. <p>
  11. 已下载图片:<b>{{ finishedCount }}</b> / 错误图片:<b>{{ errorCount }}</b>
  12. / 全部图片:<b>{{ totalCount }}</b>
  13. </p>
  14. </div>
  15. </template>
  16. <script setup lang="ts">
  17. import { store } from "@/store";
  18. import { onMounted, ref, watch } from "vue";
  19. import { Modal } from "ant-design-vue";
  20. import { useRouter } from "vue-router";
  21. const router = useRouter();
  22. import { getStudents, countStudents, getPackages } from "@/api/api";
  23. import { httpApp } from "@/plugins/axiosApp";
  24. import mustache from "mustache";
  25. async function getImageDim(
  26. blob: Blob
  27. ): Promise<[width: number, height: number]> {
  28. return new Promise((res) => {
  29. const img = new Image();
  30. img.src = URL.createObjectURL(blob);
  31. img.onload = () => {
  32. URL.revokeObjectURL(img.src);
  33. // console.log(img.width);
  34. res([img.width, img.height]);
  35. };
  36. });
  37. }
  38. // cache images
  39. async function cacheImages(urls: string) {
  40. let allPromiseCount = 0;
  41. let settledPromiseCount = 0;
  42. const MAX_CONCURRENT = 6;
  43. async function sleep() {
  44. if (allPromiseCount - settledPromiseCount >= MAX_CONCURRENT) {
  45. console.log("sleep because cache images thread is full");
  46. await new Promise((res) => setTimeout(res, 300));
  47. await sleep();
  48. }
  49. }
  50. for (const url of urls) {
  51. allPromiseCount++;
  52. await sleep();
  53. // console.log(url);
  54. httpApp
  55. .get(url, {
  56. responseType: "blob",
  57. headers: {
  58. // 怀疑 electron 有问题,这里没生效
  59. "Cache-Control": "no-cache",
  60. },
  61. })
  62. .catch((e) => {
  63. console.log(e, "cache error");
  64. })
  65. .finally(() => {
  66. settledPromiseCount++;
  67. });
  68. }
  69. }
  70. const totalCount = ref(0);
  71. let finishedCount = ref(0);
  72. let errorCount = ref(0);
  73. let students = [];
  74. const config = store.pageInputs["/image-download"];
  75. onMounted(async () => {
  76. const storePassedToNodeJs = JSON.parse(JSON.stringify(store));
  77. try {
  78. if (config.type === "1") {
  79. console.log("download start ", Date.now());
  80. const res = await countStudents(store.env.examId, {
  81. upload: true,
  82. withSheetUrl: true,
  83. withScoreDetail: config.watermark,
  84. withGroupScoreTrack: config.watermark && config.trackMode,
  85. examNumberIn: config.examNumber ?? "",
  86. subjectCodeIn: config.subjectCode ?? "",
  87. });
  88. totalCount.value = res.data;
  89. for (
  90. let pageNumber = 0;
  91. pageNumber * 10 < totalCount.value;
  92. pageNumber++
  93. ) {
  94. const resStudents = await getStudents(
  95. store.env.examId,
  96. pageNumber + 1,
  97. 10,
  98. {
  99. upload: true,
  100. withSheetUrl: true,
  101. withScoreDetail: config.watermark,
  102. withGroupScoreTrack: config.watermark && config.trackMode === "1",
  103. examNumberIn: config.examNumber ?? "",
  104. subjectCodeIn: config.subjectCode ?? "",
  105. }
  106. );
  107. students = resStudents.data;
  108. const urls = students.reduce(
  109. (accumulator, stu) => accumulator.concat(stu.sheetUrls),
  110. []
  111. );
  112. cacheImages(urls);
  113. for (const student of students) {
  114. for (const sheetUrl of student.sheetUrls) {
  115. try {
  116. const index = student.sheetUrls.indexOf(sheetUrl);
  117. student.index = index + 1;
  118. student.examId = store.env.examId;
  119. const filePath = window.electron.join(
  120. config.dir,
  121. mustache.render(config.template, student)
  122. );
  123. if (config.append && window.electron.existsSync(filePath)) {
  124. console.log(filePath + " already exists");
  125. // 执行到这里时,可能图片已经cache了
  126. urls.splice(urls.indexOf(sheetUrl), 1);
  127. // console.log(urls);
  128. continue;
  129. }
  130. const imageRes = await httpApp.get(sheetUrl, {
  131. responseType: "blob",
  132. headers: {
  133. "Cache-Control": "no-cache",
  134. },
  135. });
  136. const [width, height] = await getImageDim(imageRes.data);
  137. const arrayBuffer = await imageRes.data.arrayBuffer();
  138. // console.log(imageRes.data);
  139. // console.log(await imageRes.data.arrayBuffer());
  140. // console.log(new Uint8Array(await imageRes.data.arrayBuffer()));
  141. await window.electron.addWatermark(
  142. storePassedToNodeJs,
  143. arrayBuffer,
  144. width,
  145. height,
  146. filePath,
  147. student,
  148. index + 1,
  149. config.trackMode,
  150. config.x,
  151. config.y
  152. );
  153. } catch (error) {
  154. errorCount.value += 1;
  155. if (config.failover) {
  156. throw error;
  157. } else {
  158. console.log(student, error);
  159. continue;
  160. }
  161. }
  162. }
  163. // 下载完一个学生
  164. finishedCount.value += 1;
  165. }
  166. }
  167. console.log("all end ", Date.now());
  168. } else if (config.type === "2") {
  169. await processPackage();
  170. }
  171. const modal = Modal.success({});
  172. modal.update({
  173. title: "图片下载完成",
  174. content: "完成",
  175. onOk: () => router.back(),
  176. });
  177. } catch (error) {
  178. const modal = Modal.error({});
  179. console.log(error);
  180. modal.update({
  181. title: "图片下载出错",
  182. content: error.message || error,
  183. onOk: () => router.back(),
  184. });
  185. }
  186. });
  187. async function processPackage() {
  188. const res = await getPackages(store.env.examId, true, true);
  189. const array = res.data;
  190. totalCount.value = array.length;
  191. const urls = array.reduce((accumulator, p) => accumulator.concat(p.urls), []);
  192. cacheImages(urls);
  193. for (let i = 0; i < array.length; i++) {
  194. const p = array[i];
  195. p.examId = store.env.examId;
  196. for (let i = 0; i < p.urls.length; i++) {
  197. try {
  198. const index = i + 1;
  199. p.index = index;
  200. const filePath = window.electron.join(
  201. config.dir,
  202. mustache.render(config.template, p)
  203. );
  204. if (config.append && window.electron.existsSync(filePath)) {
  205. console.log(filePath + " already exists");
  206. urls.splice(urls.indexOf(p.urls[i]), 1);
  207. continue;
  208. }
  209. const imageRes = await httpApp.get(p.urls[i], {
  210. responseType: "blob",
  211. });
  212. await window.electron.saveImage(
  213. JSON.parse(JSON.stringify(store)),
  214. await imageRes.data.arrayBuffer(),
  215. filePath
  216. );
  217. } catch (error) {
  218. errorCount.value += 1;
  219. if (config.failover) {
  220. throw error;
  221. } else {
  222. console.log(p, error);
  223. continue;
  224. }
  225. }
  226. }
  227. finishedCount.value += 1;
  228. }
  229. }
  230. </script>