zhangjie 3 éve
szülő
commit
b7bed47ea4

BIN
extra/database/org.rdb


+ 16 - 0
src/assets/styles/base.less

@@ -114,3 +114,19 @@ body {
   color: @fontMain;
   min-width: 1024px;
 }
+
+.box-justify {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.mb-0 {
+  margin-bottom: 0;
+}
+.mb-2 {
+  margin-bottom: 10px;
+}
+.mb-4 {
+  margin-bottom: 20px;
+}

+ 5 - 2
src/assets/styles/home.less

@@ -83,12 +83,15 @@
 /* part */
 .part-box {
   position: relative;
-  width: 944px;
   min-height: 490px;
-  margin: 0 auto 40px;
+  margin: 0 30px 40px;
   border-radius: 40px;
   background-color: @background-light;
   overflow: auto;
+
+  &-pad {
+    padding: 30px;
+  }
 }
 .part-head {
   font-size: 18px;

+ 27 - 0
src/assets/styles/iview-custom.less

@@ -12,6 +12,33 @@
     }
   }
 }
+// ivu-table
+.ivu-table {
+  background-color: @background-light;
+  color: @fontMain;
+  th {
+    background-color: @background-light;
+    color: @fontSub;
+  }
+  td {
+    background-color: @background-light;
+  }
+  &-border {
+    td,
+    th {
+      border-color: @background;
+    }
+    &:after {
+      background-color: @background;
+    }
+  }
+  &-wrapper-with-border {
+    border-color: @background;
+  }
+  &::before {
+    background-color: @background;
+  }
+}
 // select - ok
 .ivu-select {
   .ivu-select-dropdown {

+ 0 - 2
src/assets/styles/iview.less

@@ -5,8 +5,6 @@
 @background-color-base: rgba(235, 239, 247, 1);
 
 // table
-@table-thead-bg: #d6e5f8;
-@table-td-stripe-bg: rgba(237, 242, 250, 1);
 
 // Button
 @btn-height-base: 36px;

+ 132 - 0
src/assets/styles/manage.less

@@ -187,3 +187,135 @@
 .export-task {
   padding: 15px 20px;
 }
+
+// cropper-task-detail-dialog
+.cropper-task-detail-dialog {
+  .ivu-modal-header {
+    padding: 15px 20px;
+  }
+}
+.cropper-task-detail {
+  height: 100%;
+  .task-list {
+    position: fixed;
+    width: 200px;
+    top: 52px;
+    left: 0;
+    bottom: 0;
+    z-index: 99;
+    background-color: @background;
+    padding: 20px;
+    overflow-y: auto;
+    overflow-x: hidden;
+    border-top-right-radius: 20px;
+  }
+  .task-item {
+    padding: 6px 10px 6px 30px;
+    color: @fontSub;
+    border-radius: 5px;
+    background-color: @background-light;
+    position: relative;
+    margin-bottom: 8px;
+    cursor: pointer;
+
+    &:hover {
+      box-shadow: 0 0 0 1px @main-color;
+    }
+
+    &-icon {
+      position: absolute;
+      top: 6px;
+      left: 10px;
+      width: 20px;
+      height: 20px;
+      line-height: 20px;
+      font-size: 16px;
+    }
+
+    &.task-current {
+      box-shadow: 0 0 0 1px @main-color;
+    }
+    &.task-over {
+      color: @success-color;
+    }
+  }
+  .task-main {
+    padding: 0 0 10px 190px;
+    height: 100%;
+    position: relative;
+  }
+  .task-progress {
+    position: absolute;
+    height: 24px;
+    top: 0;
+    left: 190px;
+    right: 0;
+    z-index: 9;
+  }
+  .ivu-progress-inner {
+    background-color: @background;
+  }
+  .task-none {
+    padding-top: 100px;
+    text-align: center;
+    font-size: 16px;
+    color: @fontSub;
+  }
+  .task-body {
+    padding-top: 30px;
+    margin: 0px -10px;
+    font-size: 0;
+    height: 100%;
+
+    &-item {
+      display: inline-block;
+      vertical-align: top;
+      padding: 10px;
+      width: 50%;
+      font-size: 14px;
+    }
+    .task-org {
+      width: 70%;
+      height: 100%;
+    }
+    .task-finally {
+      width: 30%;
+      height: auto;
+      &-body {
+        position: relative;
+        background-color: @background;
+        height: 400px;
+        margin-bottom: 10px;
+      }
+    }
+  }
+
+  // scan-area-steps
+  .scan-area-steps {
+    position: relative;
+    background-color: @background;
+    height: 100%;
+
+    .part-head {
+      position: absolute;
+      width: 100%;
+      height: 64px;
+      top: 0;
+      left: 0;
+      z-index: auto;
+    }
+    .step-body {
+      padding: 64px 0 66px;
+      height: 100%;
+      margin: 0;
+    }
+    .step-ctrl {
+      position: absolute;
+      width: 100%;
+      height: 66px;
+      bottom: 0;
+      left: 0;
+      z-index: auto;
+    }
+  }
+}

+ 16 - 14
src/assets/styles/pages.less

@@ -146,21 +146,20 @@
   }
   .step-ctrl {
     text-align: center;
-    padding-bottom: 30px;
+    padding: 15px 0;
 
     > button {
       width: 160px;
-      height: 44px;
     }
   }
 }
 /* code-area */
 .code-area {
-  min-height: 458px;
+  height: 100%;
   &-cont {
     position: relative;
-    width: 610px;
-    box-shadow: 0px 40px 40px 0px rgba(4, 5, 17, 0.3);
+    width: calc(100% - 300px);
+    height: 100%;
     &-disabled::before {
       content: "";
       display: block;
@@ -324,6 +323,7 @@
 
 /* cover-area */
 .cover-area {
+  height: 100%;
   .code-area-preview {
     position: absolute;
     height: 100%;
@@ -339,17 +339,19 @@
 }
 
 /* tailor-area */
-.tailor-area .code-area-cont {
-  width: 100%;
-  height: 400px;
+.tailor-area {
+  height: 100%;
+  .code-area-cont {
+    width: 100%;
+    height: 100%;
+  }
 }
 /* image-orientation */
 .image-orientation {
-  padding: 0 40px;
+  height: 100%;
   .image-orient-cont {
-    width: 450px;
-    height: 450px;
-    margin: 0 auto;
+    width: 100%;
+    height: 100%;
     position: relative;
   }
   .image-orient-icon {
@@ -357,10 +359,10 @@
     top: 50%;
     transform: translateY(-50%);
     &:first-child {
-      left: 32px;
+      left: 15px;
     }
     &:last-child {
-      right: 32px;
+      right: 15px;
     }
   }
 }

+ 1 - 7
src/modules/client/components/ScanAreaSteps.vue

@@ -17,19 +17,13 @@
     <div class="step-ctrl">
       <Button
         type="primary"
-        size="large"
         @click="prevStep"
         v-if="!isFirstStep"
         style="margin-right: 20px;"
       >
         上一步
       </Button>
-      <Button
-        type="primary"
-        size="large"
-        @click="nextStep"
-        :disabled="nextHolder"
-      >
+      <Button type="primary" @click="nextStep" :disabled="nextHolder">
         {{ isLastStep ? "完成" : "下一步" }}
       </Button>
     </div>

+ 284 - 0
src/modules/cropper-task/CropperTaskDetailDialog.vue

@@ -0,0 +1,284 @@
+<template>
+  <Modal
+    class="cropper-task-detail-dialog"
+    v-model="modalIsShow"
+    :title="title"
+    :mask-closable="false"
+    fullscreen
+    footer-hide
+    @on-visible-change="visibleChange"
+  >
+    <div class="cropper-task-detail">
+      <div class="task-list" id="task-list">
+        <div class="task-list-body" v-if="taskList.length">
+          <div
+            v-for="(task, index) in taskList"
+            :key="index"
+            :id="`task-${task.id}`"
+            :class="[
+              'task-item',
+              {
+                'task-current': task.id === curTask.id,
+                'task-over': task.isFinished
+              }
+            ]"
+            @click="toDo(task)"
+          >
+            <div class="task-item-icon">
+              <Icon type="ios-document" />
+            </div>
+            <p>{{ task.filename }}</p>
+          </div>
+        </div>
+      </div>
+      <div class="task-main">
+        <div class="task-progress">
+          <Progress :percent="taskProgress" />
+        </div>
+        <div class="task-body">
+          <div class="task-body-item task-org">
+            <scan-area-steps
+              v-if="curTask.filepath && modalIsShow && curCollectConfig"
+              :image-url="curTask.filepath"
+              :cur-setting="curCollectConfig"
+              :key="curTask.key"
+              @on-finished="finished"
+            ></scan-area-steps>
+            <div v-else><p class="task-none">暂无数据</p></div>
+          </div>
+          <div class="task-body-item task-finally">
+            <div class="task-finally-body">
+              <img
+                v-if="curTaskImagesPath"
+                class="img-contain"
+                :src="curTaskImagesPath"
+                alt="裁切图"
+              />
+            </div>
+            <div
+              v-if="curTaskImagesPath && !curTask.isFinished"
+              class="task-action"
+            >
+              <Button
+                type="primary"
+                :disabled="loading"
+                @click="toComfirmAndNext"
+              >
+                确认并下一个
+              </Button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </Modal>
+</template>
+
+<script>
+import ScanAreaSteps from "../client/components/ScanAreaSteps";
+import {
+  getCropperTaskDetailList,
+  getCropperTaskFinishCount,
+  updateCropperTaskDetail,
+  updateCropperTaskFinishedCount
+} from "../../plugins/db";
+import { saveCropperImage } from "./taskUtils";
+import { formatDate, randomCode } from "../../plugins/utils";
+const path = require("path");
+const fs = require("fs");
+
+export default {
+  name: "cropper-task-detail-dialog",
+  components: { ScanAreaSteps },
+  props: {
+    cropperTask: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  computed: {
+    title() {
+      return this.cropperTask.name;
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      curTask: {},
+      taskList: [],
+      curCollectConfig: {},
+      taskCount: 0,
+      finishedCount: 0,
+      taskProgress: 0,
+      curTaskImagesPath: null,
+      loading: false
+    };
+  },
+  methods: {
+    visibleChange(visible) {
+      if (visible) {
+        this.initData();
+      }
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async initData() {
+      await this.getTasks();
+
+      this.taskCount = this.cropperTask.taskCount;
+      this.finishedCount = this.cropperTask.finishedCount;
+      this.taskProgress =
+        ((100 * this.finishedCount) / this.taskCount).toFixed(2) * 1;
+
+      const nextTaskIndex = this.taskList.findIndex(item => !item.isFinished);
+      if (nextTaskIndex === -1) {
+        this.$Message.success("当前任务已全部结束");
+      } else {
+        this.curCollectConfig =
+          nextTaskIndex === 0
+            ? {}
+            : JSON.parse(this.taskList[nextTaskIndex - 1].cropperSet);
+        this.toDo(this.taskList[nextTaskIndex]);
+        this.scrollTaskList(this.curTask.id);
+      }
+    },
+    async getTasks() {
+      const data = await getCropperTaskDetailList(this.cropperTask.id);
+      this.taskList = data || [];
+    },
+    async updateProgress() {
+      this.finishedCount = await getCropperTaskFinishCount(this.cropperTask.id);
+      await updateCropperTaskFinishedCount({
+        id: this.cropperTask.id,
+        finishedCount: this.finishedCount
+      });
+      this.taskProgress =
+        ((100 * this.finishedCount) / this.taskCount).toFixed(2) * 1;
+    },
+    scrollTaskList(taskId) {
+      const taskDom = document.getElementById(`task-${taskId}`);
+      const scrollTop = Math.max(0, taskDom.offsetTop - 84);
+      document.getElementById("task-list").scrollTop = scrollTop;
+    },
+    getTaskOutputFilePath(taskPath) {
+      const paperPath = taskPath.replace(this.cropperTask.inputDir, "");
+      const types = ["sheet", "images", "thumbs"];
+      return types.map(item =>
+        path.join(this.cropperTask.outputDir, item, paperPath)
+      );
+    },
+    async toDo(task) {
+      const [sheetPath, imagesPath, thumbsPath] = this.getTaskOutputFilePath(
+        task.filepath
+      );
+      this.curTask = {
+        ...task,
+        sheetPath,
+        imagesPath,
+        thumbsPath,
+        key: randomCode()
+      };
+      this.curTaskImagesPath = fs.existsSync(this.curTask.imagesPath)
+        ? this.curTask.imagesPath
+        : null;
+
+      // 在没有裁切图的情况下,自动根据已保存的设置信息生成裁切图。
+      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;
+      }
+    },
+    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,
+        isFinished: 1,
+        updateTime: formatDate()
+      });
+      await this.updateProgress();
+
+      const curTask = this.taskList.find(item => item.id === this.curTask.id);
+      curTask.isFinished = true;
+
+      return true;
+    },
+    async finished(setting) {
+      if (this.loading) return;
+      this.loading = true;
+
+      this.curCollectConfig = setting;
+      let res = await saveCropperImage(this.curTask, setting).catch(() => {
+        this.loading = false;
+      });
+      if (!res) {
+        this.$Message.error("保存图片失败,请重新尝试!");
+        return;
+      }
+
+      this.curTaskImagesPath = this.curTask.imagesPath;
+
+      res = await this.curTaskFinished(setting).catch(() => {});
+      this.loading = false;
+      if (!res) {
+        this.$Message.error("更新图片失败,请重新尝试!");
+        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);
+        }
+      });
+    },
+    async toComfirmAndNext() {
+      if (this.loading) return;
+      this.loading = true;
+
+      const res = await this.curTaskFinished(
+        this.curCollectConfig
+      ).catch(() => {});
+      this.loading = false;
+      if (!res) {
+        this.$Message.error("更新图片失败,请重新尝试!");
+        return;
+      }
+
+      const nextTask = this.taskList.find(item => !item.isFinished);
+      if (!nextTask) {
+        this.$Message.success("当前任务已全部结束");
+        return;
+      }
+
+      this.toDo(nextTask);
+      this.scrollTaskList(this.curTask.id);
+    }
+  }
+};
+</script>

