Browse Source

图片再处理修改

zhangjie 3 năm trước cách đây
mục cha
commit
6f771c7874

+ 64 - 64
src/modules/cropper-task/CropperTaskDetailDialog.vue

@@ -44,8 +44,8 @@
         <div class="task-body">
           <div class="task-body-item task-org">
             <scan-area-steps
-              v-if="curTask.filepath && modalIsShow && curCollectConfig"
-              :image-url="curTask.filepath"
+              v-if="curTask.originImgPath && modalIsShow && curCollectConfig"
+              :image-url="curTask.originImgPath"
               :cur-setting="curCollectConfig"
               :key="curTask.key"
               @on-finished="finished"
@@ -55,16 +55,13 @@
           <div class="task-body-item task-finally">
             <div class="task-finally-body">
               <img
-                v-if="curTaskImagesPath"
+                v-if="curTask.sliceImgPath"
                 class="img-contain"
-                :src="curTaskImagesPath"
+                :src="curTask.sliceImgPath"
                 alt="裁切图"
               />
             </div>
-            <div
-              v-if="curTaskImagesPath && !curTask.isFinished"
-              class="task-action box-justify"
-            >
+            <div v-if="curTask.sliceImgPath" class="task-action box-justify">
               <h5>处理结果</h5>
               <Button
                 type="primary"
@@ -89,10 +86,11 @@ import {
   updateCropperTaskDetail,
   updateCropperTaskFinishedCount
 } from "../../plugins/db";
-import { saveCropperImage } from "./taskUtils";
+import { saveCropperImage, downloadOriginImg } from "./taskUtils";
 import { formatDate, randomCode } from "../../plugins/utils";
-const path = require("path");
-const fs = require("fs");
+import { getPaperInfo } from "./api";
+// const path = require("path");
+// const fs = require("fs");
 
 export default {
   name: "cropper-task-detail-dialog",
@@ -119,7 +117,6 @@ export default {
       taskCount: 0,
       finishedCount: 0,
       taskProgress: 0,
-      curTaskImagesPath: null,
       loading: false
     };
   },
@@ -175,55 +172,56 @@ export default {
       const scrollTop = Math.max(0, taskDom.offsetTop - 84);
       document.getElementById("task-list").scrollTop = scrollTop;
     },
