浏览代码

图片再处理修改

zhangjie 3 年之前
父节点
当前提交
6f771c7874

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

@@ -44,8 +44,8 @@
         <div class="task-body">
         <div class="task-body">
           <div class="task-body-item task-org">
           <div class="task-body-item task-org">
             <scan-area-steps
             <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"
               :cur-setting="curCollectConfig"
               :key="curTask.key"
               :key="curTask.key"
               @on-finished="finished"
               @on-finished="finished"
@@ -55,16 +55,13 @@
           <div class="task-body-item task-finally">
           <div class="task-body-item task-finally">
             <div class="task-finally-body">
             <div class="task-finally-body">
               <img
               <img
-                v-if="curTaskImagesPath"
+                v-if="curTask.sliceImgPath"
                 class="img-contain"
                 class="img-contain"
-                :src="curTaskImagesPath"
+                :src="curTask.sliceImgPath"
                 alt="裁切图"
                 alt="裁切图"
               />
               />
             </div>
             </div>
-            <div
-              v-if="curTaskImagesPath && !curTask.isFinished"
-              class="task-action box-justify"
-            >
+            <div v-if="curTask.sliceImgPath" class="task-action box-justify">
               <h5>处理结果</h5>
               <h5>处理结果</h5>
               <Button
               <Button
                 type="primary"
                 type="primary"
@@ -89,10 +86,11 @@ import {
   updateCropperTaskDetail,
   updateCropperTaskDetail,
   updateCropperTaskFinishedCount
   updateCropperTaskFinishedCount
 } from "../../plugins/db";
 } from "../../plugins/db";
-import { saveCropperImage } from "./taskUtils";
+import { saveCropperImage, downloadOriginImg } from "./taskUtils";
 import { formatDate, randomCode } from "../../plugins/utils";
 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 {
 export default {
   name: "cropper-task-detail-dialog",
   name: "cropper-task-detail-dialog",
@@ -119,7 +117,6 @@ export default {
       taskCount: 0,
       taskCount: 0,
       finishedCount: 0,
       finishedCount: 0,
       taskProgress: 0,
       taskProgress: 0,
-      curTaskImagesPath: null,
       loading: false
       loading: false
     };
     };
   },
   },