+ 158 - 0
src/modules/cropper-task/CropperTaskManage.vue

@@ -0,0 +1,158 @@
+<template>
+  <div class="cropper-task-manage">
+    <div class="part-box part-box-pad">
+      <div class="box-justify mb-2">
+        <Button type="primary" icon="md-search" @click="getList">查询</Button>
+        <Button type="primary" icon="md-add" @click="toAdd">新增</Button>
+      </div>
+      <Table
+        ref="TableList"
+        :columns="columes"
+        :data="tasks"
+        disabled-hover
+        border
+      ></Table>
+    </div>
+
+    <!-- CropperTaskDetailDialog -->
+    <cropper-task-detail-dialog
+      ref="CropperTaskDetailDialog"
+      :cropper-task="curTask"
+    ></cropper-task-detail-dialog>
+    <!-- ModifyCropperTask -->
+    <modify-cropper-task
+      ref="ModifyCropperTask"
+      :instance="curTask"
+      @modified="getList"
+    ></modify-cropper-task>
+  </div>
+</template>
+
+<script>
+import { getCropperTaskList, deleteCropperTaskById } from "../../plugins/db";
+import CropperTaskDetailDialog from "./CropperTaskDetailDialog";
+import ModifyCropperTask from "./ModifyCropperTask";
+
+export default {
+  name: "cropper-task-manage",
+  components: { CropperTaskDetailDialog, ModifyCropperTask },
+  data() {
+    return {
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      tasks: [],
+      curTask: {},
+      columes: [
+        {
+          title: "序号",
+          type: "index",
+          width: 70,
+          align: "center"
+        },
+        {
+          title: "任务名称",
+          key: "name",
+          minWidth: 120
+        },
+        {
+          title: "输入目录",
+          key: "inputDir",
+          minWidth: 200
+        },
+        {
+          title: "导出目录",
+          key: "outputDir",
+          minWidth: 200
+        },
+        {
+          title: "任务总量",
+          key: "taskCount",
+          width: 100
+        },
+        {
+          title: "完成数量",
+          key: "finishedCount",
+          width: 100
+        },
+        {
+          title: "状态",
+          key: "status",
+          width: 100,
+          render: (h, param) => {
+            return h(
+              "span",
+              param.row.taskCount === param.row.finishedCount
+                ? "已完成"
+                : "未完成"
+            );
+          }
+        },
+        {
+          title: "操作",
+          key: "action",
+          width: 220,
+          align: "center",
+          render: (h, param) => {
+            const actions = [
+              {
+                name: "编辑",
+                action: () => {
+                  this.toEdit(param.row);
+                }
+              },
+              {
+                name: "删除",
+                type: "error",
+                action: () => {
+                  this.toDelete(param.row);
+                }
+              },
+              {
+                name: "任务详情",
+                action: () => {
+                  this.toDetail(param.row);
+                }
+              }
+            ];
+            return h("div", this.$tableAction(h, actions));
+          }
+        }
+      ]
+    };
+  },
+  mounted() {
+    this.getList();
+  },
+  methods: {
+    async getList() {
+      const data = await getCropperTaskList({});
+      this.tasks = data || [];
+    },
+    toAdd() {
+      this.curTask = {};
+      this.$refs.ModifyCropperTask.open();
+    },
+    toEdit(row) {
+      this.curTask = row;
+      this.$refs.ModifyCropperTask.open();
+    },
+    toDetail(row) {
+      this.curTask = row;
+      this.$refs.CropperTaskDetailDialog.open();
+    },
+    toDelete(row) {
+      this.$Modal.confirm({
+        content: "确定要删除当前任务吗?",
+        onOk: () => {
+          this.toDel(row.id);
+        }
+      });
+    },
+    async toDel(id) {
+      await deleteCropperTaskById(id);
+      this.$Message.success("删除成功!");
+    }
+  }
+};
+</script>

