Browse Source

保存图片到文件夹

Michael Wang 3 years ago
parent
commit
029716b3bd

+ 1 - 0
config.json

@@ -18,6 +18,7 @@
     "logger": {
         "level": "info"
     },
+    "imagemagick": "/usr/local/bin/",
     "openDevTools": false,
     "servers": [
         {

+ 8 - 1
src/lib/api.ts

@@ -2,6 +2,12 @@ import request from "requestretry";
 // import env from "./env";
 import _logger from "./logger";
 const logger = _logger("api.js");
+import fs from "fs/promises";
+
+async function doIt(filepath: string) {
+  const b = await fs.readFile(filepath);
+  console.log(b.toString());
+}
 
 async function execute(uri, method, form) {
   // await new Promise((res) => setTimeout(res, 3000));
@@ -100,9 +106,10 @@ export function getPackages(examId, upload, withUrl) {
 
 export default {
   execute,
-  login,
+  // login,
   getExams,
   getStudents,
   countStudents,
   getPackages,
+  doIt,
 };

+ 243 - 0
src/lib/watermark.ts

@@ -0,0 +1,243 @@
+import fs from "fs";
+import path from "path";
+import readline from "readline";
+import request_util from "requestretry";
+import sizeOf from "image-size";
+import mustache from "mustache";
+import mkdirp from "mkdirp";
+
+let gm;
+
+export async function addWatermark(
+  store,
+  image,
+  imageWidth,
+  imageHeight,
+  file,
+  student,
+  index,
+  trackMode
+) {
+  console.log(
+    "enter addWatermark2",
+    store.config.watermark.fontFile,
+    store.config.watermark.color,
+    student.objectiveScore
+  );
+  if (!gm) {
+    gm =
+      store.config.imagemagick != undefined
+        ? require("gm").subClass({
+            imageMagick: true,
+            appPath: store.config.imagemagick,
+          })
+        : require("gm");
+  }
+  const fontFile = store.config.watermark.fontFile;
+  const color = store.config.watermark.color;
+  image = Buffer.from(image, "binary");
+  const size = sizeOf(image);
+  console.log(size);
+  const imgData = gm(image);
+  //添加第一页的得分明细
+  if (index == 1) {
+    //初始坐标
+    let x = 30;
+    let y = 10;
+    //最大宽/高限制
+    const fontSize = store.config.watermark.fontSize || 30;
+    const maxX = imageWidth / 2 - x * 2;
+    const height = fontSize + 10;
+    //计算总分
+    const totalScore =
+      (parseFloat(student.objectiveScore) || 0) +
+      (parseFloat(student.subjectiveScore) || 0);
+    //显示总分明细
+    imgData.font(fontFile, fontSize).fill(color);
+    imgData.drawText(x, (y += height), "成绩明细");
+    //普通考试模式,按客观+主观模式显示总分
+    if (trackMode === "1") {
+      imgData.drawText(
+        x,
+        (y += height),
+        "总分=(客观+主观) | " +
+          totalScore +
+          "=" +
+          student.objectiveScore +
+          "+" +
+          student.subjectiveScore
+      );
+    }
+    //研究生考试模式,只显示总分
+    else if (trackMode === "2") {
+      imgData.drawText(x, (y += height), "总分=" + totalScore + "分");
+    }
+    //显示客观题明细
+    if (
+      student.objectiveScoreDetail &&
+      student.objectiveScoreDetail.length > 0
+    ) {
+      const lines = [];
+      let array = [];
+      //前置提示文字的字符数
+      let count = 10;
+      lines.push(array);
+      for (let i = 0; i < student.objectiveScoreDetail.length; i++) {
+        const detail = student.objectiveScoreDetail[i];
+        const content = detail.answer + ":" + detail.score;
+        //超长后另起一行显示客观题
+        if ((count + content.length) * fontSize * 0.7 > maxX) {
+          array = [];
+          lines.push(array);
+          count = 10;
+        }
+        array.push(content);
+        count += content.length;
+      }
+      //显示所有行的客观题明细
+      for (let l = 0; l < lines.length; l++) {
+        imgData.drawText(
+          x,
+          (y += height),
+          "客观题识别结果 | " + lines[l].join(";")
+        );
+      }
+    }
+    //显示复核人
+    if (student.inspector) {
+      imgData.drawText(
+        x,
+        (y += height),
+        "复核人: " + student.inspector.loginName
+      );
+    }
+    //显示主观题明细
+    if (
+      student.subjectiveScoreDetail &&
+      student.subjectiveScoreDetail.length > 0
+    ) {
+      //普通考试模式,按小题显示明细
+      if (trackMode === "1") {
+        const title = "主观题号 | 分数 | 评卷员 | 仲裁员";
+        const startY = y;
+        let width = title.length * fontSize;
+        imgData.drawText(x, (y += height), title);
+        for (let i = 0; i < student.subjectiveScoreDetail.length; i++) {
+          const detail = student.subjectiveScoreDetail[i];
+          //超过最大高度了则另起一列
+          if (y + height + 15 > imageHeight) {
+            y = startY;
+            x += width;
+            imgData.drawText(x, (y += height), title);
+          }
+          const content =
+            detail.mainNumber +
+            "-" +
+            detail.subNumber +
+            " : " +
+            detail.score +
+            " " +
+            (detail.marker || "") +
+            " " +
+            (detail.header || "");
+          width = Math.max(width, content.length * fontSize);
+          imgData.drawText(x, (y += height), content);
+        }
+      }
+      //研究生考试模式,按分组显示明细
+      else if (trackMode === "2") {
+        const title = "评卷分组 | 总分 | 评卷员 | 仲裁员";
+        const startY = y;
+        let width = title.length * fontSize;
+        imgData.drawText(x, (y += height), title);
+        //所有小题得分按评卷分组聚合
+        let maxGroupNumber = 0;
+        const groups = {};
+        for (let i = 0; i < student.subjectiveScoreDetail.length; i++) {
+          const detail = student.subjectiveScoreDetail[i];
+          let group = groups[detail.groupNumber];
+          if (group == undefined) {
+            group = {
+              number: detail.groupNumber,
+              score: 0,
+              title: {},
+              titleString: [],
+              marker: {},
+              markerString: [],
+              header: {},
+              headerString: [],
+            };
+            groups[detail.groupNumber] = group;
+            maxGroupNumber = Math.max(maxGroupNumber, group.number);
+          }
+          group.score = group.score + detail.score;
+          if (detail.mainTitle && !group.title[detail.mainTitle]) {
+            group.titleString.push(detail.mainTitle);
+            group.title[detail.mainTitle] = true;
+          }
+          if (detail.marker && !group.marker[detail.marker]) {
+            group.markerString.push(detail.marker);
+            group.marker[detail.marker] = true;
+          }
+          if (detail.header && !group.header[detail.header]) {
+            group.headerString.push(detail.header);
+            group.header[detail.header] = true;
+          }
+        }
+        for (let i = 1; i <= maxGroupNumber; i++) {
+          const group = groups[i];
+          if (group != undefined) {
+            //超过最大高度了则另起一列
+            if (y + height + 15 > imageHeight) {
+              y = startY;
+              x += width;
+              imgData.drawText(x, (y += height), title);
+            }
+            const content =
+              group.number +
+              "(" +
+              group.titleString.join(",") +
+              ")" +
+              " " +
+              group.score +
+              " " +
+              group.markerString.join(",") +
+              " " +
+              group.headerString.join(",");
+            width = Math.max(width, content.length * fontSize);
+            imgData.drawText(x, (y += height), content);
+          }
+        }
+      }
+    }
+  }
+  //显示评卷标记
+  if (student.tags != undefined && student.tags[index] != undefined) {
+    const fontSize = 60;
+    const height = fontSize + 10;
+    imgData.font(fontFile, fontSize).fill(color);
+    const tags = student.tags[index];
+    for (let i = 0; i < tags.length; i++) {
+      const tag = tags[i];
+      if (tag.content != undefined) {
+        let top = tag.top;
+        for (let j = 0; j < tag.content.length; j++) {
+          imgData.drawText(tag.left, top, tag.content[j]);
+          top += height;
+        }
+      }
+    }
+  }
+
+  return new Promise((resolve, reject) => {
+    imgData.write(file, (error) => {
+      if (error) {
+        // logger.error("add watermark error: " + file);
+        // logger.error(error);
+        reject(error);
+      } else {
+        resolve();
+      }
+    });
+  });
+}

+ 2 - 0
src/preload.ts

@@ -3,12 +3,14 @@ import { contextBridge, ipcRenderer, remote } from "electron";
 import env from "./lib/env";
 import config from "./lib/config";
 // import api from "./lib/api";
+import { addWatermark } from "./lib/watermark";
 
 export const electronInWindow = {
   dialog: remote.dialog,
   env: env,
   config,
   // api,
+  addWatermark,
   updateEnv(env) {
     ipcRenderer.sendSync("update-env", env);
   },

+ 6 - 2
src/store.ts

@@ -2,6 +2,7 @@ import { Store } from "@/types";
 
 let _inited = false;
 
+// for dev
 const _store = {
   globalMask: false,
   env: {
@@ -14,6 +15,9 @@ const _store = {
   pageInputs: {},
 } as Store;
 
+// for dev
+Object.assign(_store, JSON.parse(window.localStorage.getItem("store") || "{}"));
+
 const handler = function () {
   return {
     // @ts-ignore
@@ -21,7 +25,7 @@ const handler = function () {
       // console.log("set", obj, prop, value);
       // @ts-ignore
       obj[prop] = value;
-      window.sessionStorage.setItem("store", JSON.stringify(_store));
+      window.localStorage.setItem("store", JSON.stringify(_store));
       return true;
     },
     // @ts-ignore
@@ -30,7 +34,7 @@ const handler = function () {
       if (!_inited) {
         Object.assign(
           _store,
-          JSON.parse(window.sessionStorage.getItem("store") || "{}")
+          JSON.parse(window.localStorage.getItem("store") || "{}")
         );
         _inited = true;
       }

+ 52 - 7
src/views/features/ImageDownload/ImageDownload.vue

@@ -57,8 +57,25 @@ import { store } from "@/store";
 import { computed, onMounted, ref } from "vue";
 import router from "@/router";
 import { getStudents, countStudents } from "@/api/api";
+import { httpApp } from "@/plugins/axiosApp";
+
+async function getImageDim(
+  blob: Blob
+): Promise<[width: number, height: number]> {
+  return new Promise((res) => {
+    const img = new Image();
+    img.src = URL.createObjectURL(blob);
+    img.onload = () => {
+      URL.revokeObjectURL(img.src);
+      console.log(img.width);
+      res([img.width, img.height]);
+    };
+  });
+}
 
 const totalCount = ref(0);
+let processedCount = ref(0);
+let students = [];
 
 const config = store.pageInputs["/image-download"];
 onMounted(async () => {
@@ -68,13 +85,7 @@ onMounted(async () => {
     //                 examNumberIn: config.examNumber,
     //                 subjectCode: config.subjectCode
     //             })
-    // const res = await getStudents(store.env.examId, 1, 10, {
-    //   upload: true,
-    //   withSheetUrl: true,
-    //   withScoreDetail: config.watermark,
-    //   withGroupScoreTrack: config.watermark && config.trackMode,
-    // });
-    // totalCount.value = res.data.length;
+
     const res = await countStudents(store.env.examId, {
       upload: true,
       withSheetUrl: true,
@@ -82,6 +93,40 @@ onMounted(async () => {
       withGroupScoreTrack: config.watermark && config.trackMode,
     });
     totalCount.value = res.data;
+
+    const resStudents = await getStudents(store.env.examId, 1, 10, {
+      upload: true,
+      withSheetUrl: true,
+      withScoreDetail: config.watermark,
+      withGroupScoreTrack: config.watermark && config.trackMode,
+    });
+    students = resStudents.data;
+
+    for (const student of students) {
+      for (const sheetUrl of student.sheetUrls) {
+        const imageRes = await httpApp.get(sheetUrl, { responseType: "blob" });
+
+        const [width, height] = await getImageDim(imageRes.data);
+
+        // console.log(imageRes.data);
+        // console.log(await imageRes.data.arrayBuffer());
+        // console.log(new Uint8Array(await imageRes.data.arrayBuffer()));
+        await window.electron.addWatermark(
+          // null,
+          JSON.parse(JSON.stringify(store)),
+          // null,
+          await imageRes.data.arrayBuffer(),
+          // new Uint8Array(await imageRes.data.arrayBuffer()),
+          width,
+          height,
+          `/Users/michael/Documents/qmth/electron-exam-shell/test${Date.now()}.jpg`,
+          // null,
+          student,
+          1,
+          "1"
+        );
+      }
+    }
   }
 });
 </script>

+ 8 - 0
src/views/features/Login/Login.vue

@@ -95,4 +95,12 @@ const loginAction = () => {
       alert("登陆失败,用户名或密码错误");
     });
 };
+// console.log(window.electron.api);
+
+// await window.electron.api.doIt(
+//   "/Users/michael/Documents/qmth/electron-exam-shell/package.json"
+// );
+// console.log("ene");
+
+// setInterval(() => window.electron.printStore(), 3000);
 </script>