@@ -175,55 +172,56 @@ export default {
       const scrollTop = Math.max(0, taskDom.offsetTop - 84);
       const scrollTop = Math.max(0, taskDom.offsetTop - 84);
       document.getElementById("task-list").scrollTop = scrollTop;
       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) {
     async toDo(task) {
-      // TODO:操作流程与正常采集过程保持一致,需要点时间整理,数据表需要再调整。
-      const [sheetPath, imagesPath, thumbsPath] = this.getTaskOutputFilePath(
-        task.filepath
-      );
       this.curTask = {
       this.curTask = {
         ...task,
         ...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) {
     async curTaskFinished(setting) {
       await updateCropperTaskDetail({
       await updateCropperTaskDetail({
         id: this.curTask.id,
         id: this.curTask.id,
         cropperSet: JSON.stringify(setting),
         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,
         isFinished: 1,
+        isUpload: 0,
         updateTime: formatDate()
         updateTime: formatDate()
       });
       });
       await this.updateProgress();
       await this.updateProgress();
@@ -233,13 +231,26 @@ export default {
 
 
       return true;
       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) {
     async finished(setting) {
       if (this.loading) return;
       if (this.loading) return;
       this.loading = true;
       this.loading = true;
 
 
-      this.curTaskImagesPath = null;
       this.curCollectConfig = setting;
       this.curCollectConfig = setting;
-      let res = await saveCropperImage(this.curTask, setting).catch(() => {
+
+      let res = await this.saveCurImage().catch(() => {
         this.loading = false;
         this.loading = false;
       });
       });
       if (!res) {
       if (!res) {
@@ -247,8 +258,6 @@ export default {
         return;
         return;
       }
       }
 
 
-      this.curTaskImagesPath = this.curTask.imagesPath;
-
       res = await this.curTaskFinished(setting).catch(() => {});
       res = await this.curTaskFinished(setting).catch(() => {});
       this.loading = false;
       this.loading = false;
       if (!res) {
       if (!res) {
@@ -256,19 +265,7 @@ export default {
         return;
         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() {
     async toComfirmAndNext() {
       if (this.loading) return;
       if (this.loading) return;
@@ -283,6 +280,9 @@ export default {
         return;
         return;
       }
       }
 
 
+      this.toNextTask();
+    },
+    toNextTask() {
       const nextTask = this.taskList.find(item => !item.isFinished);
       const nextTask = this.taskList.find(item => !item.isFinished);
       if (!nextTask) {
       if (!nextTask) {
         this.$Message.success("当前任务已全部结束");
         this.$Message.success("当前任务已全部结束");

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

@@ -50,6 +50,7 @@ import {
 const remote = require("electron").remote;
 const remote = require("electron").remote;
 const fs = require("fs");
 const fs = require("fs");
 const nodeXlsx = require("node-xlsx");
 const nodeXlsx = require("node-xlsx");
+import { mapState } from "vuex";
 
 
 const initModalForm = {
 const initModalForm = {
   id: "",
   id: "",
@@ -68,6 +69,7 @@ export default {
     }
     }
   },
   },
   computed: {
   computed: {
+    ...mapState("client", ["clientConfig"]),
     isEdit() {
     isEdit() {
       return !!this.instance.id;
       return !!this.instance.id;
     },
     },
@@ -229,9 +231,10 @@ export default {
               subjectName,
               subjectName,
               examNumber,
               examNumber,
               studentName,
               studentName,
-              sheetPath: "",
-              imagesPath: "",
-              thumbsPath: "",
+              originImgPath: "",
+              formalImgPath: "",
+              sliceImgPath: "",
+              imageEncrypt: this.clientConfig.imageEncrypt,
               cropperSet: "{}"
               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 fs = require("fs");
 const path = require("path");
 const path = require("path");
+const request = require("request");
 const gm = require("gm").subClass({
 const gm = require("gm").subClass({
   imageMagick: true,
   imageMagick: true,
   appPath: getExtraDir("imagemagick/")
   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) => {
   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) {
 // function saveThumbsImage(imgPath, outputThumbsPath) {
 //   const outputThumbsDir = path.dirname(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) {
 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;
   const { codeArea, coverArea, tailorTailorArea, imageRotate } = collectConfig;
 
 
@@ -129,11 +121,6 @@ function saveSliceImage(paperInfo, collectConfig) {
   // 旋转
   // 旋转
   if (imageRotate) imgObj.rotate("#FFFFFF", imageRotate);
   if (imageRotate) imgObj.rotate("#FFFFFF", imageRotate);
 
 
-  // 压缩
-  if (paperInfo.compressRate !== 100) {
-    imgObj.setFormat("jpeg").quality(paperInfo.compressRate);
-  }
-
   return new Promise((resolve, reject) => {
   return new Promise((resolve, reject) => {
     imgObj.write(outputSlicelPath, function(err) {
     imgObj.write(outputSlicelPath, function(err) {
       if (err) {
       if (err) {
@@ -147,10 +134,7 @@ function saveSliceImage(paperInfo, collectConfig) {
 
 
 export async function saveCropperImage(paperInfo, collectConfig) {
 export async function saveCropperImage(paperInfo, collectConfig) {
   const outputSlicelPath = await saveSliceImage(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,
   "id" INTEGER NOT NULL,
-  "examId" INTEGER NOT NULL,
+  "cropperTaskId" INTEGER NOT NULL,
+  "examId" text NOT NULL,
+  "paperId" text,
   "subjectId" text NOT NULL,
   "subjectId" text NOT NULL,
-  "subjectName" text NOT NULL,
+  "subjectName" TEXT,
   "examNumber" text NOT NULL,
   "examNumber" text NOT NULL,
   "studentName" 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,
   "isFinished" integer NOT NULL DEFAULT 0,
   "createTime" TEXT NOT NULL DEFAULT '',
   "createTime" TEXT NOT NULL DEFAULT '',
   "updateTime" TEXT NOT NULL DEFAULT '',
   "updateTime" TEXT NOT NULL DEFAULT '',
@@ -697,13 +702,16 @@ export function addCropperTaskDetail(paramList) {
     const datas = [
     const datas = [
       params.cropperTaskId,
       params.cropperTaskId,
       params.examId,
       params.examId,
+      params.paperId || "",
       params.subjectId,
       params.subjectId,
       params.subjectName,
       params.subjectName,
       params.examNumber,
       params.examNumber,
       params.studentName,
       params.studentName,
-      params.sheetPath,
-      params.imagesPath,
-      params.thumbsPath,
+      params.originImgPath,
+      params.formalImgPath,
+      params.sliceImgPath,
+      params.imageEncrypt,
+      0, // isUpload
       params.cropperSet,
       params.cropperSet,
       0, // isFinished
       0, // isFinished
       formatDate(), // createTime
       formatDate(), // createTime
@@ -716,7 +724,7 @@ export function addCropperTaskDetail(paramList) {
   });
   });
   const vals = listVals.join(",");
   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) => {
   return new Promise((resolve, reject) => {
     db.run(sql, function(err) {
     db.run(sql, function(err) {

+ 7 - 1
src/plugins/env.js

@@ -15,7 +15,12 @@ function initPath() {
     getStoresDir("out"),
     getStoresDir("out"),
     getOutputDir("formal"),
     getOutputDir("formal"),
     getOutputDir("slice"),
     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 => {
   paths.forEach(path => {
     if (!fs.existsSync(path)) fs.mkdirSync(path);
     if (!fs.existsSync(path)) fs.mkdirSync(path);
@@ -110,6 +115,7 @@ function initConfigData(data) {
 export {
 export {
   initPath,
   initPath,
   getHomeDir,
   getHomeDir,
+  getStoresDir,
   getExtraDir,
   getExtraDir,
   getInputDir,
   getInputDir,
   getOutputDir,
   getOutputDir,

+ 8 - 1
src/router.js

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

+ 6 - 31
src/views/Home.vue

@@ -1,41 +1,16 @@
 <template>
 <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>
         <span>{{ examName }}</span>
         <i v-if="curSubject && curSubject.name"></i>
         <i v-if="curSubject && curSubject.name"></i>
         <span>{{ curSubject.name }}</span>
         <span>{{ curSubject.name }}</span>
       </div>
       </div>
-    </div>
-    <div class="home-body">
-      <router-view />
-    </div>
-  </div>
+    </template>
+  </Layout>
 </template>
 </template>
 
 
 <script>
 <script>
+import Layout from "./Layout.vue";
 import uploadTaskMixin from "../mixins/uploadTaskMixin";
 import uploadTaskMixin from "../mixins/uploadTaskMixin";
 import setTimeMixins from "../mixins/setTimeMixins";
 import setTimeMixins from "../mixins/setTimeMixins";
 import { mapState, mapMutations } from "vuex";
 import { mapState, mapMutations } from "vuex";
@@ -48,11 +23,11 @@ const { ipcRenderer } = require("electron");
 export default {
 export default {
   name: "home",
   name: "home",
   mixins: [uploadTaskMixin, setTimeMixins],
   mixins: [uploadTaskMixin, setTimeMixins],
+  components: { Layout },
   data() {
   data() {
     return {
     return {
       showProgress: false,
       showProgress: false,
       examName: "",
       examName: "",
-      isDev: process.env.NODE_ENV === "development",
       // 路由历史只前进,不后退
       // 路由历史只前进,不后退
       backRouters: {
       backRouters: {
         PaperExport: "Login",
         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>