ImageDownload.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  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, onUnmounted, ref } from "vue";
  19. import { Modal } from "ant-design-vue";
  20. import { useRouter } from "vue-router";
  21. const router = useRouter();
  22. import { countStudents, getPackages, getStudents } 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. .then(() => console.debug("downloaded ", url))
  63. .catch((e) => {
  64. console.log(e, "cache error");
  65. })
  66. .finally(() => {
  67. settledPromiseCount++;
  68. });
  69. }
  70. }
  71. const totalCount = ref(0);
  72. let finishedCount = ref(0);
  73. let errorCount = ref(0);
  74. let students = [];
  75. const config = store.pageInputs["/image-download"];
  76. // console.log("config::", config);
  77. onMounted(async () => {
  78. const storePassedToNodeJs = JSON.parse(JSON.stringify(store));
  79. const color = storePassedToNodeJs.config.watermark.color;
  80. const nextColor = storePassedToNodeJs.config.watermark.nextColor;
  81. // const otherColor = storePassedToNodeJs.config.watermark.otherColor;
  82. const otherColor = "#ddd";
  83. try {
  84. if (config.type === "1") {
  85. console.log("download start ", Date.now());
  86. const res = await countStudents(store.env.examId, {
  87. upload: true,
  88. withSheetUrl: true,
  89. withScoreDetail: config.watermark,
  90. withMarkTrack: config.watermark,
  91. withGroupScoreTrack: config.watermark && config.trackMode,
  92. examNumberIn: config.examNumber ?? "",
  93. subjectCodeIn: config.subjectCode ?? "",
  94. });
  95. totalCount.value = res.data;
  96. let totalImageDownloadTime = 0;
  97. for (
  98. let pageNumber = 0;
  99. pageNumber * 10 < totalCount.value;
  100. pageNumber++
  101. ) {
  102. const resStudents = await getStudents(
  103. store.env.examId,
  104. pageNumber + 1,
  105. 10,
  106. {
  107. upload: true,
  108. withSheetUrl: true,
  109. withScoreDetail: config.watermark,
  110. withMarkTrack: config.watermark,
  111. withGroupScoreTrack: config.watermark && config.trackMode === "1",
  112. examNumberIn: config.examNumber ?? "",
  113. subjectCodeIn: config.subjectCode ?? "",
  114. sliceConfigFix: config.watermark && config.trackMode === "2",
  115. }
  116. );
  117. students = resStudents.data;
  118. const urls = students.reduce(
  119. (accumulator, stu) => accumulator.concat(stu.sheetUrls),
  120. []
  121. );
  122. // cacheImages(urls);
  123. for (const student of students) {
  124. let allTags = Object.values(student.tags || {})
  125. .filter((x) => !!x)
  126. .flat()
  127. .filter((v: any) => v.userId != 0);
  128. allTags.forEach((item: any) => {
  129. if (
  130. allTags.find((v: any) => {
  131. return (
  132. v.groupNumber == item.groupNumber && v.userId != item.userId
  133. );
  134. })
  135. ) {
  136. item.hide = true;
  137. } else {
  138. item.hide = false;
  139. }
  140. if (
  141. allTags.find((v: any) => {
  142. return (
  143. v.groupNumber == item.groupNumber &&
  144. v.userRole &&
  145. v.userRole !== "MARKER" &&
  146. (item.userRole === "MARKER" || !item.userRole)
  147. );
  148. })
  149. ) {
  150. item.forceHide = true;
  151. }
  152. });
  153. // allTags.sort((a: any, b: any) => {
  154. // return a.userId - b.userId;
  155. // });
  156. let colorMap: any = {};
  157. let headerColorMap: any = {};
  158. for (let i = 0; i < allTags.length; i++) {
  159. const tag: any = allTags[i];
  160. const { groupNumber } = tag;
  161. if (tag.userRole && tag.userRole !== "MARKER") {
  162. if (!headerColorMap[groupNumber + ""]) {
  163. headerColorMap[groupNumber + ""] = {};
  164. headerColorMap[groupNumber + ""][tag.userId + ""] = "#008000";
  165. continue;
  166. } else {
  167. headerColorMap[groupNumber + ""][tag.userId + ""] = "#008000";
  168. continue;
  169. }
  170. }
  171. if (!colorMap[groupNumber + ""]) {
  172. colorMap[groupNumber + ""] = {};
  173. colorMap[groupNumber + ""][tag.userId + ""] = color;
  174. } else {
  175. let targetGroupObjKeys = Object.keys(colorMap[groupNumber + ""]);
  176. const len = targetGroupObjKeys.length;
  177. if (len == 1) {
  178. if (!targetGroupObjKeys.includes(tag.userId + "")) {
  179. colorMap[groupNumber + ""][tag.userId + ""] = nextColor;
  180. } else {
  181. continue;
  182. }
  183. } else if (len > 1) {
  184. if (!targetGroupObjKeys.includes(tag.userId + "")) {
  185. colorMap[groupNumber + ""][tag.userId + ""] = otherColor;
  186. } else {
  187. continue;
  188. }
  189. }
  190. // let c = len === 1 ? nextColor : otherColor;
  191. // colorMap[groupNumber + ""][tag.userId + ""] = c;
  192. }
  193. }
  194. console.log("allTags:", allTags);
  195. // console.log("colorMap:", colorMap);
  196. let resultImgList: any[] = [];
  197. let sheetUrlsLength = (student.sheetUrls || []).length;
  198. for (const sheetUrl of student.sheetUrls) {
  199. if (stopSignal) return;
  200. try {
  201. const index = student.sheetUrls.indexOf(sheetUrl);
  202. student.index = index + 1;
  203. student.examId = store.env.examId;
  204. // const filePath = window.electron.join(
  205. // config.dir,
  206. // mustache.render(config.template, student)
  207. // );
  208. const filePath = [
  209. config.dir,
  210. mustache.render(config.template, student),
  211. ];
  212. if (config.append && window.electron.existsImage(filePath)) {
  213. console.log(filePath + " already exists");
  214. // 执行到这里时,可能图片已经cache了
  215. urls.splice(urls.indexOf(sheetUrl), 1);
  216. // console.log(urls);
  217. continue;
  218. }
  219. console.debug("start ", sheetUrl);
  220. const imageDownloadStartTime = Date.now();
  221. const imageRes = await httpApp.get(sheetUrl, {
  222. responseType: "blob",
  223. headers: {
  224. "Cache-Control": "no-cache",
  225. },
  226. });
  227. totalImageDownloadTime += Date.now() - imageDownloadStartTime;
  228. const [width, height] = await getImageDim(imageRes.data);
  229. const arrayBuffer = await imageRes.data.arrayBuffer();
  230. // console.log(imageRes.data);
  231. // console.log(await imageRes.data.arrayBuffer());
  232. // console.log(new Uint8Array(await imageRes.data.arrayBuffer()));
  233. let onlyUsePdf = config.pdf == "2";
  234. console.log("colorMap:", colorMap);
  235. let fileLocation = await window.electron.addWatermark(
  236. storePassedToNodeJs,
  237. arrayBuffer,
  238. width,
  239. height,
  240. filePath,
  241. student,
  242. index + 1,
  243. config.trackMode,
  244. config.x,
  245. config.y,
  246. colorMap,
  247. headerColorMap,
  248. onlyUsePdf
  249. );
  250. resultImgList.push(fileLocation);
  251. } catch (error) {
  252. window.electron.errorLogger(student, sheetUrl, error);
  253. errorCount.value += 1;
  254. if (config.failover) {
  255. throw error;
  256. } else {
  257. console.log(student, error);
  258. continue;
  259. }
  260. }
  261. }
  262. // 下载完一个学生
  263. finishedCount.value += 1;
  264. if (
  265. resultImgList.length === sheetUrlsLength &&
  266. (config.pdf == "2" || config.pdf == "3")
  267. ) {
  268. let imgName = "";
  269. if (config.template.lastIndexOf("/") > -1) {
  270. imgName = config.template.slice(
  271. config.template.lastIndexOf("/") + 1
  272. );
  273. } else {
  274. imgName = config.template;
  275. }
  276. let n = imgName.slice(0, imgName.lastIndexOf("."));
  277. let pdfName = n
  278. .match(/{{.*?}}/g)
  279. ?.map((item: any) => {
  280. return item.replace("{{", "").replace("}}", "");
  281. })
  282. .filter((v) => !!v)
  283. .map((item: any) => {
  284. return item === "index" ? "" : student[item];
  285. })
  286. .filter((v) => !!v)
  287. .join("-");
  288. window.electron.saveToPDF(resultImgList, [
  289. config.dir,
  290. `/pdfs/${store.env.examId}/${student.subjectCode}/${pdfName}.pdf`,
  291. ]);
  292. }
  293. }
  294. }
  295. console.log(
  296. "all end at ",
  297. Date.now(),
  298. " totalImageDownloadTime: ",
  299. totalImageDownloadTime
  300. );
  301. } else if (config.type === "2") {
  302. await processPackage();
  303. }
  304. const modal = Modal.success({});
  305. modal.update({
  306. title: "图片下载完成",
  307. content: "完成",
  308. onOk: () => router.back(),
  309. });
  310. } catch (error) {
  311. const modal = Modal.error({});
  312. console.log(error);
  313. modal.update({
  314. title: "图片下载出错",
  315. content: error.message || error,
  316. onOk: () => router.back(),
  317. });
  318. }
  319. });
  320. async function processPackage() {
  321. const res = await getPackages(store.env.examId, true, true);
  322. const array = res.data;
  323. totalCount.value = array.length;
  324. const urls = array.reduce((accumulator, p) => accumulator.concat(p.urls), []);
  325. // cacheImages(urls);
  326. for (let i = 0; i < array.length; i++) {
  327. const p = array[i];
  328. p.examId = store.env.examId;
  329. for (let i = 0; i < p.urls.length; i++) {
  330. if (stopSignal) return;
  331. try {
  332. const index = i + 1;
  333. p.index = index;
  334. const filePath = [config.dir, mustache.render(config.template, p)];
  335. if (config.append && window.electron.existsImage(filePath)) {
  336. console.log(filePath + " already exists");
  337. urls.splice(urls.indexOf(p.urls[i]), 1);
  338. continue;
  339. }
  340. const imageRes = await httpApp.get(p.urls[i], {
  341. responseType: "blob",
  342. });
  343. await window.electron.saveImage(
  344. JSON.parse(JSON.stringify(store)),
  345. await imageRes.data.arrayBuffer(),
  346. filePath
  347. );
  348. } catch (error) {
  349. window.electron.errorLogger2(p.urls[i], error);
  350. errorCount.value += 1;
  351. if (config.failover) {
  352. throw error;
  353. } else {
  354. console.log(p, error);
  355. continue;
  356. }
  357. }
  358. }
  359. finishedCount.value += 1;
  360. }
  361. }
  362. let stopSignal = false;
  363. onUnmounted(() => (stopSignal = true));
  364. </script>