-    getTaskOutputFilePath(taskPath) {
-      // TODO:
-      const paperPath = taskPath.replace(this.cropperTask.inputDir, "");
-      const types = ["sheet", "images", "thumbs"];
-      return types.map(item =>
-        path.join(this.cropperTask.outputDir, item, paperPath)
-      );
+    async getOriginImg(paperInfo) {
+      if (paperInfo.originImgPath) return paperInfo.originImgPath;
+
+      const pInfo = await getPaperInfo({
+        workId: paperInfo.workId,
+        subjectId: paperInfo.subjectId,
+        examNumber: paperInfo.examNumber
+      }).catch(() => {});
+      if (!pInfo) {
+        this.$Message.error("获取原图失败,请重新尝试!");
+        return;
+      }
+
+      this.curTask.paperId = paperInfo.id;
+
+      const outputOriginPath = await downloadOriginImg({
+        ...paperInfo,
+        url: pInfo.imgSrc
+      }).catch(() => {});
+      if (!outputOriginPath) {
+        this.$Message.error("下载原图失败,请重新尝试!");
+        return;
+      }
+
+      this.curTask.originImgPath = outputOriginPath;
     },
     async toDo(task) {
-      // TODO:操作流程与正常采集过程保持一致,需要点时间整理,数据表需要再调整。
-      const [sheetPath, imagesPath, thumbsPath] = this.getTaskOutputFilePath(
-        task.filepath
-      );
       this.curTask = {
         ...task,
-        sheetPath,
-        imagesPath,
-        thumbsPath,
-        key: randomCode(),
-        compressRate: this.GLOBAL.compressRate
+        key: randomCode()
       };
-      this.curTaskImagesPath = fs.existsSync(this.curTask.imagesPath)
-        ? this.curTask.imagesPath
-        : null;
+      await this.getOriginImg(task);
 
       // 在没有裁切图的情况下,自动根据已保存的设置信息生成裁切图。
-      if (!this.curTaskImagesPath && this.curCollectConfig["codeArea"]) {
-        let res = await saveCropperImage(
-          this.curTask,
-          this.curCollectConfig
-        ).catch(() => {
-          this.loading = false;
-        });
-        if (!res) {
-          this.$Message.error("保存图片失败,请重新尝试!");
-          return;
-        }
-
-        this.curTaskImagesPath = this.curTask.imagesPath;
+      if (!this.curTask.sliceImgPath && this.curCollectConfig["codeArea"]) {
+        this.loading = true;
+        let res = await this.saveCurImage().catch(() => {});
+        this.loading = false;
+        if (!res) return;
       }
     },
     async curTaskFinished(setting) {
       await updateCropperTaskDetail({
         id: this.curTask.id,
         cropperSet: JSON.stringify(setting),
-        sheetPath: this.curTask.sheetPath,
-        imagesPath: this.curTask.imagesPath,
-        thumbsPath: this.curTask.thumbsPath,
+        originImgPath: this.curTask.originImgPath,
+        formalImgPath: this.curTask.formalImgPath,
+        sliceImgPath: this.curTask.sliceImgPath,
         isFinished: 1,
+        isUpload: 0,
         updateTime: formatDate()
       });
       await this.updateProgress();
@@ -233,13 +231,26 @@ export default {
 
       return true;
     },
+    async saveCurImage() {
+      let res = await saveCropperImage(
+        this.curTask,
+        this.curCollectConfig
+      ).catch(() => {});
+      if (!res) {
+        return Promise.reject();
+      }
+
+      this.curTask.sliceImgPath = res.outputSlicelPath;
+      this.curTask.formalImgPath = res.outputFormalPath;
+      return true;
+    },
     async finished(setting) {
       if (this.loading) return;
       this.loading = true;
 
-      this.curTaskImagesPath = null;
       this.curCollectConfig = setting;
-      let res = await saveCropperImage(this.curTask, setting).catch(() => {
+
+      let res = await this.saveCurImage().catch(() => {
         this.loading = false;
       });
       if (!res) {
@@ -247,8 +258,6 @@ export default {
         return;
       }
 
-      this.curTaskImagesPath = this.curTask.imagesPath;
-
       res = await this.curTaskFinished(setting).catch(() => {});
       this.loading = false;
       if (!res) {
@@ -256,19 +265,7 @@ export default {
         return;
       }
 
-      this.$Modal.confirm({
-        content: "操作成功,是否进行下一个任务?",
-        onOk: () => {
-          const nextTask = this.taskList.find(item => !item.isFinished);
-          if (!nextTask) {
-            this.$Message.success("当前任务已全部结束");
-            return;
-          }
-
-          this.toDo(nextTask);
-          this.scrollTaskList(this.curTask.id);
-        }
-      });
+      this.toNextTask();
     },
     async toComfirmAndNext() {
       if (this.loading) return;
@@ -283,6 +280,9 @@ export default {
         return;
       }
 
+      this.toNextTask();
+    },
+    toNextTask() {
       const nextTask = this.taskList.find(item => !item.isFinished);
       if (!nextTask) {
         this.$Message.success("当前任务已全部结束");

+ 6 - 3
src/modules/cropper-task/ModifyCropperTask.vue

@@ -50,6 +50,7 @@ import {
 const remote = require("electron").remote;
 const fs = require("fs");
 const nodeXlsx = require("node-xlsx");
+import { mapState } from "vuex";
 
 const initModalForm = {
   id: "",
@@ -68,6 +69,7 @@ export default {
     }
   },
   computed: {
+    ...mapState("client", ["clientConfig"]),
     isEdit() {
       return !!this.instance.id;
     },
@@ -229,9 +231,10 @@ export default {
               subjectName,
               examNumber,
               studentName,
-              sheetPath: "",
-              imagesPath: "",
-              thumbsPath: "",
+              originImgPath: "",
+              formalImgPath: "",
+              sliceImgPath: "",
+              imageEncrypt: this.clientConfig.imageEncrypt,
               cropperSet: "{}"
             };
           });

+ 5 - 0
src/modules/cropper-task/api.js

@@ -0,0 +1,5 @@
+import { $get } from "@/plugins/axios";
+
+export const getPaperInfo = ({ workId, subjectId, examNumber }) => {
+  return $get(`/api/papers/${workId}/${subjectId}/${examNumber}`);
+};

+ 57 - 73
src/modules/cropper-task/taskUtils.js

@@ -1,71 +1,40 @@
-import { getExtraDir, makeDirSync } from "../../plugins/env";
+import { getExtraDir, makeDirSync, getStoresDir } from "../../plugins/env";
+import { randomCode } from "../../plugins/utils";
 const fs = require("fs");
 const path = require("path");
+const request = require("request");
 const gm = require("gm").subClass({
   imageMagick: true,
   appPath: getExtraDir("imagemagick/")
 });
 
-export const isPaperFile = filename => {
-  return filename.match(/\.jpg|jpeg|png/);
-};
-
-export const getValidFiles = folderPath => {
-  let taskList = [];
-
-  const readFiles = folderPath => {
-    const files = fs.readdirSync(folderPath, { withFileTypes: true });
-    files.map(dirent => {
-      const filePath = path.join(folderPath, dirent.name);
-      if (dirent.isDirectory()) {
-        readFiles(filePath);
-      } else {
-        if (isPaperFile(dirent.name)) {
-          taskList.push(filePath);
-        }
-      }
-    });
-  };
-  return new Promise((resolve, reject) => {
-    try {
-      readFiles(folderPath);
-      resolve(taskList);
-    } catch (error) {
-      console.log(error);
-      reject(error);
-    }
-  });
-};
-
-export const getInputFirstPaper = folderPath => {
-  let paper = "";
-
-  const readFiles = folderPath => {
-    if (paper) return;
-    const files = fs.readdirSync(folderPath, { withFileTypes: true });
-    files.map(dirent => {
-      if (paper) return;
-
-      const filePath = path.join(folderPath, dirent.name);
-      if (dirent.isDirectory()) {
-        readFiles(filePath);
-      } else {
-        if (isPaperFile(dirent.name)) {
-          paper = filePath;
-        }
-      }
-    });
-  };
+export function downloadOriginImg(paperInfo) {
+  const outputOriginPath = getOutputImagePath(paperInfo, "origin");
+
   return new Promise((resolve, reject) => {
-    try {
-      readFiles(folderPath);
-      resolve(paper);
-    } catch (error) {
-      console.log(error);
-      reject(error);
-    }
+    request
+      .get({ url: paperInfo.url })
+      .pipe(fs.createWriteStream(outputOriginPath))
+      .on("error", e => {
+        reject(`write file error: ${outputOriginPath}`);
+      })
+      .on("finish", () => {
+        console.log(`file loaded: ${outputOriginPath}`);
+        resolve(outputOriginPath);
+      });
   });
-};
+}
+
+function getOutputImagePath(paperInfo, type) {
+  const outputDir = path.join(
+    path.join(getStoresDir("cropper"), type),
+    paperInfo.examId + "",
+    paperInfo.subjectId + ""
+  );
+
+  if (!fs.existsSync(outputDir)) makeDirSync(outputDir);
+  return path.join(outputDir, `${paperInfo.examNumber}-${randomCode()}.jpg`);
+}
 
 // function saveThumbsImage(imgPath, outputThumbsPath) {
 //   const outputThumbsDir = path.dirname(outputThumbsPath);
@@ -88,11 +57,34 @@ export const getInputFirstPaper = folderPath => {
 //   });
 // }
 
+function saveFormalImage(paperInfo, collectConfig) {
+  const imgPath = paperInfo.originImgPath;
+  const outputFormalPath = getOutputImagePath(paperInfo, "formal");
+
+  const { originTailorArea } = collectConfig;
+
+  let imgObj = gm(imgPath);
+  // 边缘裁切
+  imgObj.crop(
+    originTailorArea.width,
+    originTailorArea.height,
+    originTailorArea.x,
+    originTailorArea.y
+  );
+
+  return new Promise((resolve, reject) => {
+    imgObj.write(outputFormalPath, function(err) {
+      if (err) {
+        reject(err);
+      }
+      resolve(outputFormalPath);
+    });
+  });
+}
+
 function saveSliceImage(paperInfo, collectConfig) {
-  const imgPath = paperInfo.filepath;
-  const outputSlicelPath = paperInfo.imagesPath;
-  const outputSliceDir = path.dirname(outputSlicelPath);
-  if (!fs.existsSync(outputSliceDir)) makeDirSync(outputSliceDir);
+  const imgPath = paperInfo.originImgPath;
+  const outputSlicelPath = getOutputImagePath(paperInfo, "slice");
 
   const { codeArea, coverArea, tailorTailorArea, imageRotate } = collectConfig;
 
@@ -129,11 +121,6 @@ function saveSliceImage(paperInfo, collectConfig) {
   // 旋转
   if (imageRotate) imgObj.rotate("#FFFFFF", imageRotate);
 
-  // 压缩
-  if (paperInfo.compressRate !== 100) {
-    imgObj.setFormat("jpeg").quality(paperInfo.compressRate);
-  }
-
   return new Promise((resolve, reject) => {
     imgObj.write(outputSlicelPath, function(err) {
       if (err) {
@@ -147,10 +134,7 @@ function saveSliceImage(paperInfo, collectConfig) {
 
 export async function saveCropperImage(paperInfo, collectConfig) {
   const outputSlicelPath = await saveSliceImage(paperInfo, collectConfig);
-  // const outputThumbsPath = await saveThumbsImage(
-  //   outputSlicelPath,
-  //   paperInfo.thumbsPath
-  // );
+  const outputFormalPath = await saveFormalImage(paperInfo, collectConfig);
 
-  return { outputSlicelPath };
+  return { outputFormalPath, outputSlicelPath };
 }

+ 19 - 11
src/plugins/db.js

@@ -657,17 +657,22 @@ export function getCropperTaskFinishCount(cropperTaskId) {
 
 /**
  * 
- *CREATE TABLE "cropper_task_detail" (
+CREATE TABLE "cropper_task_detail" (
   "id" INTEGER NOT NULL,
-  "examId" INTEGER NOT NULL,
+  "cropperTaskId" INTEGER NOT NULL,
+  "examId" text NOT NULL,
+  "paperId" text,
   "subjectId" text NOT NULL,
-  "subjectName" text NOT NULL,
+  "subjectName" TEXT,
   "examNumber" text NOT NULL,
   "studentName" TEXT NOT NULL,
-  "sheetPath" TEXT NOT NULL,
-  "imagesPath" TEXT NOT NULL,
-  "thumbsPath" TEXT NOT NULL,
-  "cropperSet" TEXT NOT NULL,
+  "originImgPath" TEXT,
+  "formalImgPath" TEXT,
+  "sliceImgPath" TEXT,
+  "compressRate" integer NOT NULL DEFAULT 100,
+  "imageEncrypt" integer NOT NULL DEFAULT 1,
+  "isUpload" integer NOT NULL DEFAULT 0,
+  "cropperSet" TEXT,
   "isFinished" integer NOT NULL DEFAULT 0,
   "createTime" TEXT NOT NULL DEFAULT '',
   "updateTime" TEXT NOT NULL DEFAULT '',
@@ -697,13 +702,16 @@ export function addCropperTaskDetail(paramList) {
     const datas = [
       params.cropperTaskId,
       params.examId,
+      params.paperId || "",
       params.subjectId,
       params.subjectName,
       params.examNumber,
       params.studentName,
-      params.sheetPath,
-      params.imagesPath,
-      params.thumbsPath,
+      params.originImgPath,
+      params.formalImgPath,
+      params.sliceImgPath,
+      params.imageEncrypt,
+      0, // isUpload
       params.cropperSet,
       0, // isFinished
       formatDate(), // createTime
@@ -716,7 +724,7 @@ export function addCropperTaskDetail(paramList) {
   });
   const vals = listVals.join(",");
 
-  const sql = `INSERT INTO cropper_task_detail (cropperTaskId,examId, subjectId,subjectName,examNumber,studentName,sheetPath, imagesPath,thumbsPath,cropperSet,isFinished,createTime,updateTime) VALUES ${vals}`;
+  const sql = `INSERT INTO cropper_task_detail (cropperTaskId,examId,paperId, subjectId,subjectName,examNumber,studentName,originImgPath, formalImgPath,sliceImgPath,imageEncrypt,isUpload,cropperSet,isFinished,createTime,updateTime) VALUES ${vals}`;
 
   return new Promise((resolve, reject) => {
     db.run(sql, function(err) {

+ 7 - 1
src/plugins/env.js

@@ -15,7 +15,12 @@ function initPath() {
     getStoresDir("out"),
     getOutputDir("formal"),
     getOutputDir("slice"),
-    getTmpDir()
+    getTmpDir(),
+    // cropper
+    getStoresDir("cropper"),
+    path.join(getStoresDir("cropper"), "origin"),
+    path.join(getStoresDir("cropper"), "formal"),
+    path.join(getStoresDir("cropper"), "slice")
   ];
   paths.forEach(path => {
     if (!fs.existsSync(path)) fs.mkdirSync(path);
@@ -110,6 +115,7 @@ function initConfigData(data) {
 export {
   initPath,
   getHomeDir,
+  getStoresDir,
   getExtraDir,
   getInputDir,
   getOutputDir,

+ 8 - 1
src/router.js

@@ -2,6 +2,7 @@ import Vue from "vue";
 import Router from "vue-router";
 
 import Home from "./views/Home";
+import Layout from "./views/Layout";
 import Login from "./views/Login";
 // modules
 import client from "./modules/client/router";
@@ -32,7 +33,13 @@ export default new Router({
       path: "/home",
       name: "Home",
       component: Home,
-      children: [...client, ...manage, ...cropperTask]
+      children: [...client, ...manage]
+    },
+    {
+      path: "",
+      name: "CropperHome",
+      component: Layout,
+      children: [...cropperTask]
     }
     // [lazy-loaded] route level code-splitting
     // {

+ 6 - 31
src/views/Home.vue

@@ -1,41 +1,16 @@
 <template>
-  <div class="home">
-    <div class="home-header">
-      <div class="head-back">
-        <Button type="default" @click="toBack">返回</Button>
-      </div>
-      <div class="head-actions">
-        <span class="action-icon" title="最小化" @click="minWin"
-          ><Icon type="md-remove" size="16"
-        /></span>
-        <span class="action-icon" title="最大化" @click="maxWin"
-          ><Icon type="md-browsers" size="16"
-        /></span>
-        <span class="action-icon" title="关闭" @click="close"
-          ><Icon type="md-close" size="16"
-        /></span>
-        <span
-          class="action-icon action-logout"
-          @click="logout"
-          title="退出"
-          v-if="isDev"
-        >
-          <Icon type="md-power" size="16" />
-        </span>
-      </div>
-      <div class="head-info">
+  <Layout :back-handle="toBack">
+    <template slot="head-info">
         <span>{{ examName }}</span>
         <i v-if="curSubject && curSubject.name"></i>
         <span>{{ curSubject.name }}</span>
       </div>
-    </div>
-    <div class="home-body">
-      <router-view />
-    </div>
-  </div>
+    </template>
+  </Layout>
 </template>
 
 <script>
+import Layout from "./Layout.vue";
 import uploadTaskMixin from "../mixins/uploadTaskMixin";
 import setTimeMixins from "../mixins/setTimeMixins";
 import { mapState, mapMutations } from "vuex";
@@ -48,11 +23,11 @@ const { ipcRenderer } = require("electron");
 export default {
   name: "home",
   mixins: [uploadTaskMixin, setTimeMixins],
+  components: { Layout },
   data() {
     return {
       showProgress: false,
       examName: "",
-      isDev: process.env.NODE_ENV === "development",
       // 路由历史只前进,不后退
       backRouters: {
         PaperExport: "Login",

+ 79 - 0
src/views/Layout.vue

@@ -0,0 +1,79 @@
+<template>
+  <div class="home">
+    <div class="home-header">
+      <div class="head-back">
+        <Button type="default" @click="toBack">返回</Button>
+      </div>
+      <div class="head-actions">
+        <span class="action-icon" title="最小化" @click="minWin"
+          ><Icon type="md-remove" size="16"
+        /></span>
+        <span class="action-icon" title="最大化" @click="maxWin"
+          ><Icon type="md-browsers" size="16"
+        /></span>
+        <span class="action-icon" title="关闭" @click="close"
+          ><Icon type="md-close" size="16"
+        /></span>
+        <span
+          class="action-icon action-logout"
+          @click="logout"
+          title="退出"
+          v-if="isDev"
+        >
+          <Icon type="md-power" size="16" />
+        </span>
+      </div>
+      <div class="head-info">
+        <slot name="head-info"> </slot>
+      </div>
+    </div>
+    <div class="home-body">
+      <router-view />
+    </div>
+  </div>
+</template>
+
+<script>
+const { ipcRenderer } = require("electron");
+
+export default {
+  name: "layout",
+  props: {
+    backHandle: {
+      type: Function
+    }
+  },
+  data() {
+    return {
+      isDev: process.env.NODE_ENV === "development"
+    };
+  },
+  methods: {
+    logout() {
+      this.$ls.clear();
+      this.$router.push({ name: "Login" });
+    },
+    toBack() {
+      if (this.backHandle && typeof this.backHandle === "function") {
+        this.backHandle();
+      } else {
+        window.history.go(-1);
+      }
+    },
+    minWin() {
+      ipcRenderer.send("minimize-window");
+    },
+    maxWin() {
+      ipcRenderer.send("maximize-window");
+    },
+    close() {
+      this.$Modal.confirm({
+        content: "请确保当前窗口没有任务正在进行,确认要关闭当前窗口吗?",
+        onOk: () => {
+          ipcRenderer.send("close-window");
+        }
+      });
+    }
+  }
+};
+</script>