+ 221 - 0
src/modules/cropper-task/ModifyCropperTask.vue

@@ -0,0 +1,221 @@
+<template>
+  <Modal
+    class="modify-cropper-task"
+    v-model="modalIsShow"
+    :title="title"
+    width="742px"
+    :mask-closable="false"
+    @on-visible-change="visibleChange"
+  >
+    <Form
+      ref="modalFormComp"
+      :model="modalForm"
+      :rules="rules"
+      :key="modalForm.id"
+      :label-width="100"
+    >
+      <FormItem prop="name" label="任务名称:">
+        <Input
+          v-model.trim="modalForm.name"
+          placeholder="请输入任务名称"
+          clearable
+        ></Input>
+      </FormItem>
+      <FormItem prop="inputDir" label="输入目录:">
+        <Input
+          v-model="modalForm.inputDir"
+          readonly
+          placeholder="请选择输入目录"
+          style="width: 500px; margin-right: 15px"
+        ></Input>
+        <Button class="export-paper-btn" type="primary" @click="seleteInputpath"
+          >选择</Button
+        >
+      </FormItem>
+      <FormItem prop="outputDir" label="导出目录:">
+        <Input
+          v-model="modalForm.outputDir"
+          readonly
+          placeholder="请选择导出目录"
+          style="width: 500px; margin-right: 15px"
+        ></Input>
+        <Button class="export-paper-btn" type="primary" @click="seleteOutpath"
+          >选择</Button
+        >
+      </FormItem>
+    </Form>
+    <div slot="footer">
+      <Button type="text" @click="cancel">取消</Button>
+      <Button type="primary" :loading="isSubmit" @click="submit">确认</Button>
+    </div>
+  </Modal>
+</template>
+
+<script>
+import { getValidFiles } from "./taskUtils";
+import {
+  addCropperTask,
+  updateCropperTask,
+  addCropperTaskDetail,
+  deleteCropperTaskDetail
+} from "../../plugins/db";
+const remote = require("electron").remote;
+const path = require("path");
+
+const initModalForm = {
+  id: "",
+  name: "",
+  inputDir: "",
+  outputDir: ""
+};
+
+export default {
+  name: "modify-cropper-task",
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  computed: {
+    isEdit() {
+      return !!this.instance.id;
+    },
+    title() {
+      return (this.isEdit ? "编辑" : "新增") + "修图任务";
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      modalForm: { ...initModalForm },
+      rules: {
+        name: [
+          {
+            required: true,
+            message: "请输入任务名称",
+            trigger: "change"
+          }
+        ],
+        inputDir: [
+          {
+            required: true,
+            message: "请选择输入目录",
+            trigger: "change"
+          }
+        ],
+        outputDir: [
+          {
+            required: true,
+            message: "请选择导出目录",
+            trigger: "change"
+          }
+        ]
+      }
+    };
+  },
+  methods: {
+    initData(val) {
+      if (val.id) {
+        this.modalForm = this.$objAssign(initModalForm, val);
+      } else {
+        this.modalForm = { ...initModalForm };
+      }
+    },
+    visibleChange(visible) {
+      if (visible) {
+        this.initData(this.instance);
+      }
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    seleteInputpath() {
+      remote.dialog.showOpenDialog(
+        {
+          title: "选择输入目录",
+          properties: ["openDirectory"]
+        },
+        folderPaths => {
+          if (folderPaths && folderPaths[0]) {
+            this.modalForm.inputDir = folderPaths[0];
+          }
+        }
+      );
+    },
+    seleteOutpath() {
+      remote.dialog.showOpenDialog(
+        {
+          title: "选择导出目录",
+          properties: ["openDirectory"]
+        },
+        folderPaths => {
+          if (folderPaths && folderPaths[0]) {
+            this.modalForm.outputDir = folderPaths[0];
+          }
+        }
+      );
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate();
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+
+      const taskList = await getValidFiles(this.modalForm.inputDir).catch(
+        () => {
+          this.isSubmit = false;
+        }
+      );
+      console.log(taskList);
+      if (!taskList || !taskList.length) {
+        this.$Message.error("可操作文件获取失败,请重新选择输入目录!");
+        return;
+      }
+
+      const datas = { ...this.modalForm, taskCount: taskList.length };
+      let cropperTaskId = this.modalForm.id;
+      if (this.isEdit) {
+        await updateCropperTask(datas);
+        await deleteCropperTaskDetail(this.instance.id);
+      } else {
+        cropperTaskId = await addCropperTask(datas);
+      }
+
+      // 记录任务详情数据
+      const len = taskList.length;
+      const pageSize = 100;
+      const pageCount = Math.ceil(len / pageSize);
+      for (let i = 0; i < pageCount; i++) {
+        const startNo = i * pageSize;
+        const datas = taskList.slice(startNo, startNo + pageSize).map(item => {
+          return {
+            cropperTaskId,
+            filename: path.basename(item),
+            filepath: item,
+            sheetPath: "",
+            imagesPath: "",
+            thumbsPath: "",
+            cropperSet: "{}"
+          };
+        });
+        await addCropperTaskDetail(datas).catch(error => {
+          console.log(error);
+        });
+      }
+
+      this.isSubmit = false;
+      this.$Message.success(this.title + "成功!");
+      this.$emit("modified");
+      this.cancel();
+    }
+  }
+};
+</script>

+ 9 - 0
src/modules/cropper-task/router.js

@@ -0,0 +1,9 @@
+import CropperTaskManage from "./CropperTaskManage";
+
+export default [
+  {
+    path: "/cropper-task-manage",
+    name: "CropperTaskManage",
+    component: CropperTaskManage
+  }
+];

+ 143 - 0
src/modules/cropper-task/taskUtils.js

@@ -0,0 +1,143 @@
+import { getExtraDir, makeDirSync } from "../../plugins/env";
+const fs = require("fs");
+const path = require("path");
+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;
+        }
+      }
+    });
+  };
+  return new Promise((resolve, reject) => {
+    try {
+      readFiles(folderPath);
+      resolve(paper);
+    } catch (error) {
+      console.log(error);
+      reject(error);
+    }
+  });
+};
+
+export function saveCropperImage(paperInfo, collectConfig) {
+  const imgPath = paperInfo.filepath;
+  const outputSlicelPath = paperInfo.imagesPath;
+  const outputSliceDir = path.dirname(outputSlicelPath);
+  if (!fs.existsSync(outputSliceDir)) makeDirSync(outputSliceDir);
+  const outputThumbsPath = paperInfo.thumbsPath;
+  const outputThumbsDir = path.dirname(outputThumbsPath);
+  if (!fs.existsSync(outputThumbsDir)) makeDirSync(outputThumbsDir);
+
+  const { codeArea, coverArea, tailorTailorArea, imageRotate } = collectConfig;
+
+  // slice图:完整处理流程
+  let imgObj = gm(imgPath);
+  // 条形码覆盖区
+  imgObj
+    .fill("#5f5f5f")
+    .drawRectangle(
+      codeArea.x,
+      codeArea.y,
+      codeArea.x + codeArea.width,
+      codeArea.y + codeArea.height
+    );
+
+  // 保密覆盖区
+  imgObj
+    .fill("#5f5f5f")
+    .drawRectangle(
+      coverArea.x,
+      coverArea.y,
+      coverArea.x + coverArea.width,
+      coverArea.y + coverArea.height
+    );
+
+  // 边缘裁切
+  imgObj.crop(
+    tailorTailorArea.width,
+    tailorTailorArea.height,
+    tailorTailorArea.x,
+    tailorTailorArea.y
+  );
+
+  // 旋转
+  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) {
+        reject(err);
+        return;
+      }
+
+      // 生成缩略图
+      imgObj.resize(500).quality(80);
+      imgObj.write(outputThumbsPath, function(err) {
+        if (err) {
+          reject(err);
+          return;
+        }
+
+        imgObj.resize(500).quality(80);
+
+        resolve({
+          imagesPath: outputSlicelPath,
+          thumbsPath: outputThumbsPath
+        });
+      });
+    });
+  });
+}

+ 216 - 0
src/plugins/db.js

@@ -41,6 +41,23 @@ function serializeWhere(params) {
   };
 }
 
+function serializeUpdate(params) {
+  const template = Object.keys(params)
+    .map(key => {
+      return `${key}=$${key}`;
+    })
+    .join(",");
+  const templateData = {};
+  Object.entries(params).map(([key, val]) => {
+    templateData[`$${key}`] = val;
+  });
+
+  return {
+    template,
+    templateData
+  };
+}
+
 // scan
 function saveUploadInfo(params) {
   const sql = `INSERT INTO scan (examId, examName, subjectId, subjectName, examNumber, studentName, siteCode, roomCode, originImgPath,formalImgPath, sliceImgPath,compressRate,isManual,imageEncrypt,level,clientUserId, clientUsername, clientUserLoginTime, isUpload,createdTime, finishTime) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`;
@@ -526,6 +543,205 @@ function updateTaskFinish(id) {
   });
 }
 
+// cropper-task
+export function getCropperTaskList(params) {
+  const { where, whereData } = serializeWhere(params);
+  let sql = `SELECT * FROM cropper_task`;
+  if (where) {
+    sql += ` WHERE ${where}`;
+  }
+
+  return new Promise((resolve, reject) => {
+    db.all(sql, whereData, (err, rows) => {
+      if (err) reject("search task fail!");
+      resolve(rows);
+    });
+  });
+}
+
+export function deleteCropperTaskById(id) {
+  const sql = `DELETE FROM cropper_task WHERE id=$id`;
+  const datas = {
+    $id: id
+  };
+  return new Promise((resolve, reject) => {
+    db.run(sql, datas, err => {
+      if (err) reject("delete cropper-task fail!");
+      resolve(true);
+    });
+  });
+}
+
+export function deleteCropperTaskDetail(cropperTaskId) {
+  const sql = `DELETE FROM cropper_task_detail WHERE cropperTaskId=$cropperTaskId`;
+  const datas = {
+    $cropperTaskId: cropperTaskId
+  };
+  return new Promise((resolve, reject) => {
+    db.run(sql, datas, err => {
+      if (err) reject("delete cropper-task-detail fail!");
+      resolve(true);
+    });
+  });
+}
+
+export function addCropperTask(params) {
+  const sql = `INSERT INTO cropper_task (name,inputDir,outputDir,taskCount,finishedCount,createTime,updateTime) VALUES (?,?,?,?,?,?,?)`;
+  const datas = [
+    params.name,
+    params.inputDir,
+    params.outputDir,
+    params.taskCount,
+    0,
+    formatDate(), // createTime
+    ""
+  ];
+
+  return new Promise((resolve, reject) => {
+    db.run(sql, datas, function(err) {
+      console.log(err);
+      if (err) reject(err);
+      resolve(this.lastID);
+    });
+  });
+}
+
+export function updateCropperTask(params) {
+  const sql = `UPDATE cropper_task SET name=$name,inputDir=$inputDir,outputDir=$outputDir,taskCount=$taskCount,finishedCount=$finishedCount,updateTime=$updateTime WHERE id=$id`;
+  const datas = {
+    $id: params.id,
+    $name: params.name,
+    $inputDir: params.inputDir,
+    $outputDir: params.outputDir,
+    $taskCount: params.taskCount,
+    $finishedCount: 0,
+    $updateTime: formatDate()
+  };
+  return new Promise((resolve, reject) => {
+    db.run(sql, datas, err => {
+      if (err) reject("update cropper task fail!");
+
+      resolve(true);
+    });
+  });
+}
+
+export function updateCropperTaskFinishedCount(params) {
+  const sql = `UPDATE cropper_task SET finishedCount=$finishedCount,updateTime=$updateTime WHERE id=$id`;
+  const datas = {
+    $id: params.id,
+    $finishedCount: params.finishedCount,
+    $updateTime: formatDate()
+  };
+  return new Promise((resolve, reject) => {
+    db.run(sql, datas, err => {
+      if (err) reject("update cropper task count fail!");
+
+      resolve(true);
+    });
+  });
+}
+
+export function getCropperTaskFinishCount(cropperTaskId) {
+  const { where, whereData } = serializeWhere({
+    cropperTaskId: cropperTaskId,
+    isFinished: 1
+  });
+  const sql = `SELECT COUNT(1) AS count FROM cropper_task_detail WHERE ${where}`;
+
+  return new Promise((resolve, reject) => {
+    db.all(sql, whereData, (err, rows) => {
+      if (err) reject("count task list fail!");
+
+      resolve(rows[0].count);
+    });
+  });
+}
+
+/**
+ * 
+ *CREATE TABLE "cropper_task_detail" (
+  "id" INTEGER NOT NULL,
+  "filename" TEXT NOT NULL ,
+  "filepath" TEXT NOT NULL,
+  "sheetPath" TEXT NOT NULL,
+  "imagesPath" TEXT NOT NULL,
+  "thumbsPath" TEXT NOT NULL,
+  "cropperSet" TEXT NOT NULL,
+  "isFinished" integer NOT NULL DEFAULT 0,
+  "createTime" TEXT NOT NULL DEFAULT '',
+  "updateTime" TEXT NOT NULL DEFAULT '',
+  PRIMARY KEY ("id")
+);
+ */
+
+export function getCropperTaskDetailList(cropperTaskId) {
+  const sql = `SELECT * FROM cropper_task_detail WHERE cropperTaskId=$cropperTaskId`;
+  const datas = {
+    $cropperTaskId: cropperTaskId
+  };
+  return new Promise((resolve, reject) => {
+    db.all(sql, datas, (err, rows) => {
+      if (err) reject("search task fail!");
+      resolve(rows);
+    });
+  });
+}
+
+/**
+ *
+ * @param {Array} paramList 参数列表
+ */
+export function addCropperTaskDetail(paramList) {
+  const listVals = paramList.map(params => {
+    const datas = [
+      params.cropperTaskId,
+      params.filename,
+      params.filepath,
+      params.sheetPath,
+      params.imagesPath,
+      params.thumbsPath,
+      params.cropperSet,
+      0, // isFinished
+      formatDate(), // createTime
+      ""
+    ];
+    const nitem = datas.map(val =>
+      typeof val === "string" ? `'${val}'` : val
+    );
+    return `(${nitem.join(",")})`;
+  });
+  const vals = listVals.join(",");
+
+  const sql = `INSERT INTO cropper_task_detail (cropperTaskId,filename,filepath, sheetPath, imagesPath,thumbsPath,cropperSet,isFinished,createTime,updateTime) VALUES ${vals}`;
+
+  return new Promise((resolve, reject) => {
+    db.run(sql, function(err) {
+      console.log(err);
+      if (err) reject(err);
+      resolve(true);
+    });
+  });
+}
+
+export function updateCropperTaskDetail(params) {
+  if (!params.id) return Promise.reject("id is required");
+
+  const id = params.id;
+  let paramData = { ...params };
+  delete paramData.id;
+  const { template, templateData } = serializeUpdate(paramData);
+  const sql = `UPDATE cropper_task_detail SET ${template} WHERE id=${id}`;
+
+  return new Promise((resolve, reject) => {
+    db.run(sql, templateData, err => {
+      if (err) reject("update cropper task detail fail!");
+
+      resolve(true);
+    });
+  });
+}
+
 export default {
   init,
   // scan

+ 2 - 1
src/router.js

@@ -6,6 +6,7 @@ import Login from "./views/Login";
 // modules
 import client from "./modules/client/router";
 import manage from "./modules/manage/router";
+import cropperTask from "./modules/cropper-task/router";
 
 Vue.use(Router);
 /**
@@ -31,7 +32,7 @@ export default new Router({
       path: "/home",
       name: "Home",
       component: Home,
-      children: [...client, ...manage]
+      children: [...client, ...manage, ...cropperTask]
     }
     // [lazy-loaded] route level code-splitting
     // {

+ 2 - 1
src/views/Login.vue

@@ -158,7 +158,8 @@ export default {
 
       if (data.roleCode === "ADMIN") {
         this.$router.push({
-          name: "PaperExport"
+          name: "CropperTaskManage"
+          // name: "PaperExport"
         });
         return;
       }