zhangjie 5 years ago
parent
commit
cd3f71c044

+ 1 - 4
.env

@@ -1,5 +1,2 @@
 NODE_ENV=development
-VUE_APP_DOMAIN=
-VUE_APP_TIMEOUT=600000
-VUE_APP_PAGE_SIZE=10
-VUE_APP_AUTH_TIMEOUT=7200000
+VUE_APP_DOMAIN=http://localhost:9000/apis

+ 1 - 4
.env.production

@@ -1,5 +1,2 @@
 NODE_ENV=production
-VUE_APP_DOMAIN=
-VUE_APP_TIMEOUT=600000
-VUE_APP_PAGE_SIZE=10
-VUE_APP_AUTH_TIMEOUT=7200000
+VUE_APP_DOMAIN=http://localhost:9000

+ 1 - 1
README.md

@@ -41,7 +41,7 @@ yarn run lint
 
 ## 关于使用第三方工具的处理办法
 
-**所有第三方工具统一存放在目录`extra`文件夹中**
+**所有第三方工具统一存放在目录`extra`文件夹中**
 
 - 设置文件`vue.config.js`
 

+ 1 - 0
package.json

@@ -4,6 +4,7 @@
   "private": true,
   "scripts": {
     "start": "npm run electron:serve",
+    "e:build": "npm run electron:build",
     "serve": "vue-cli-service serve",
     "build": "vue-cli-service build",
     "lint": "vue-cli-service lint",

+ 10 - 0
src/api.js

@@ -0,0 +1,10 @@
+import { $post, $get } from "@/plugins/axios";
+
+export const login = datas => {
+  return $get("/api/user/login", datas);
+  // return Promise.resolve({ id: 1, name: "demo" });
+};
+export const login1 = datas => {
+  return $post("/user/login", datas);
+  // return Promise.resolve({ id: 1, name: "demo" });
+};

+ 2 - 1
src/assets/styles/base.less

@@ -97,7 +97,7 @@ select {
 }
 ::-webkit-scrollbar-thumb {
   border-radius: 8px;
-  background: @gray;
+  background: @dark;
 }
 ::-webkit-scrollbar-corner {
   background: transparent;
@@ -113,4 +113,5 @@ body {
   -moz-osx-font-smoothing: grayscale;
   font-size: @fontSize;
   color: @fontMain;
+  min-width: 1024px;
 }

+ 36 - 11
src/assets/styles/home.less

@@ -4,7 +4,7 @@
   width: 100%;
   height: 100%;
   z-index: auto;
-  min-width: 1024px;
+  min-width: 800px;
 }
 .home-body {
   position: absolute;
@@ -18,8 +18,8 @@
 }
 .home-main {
   position: relative;
-  width: 1024px;
   margin: 0 auto;
+  width: 1024px;
 }
 .home-progress {
   position: fixed;
@@ -56,24 +56,49 @@
 .home-header {
   position: absolute;
   width: 100%;
-  padding: 18px 30px;
+  padding: 16px 20px;
   height: 64px;
-  background: rgba(71, 195, 233, 1);
+  line-height: 32px;
+  background: linear-gradient(
+    90deg,
+    rgba(71, 195, 233, 1) 0%,
+    rgba(92, 158, 238, 1) 100%
+  );
+  // box-shadow: 0px 15px 25px 0px rgba(81, 175, 235, 0.3);
   top: 0;
   left: 0;
   z-index: 100;
 }
-.head-logo {
+.head-back {
   float: left;
+}
+.head-user {
+  float: right;
   color: #fff;
-  height: 28px;
-  line-height: 28px;
-  font-size: 20px;
+}
+.head-user span {
+  display: inline-block;
+  vertical-align: middle;
+  margin-left: 10px;
   font-weight: 600;
+  cursor: pointer;
 }
-.head-back {
-  float: right;
-  margin-top: -4px;
+.user-logout i {
+  vertical-align: middle;
+  margin-top: -5px;
+}
+.user-logout:hover {
+  color: @pink;
+}
+.head-info {
+  margin: 0 150px 0 100px;
+  font-size: 16px;
+  color: #fff;
+  text-align: center;
+
+  > span {
+    margin: 0 10px;
+  }
 }
 
 /* part */

+ 1 - 4
src/assets/styles/iview-custom.less

@@ -136,10 +136,7 @@
 
     border-radius: 27px;
     font-size: 16px;
-    padding: 15px 30px;
-    .ivu-icon {
-      font-size: 22px;
-    }
+    padding: 10px 30px;
   }
 }
 

+ 179 - 133
src/assets/styles/pages.less

@@ -59,10 +59,15 @@
 }
 
 /* setting-steps */
-.scan-area-steps .step-ctrl {
-  text-align: center;
-}
+.scan-area-steps {
+  .step-ctrl {
+    text-align: center;
 
+    > button {
+      width: 100px;
+    }
+  }
+}
 /* code-area */
 .code-area {
   padding: 0 40px;
@@ -192,33 +197,25 @@
 }
 /* waiting */
 .waiting {
-  padding: 80px 137px 20px;
-}
-.waiting-main {
-  height: 400px;
-  background: @backgroundGray;
-  margin-bottom: 30px;
-  border-radius: 10px;
-  padding-top: 112px;
+  padding: 50px 20px;
   text-align: center;
-}
-.waiting-load-tips {
-  font-size: 40px;
-  margin-top: 40px;
-  color: @dark;
-}
-.waiting-icon-load {
-  animation: ani-demo-spin 2s linear infinite;
-}
-@keyframes ani-demo-spin {
-  from {
-    transform: rotate(0deg);
+  font-size: 24px;
+
+  > i {
+    font-size: 40px;
   }
-  50% {
-    transform: rotate(180deg);
+}
+
+/* check-info */
+.check-info {
+  .check-form {
+    padding: 50px 0;
+    width: 300px;
+    margin: 0 auto;
   }
-  to {
-    transform: rotate(360deg);
+  .check-action {
+    margin-top: 30px;
+    text-align: center;
   }
 }
 
@@ -259,115 +256,164 @@
 .task-infos {
   color: @fontMain;
   font-size: 0;
-}
-.task-infos li {
-  display: inline-block;
-  vertical-align: top;
-  width: 50%;
-  margin-bottom: 10px;
-  font-size: 16px;
-}
-.task-infos li:first-child {
-  width: 100%;
-  padding: 0 !important;
-}
-.task-infos li:nth-of-type(odd) {
-  padding-left: 10px;
-}
-.task-infos li:nth-of-type(even) {
-  padding-right: 10px;
-}
-.task-infos li > p {
-  background: @backgroundGray;
-  height: 48px;
-  padding: 10px 25px;
-  line-height: 28px;
-  border-radius: 25px;
-}
-
-.task-infos li span {
-  margin-right: 20px;
-}
-.task-infos li span:first-child {
-  color: @midDark;
+  li {
+    display: inline-block;
+    vertical-align: top;
+    width: 50%;
+    margin-bottom: 10px;
+    font-size: 16px;
+    &:first-child {
+      width: 100%;
+      padding: 0 !important;
+    }
+    &:nth-of-type(odd) {
+      padding-left: 10px;
+    }
+    &:nth-of-type(even) {
+      padding-right: 10px;
+    }
+    > p {
+      background: @backgroundGray;
+      height: 48px;
+      padding: 10px 25px;
+      line-height: 28px;
+      border-radius: 25px;
+    }
+    span {
+      margin-right: 20px;
+    }
+    span:first-child {
+      color: @midDark;
+    }
+  }
 }
 
 /* scan */
-.scan .home-header {
-  height: 88px;
-  padding: 19px 40px;
-  background: linear-gradient(
-    90deg,
-    rgba(71, 195, 233, 1) 0%,
-    rgba(92, 158, 238, 1) 100%
-  );
-  box-shadow: 0px 15px 25px 0px rgba(81, 175, 235, 0.3);
-}
-.scan .head-logo {
-  height: 50px;
-  line-height: 50px;
-  font-size: 32px;
-  font-weight: 600;
-}
-.scan .home-body {
-  top: 88px;
-}
-.scan .home-main {
-  padding: 40px 137px;
-}
-.scan-progress {
-  position: absolute;
-  width: 300px;
-  height: 22px;
-  line-height: 22px;
-  top: 34px;
-  right: 40px;
-  color: #fff;
-  font-size: 16px;
-}
-.scan-progress > span {
-  display: inline-block;
-  vertical-align: middle;
-}
-.scan-main {
-  height: 500px;
-  background: @backgroundGray;
-  margin-bottom: 30px;
-  position: relative;
-}
-.scan-picture img {
-  display: block;
-  position: absolute;
-  max-width: 100%;
-  max-height: 100%;
-  left: 50%;
-  top: 50%;
-  transform: translate(-50%, -50%);
-}
-/* scan-wariting */
-.scan-waiting {
-  padding-top: 140px;
-  border: 2px dashed rgb(221, 222, 224);
-}
-.scan-waiting-icon {
-  width: 240px;
-  height: 187px;
-  background-image: url(../images/bg-camera.png);
-  background-repeat: no-repeat;
-  background-size: 100% 100%;
-  box-shadow: 0px 15px 25px 0px rgba(81, 175, 235, 0.3);
-  margin: 0 auto 32px;
-}
-.scan-waiting-tips {
-  font-size: 20px;
-  font-weight: 500;
-  color: rgba(90, 153, 230, 1);
-  line-height: 28px;
-  text-align: center;
-}
-/* scan-picture */
-.scan-picture {
-  box-shadow: 0px 20px 30px 0px rgba(0, 0, 0, 0.1);
+.scan {
+  &-task {
+    position: fixed;
+    top: 64px;
+    width: 240px;
+    bottom: 0;
+    left: 0;
+    z-index: 99;
+    padding: 15px;
+    font-size: 16px;
+    overflow-y: auto;
+    overflow-x: hidden;
+
+    .task-item {
+      line-height: 30px;
+      color: @gray;
+
+      &.task-over {
+        color: @success-color;
+      }
+
+      > span:first-child {
+        margin-right: 10px;
+      }
+    }
+    &-action {
+      margin-top: 15px;
+
+      > button {
+        margin: 0 5px;
+      }
+    }
+  }
+
+  &-history {
+    position: fixed;
+    top: 64px;
+    width: 240px;
+    bottom: 0;
+    right: 0;
+    z-index: 99;
+    padding: 15px;
+    overflow-y: auto;
+    overflow-x: hidden;
+    .history-item {
+      margin-bottom: 10px;
+
+      &-title {
+        margin-bottom: 10px;
+        text-align: center;
+        > span {
+          margin: 0 5px;
+          font-size: 16px;
+        }
+      }
+      &-body img {
+        display: block;
+        width: 100%;
+        height: auto;
+      }
+    }
+  }
+
+  &-image {
+    margin: 0 240px;
+    padding: 20px 0;
+  }
+
+  &-main {
+    height: 500px;
+    background: @backgroundGray;
+    margin-bottom: 30px;
+    position: relative;
+  }
+
+  /* scan-wariting */
+  &-waiting {
+    padding-top: 140px;
+    border: 2px dashed rgb(221, 222, 224);
+    &-icon {
+      width: 240px;
+      height: 187px;
+      background-image: url(../images/bg-camera.png);
+      background-repeat: no-repeat;
+      background-size: 100% 100%;
+      box-shadow: 0px 15px 25px 0px rgba(81, 175, 235, 0.3);
+      margin: 0 auto 32px;
+    }
+    &-tips {
+      font-size: 20px;
+      font-weight: 500;
+      color: rgba(90, 153, 230, 1);
+      line-height: 28px;
+      text-align: center;
+    }
+  }
+
+  /* scan-picture */
+  &-picture {
+    box-shadow: 0px 20px 30px 0px rgba(0, 0, 0, 0.1);
+    img {
+      display: block;
+      position: absolute;
+      max-width: 100%;
+      max-height: 100%;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+    }
+  }
+  // scan-progress
+  &-progress {
+    position: absolute;
+    width: 300px;
+    height: 22px;
+    line-height: 22px;
+    top: 34px;
+    right: 40px;
+    color: #fff;
+    font-size: 16px;
+    > span {
+      display: inline-block;
+      vertical-align: middle;
+    }
+  }
 }
 
 /* scan-input */

+ 2 - 4
src/config.js

@@ -1,6 +1,4 @@
 export default {
-  domain: process.env.VUE_APP_DOMAIN || window.location.origin,
-  timeout: process.env.VUE_APP_TIMEOUT * 1,
-  pageSize: process.env.VUE_APP_PAGE_SIZE * 1,
-  authTimeout: process.env.VUE_APP_AUTH_TIMEOUT * 1
+  domain: process.env.VUE_APP_DOMAIN,
+  input: ""
 };

+ 0 - 22
src/constants/enumerate.js

@@ -1,25 +1,3 @@
-// 性别
-export const SEX = {
-  M: "男",
-  W: "女"
-};
-
-// 年级
-export const GRADE = {
-  1: "一年级",
-  2: "二年级",
-  3: "三年级",
-  4: "四年级",
-  5: "五年级",
-  6: "六年级",
-  7: "初一",
-  8: "初二",
-  9: "初三",
-  10: "高一",
-  11: "高二",
-  12: "高三"
-};
-
 // 启用/禁用
 export const ABLE_TYPE = {
   DISABLE: "禁用",

+ 8 - 3
src/main.js

@@ -4,8 +4,9 @@ import axios from "axios";
 import App from "./App.vue";
 import router from "./router";
 import store from "./store";
-import GLOBAL from "./config";
 import globalVuePlugins from "./plugins/globalVuePlugins";
+// 系统初始采集配置
+import CONFIG from "./config";
 
 // https://github.com/RobinCK/vue-ls
 import VueLocalStorage from "vue-ls";
@@ -14,10 +15,14 @@ import ViewUI from "view-design";
 import "./assets/styles/index.less";
 import "cropperjs/dist/cropper.min.css";
 
+import { initConfigData } from "@/plugins/env";
+
 Vue.use(ViewUI);
-Vue.use(VueLocalStorage, { storage: "session" });
+Vue.use(VueLocalStorage);
 Vue.use(globalVuePlugins);
 
+// 加载采集配置
+const GLOBAL = initConfigData(CONFIG);
 Vue.prototype.GLOBAL = GLOBAL;
 
 Vue.config.productionTip = false;
@@ -76,7 +81,7 @@ axios.interceptors.request.use(
     }
 
     // 设置延迟时效
-    config.timeout = GLOBAL.timeout;
+    config.timeout = 10 * 60 * 1000;
     return config;
   },
   error => {

+ 16 - 21
src/mixins/initStoreMixin.js

@@ -3,27 +3,22 @@ import { getLocalDate } from "../plugins/utils";
 
 export default {
   methods: {
-    async initStore() {
-      const storeDict = await db.getAllDict();
-      const needInitDict = ["codeArea", "tailorArea", "imageRotate"];
-      for (let key in needInitDict) {
+    async initScanArea(subjects) {
+      const storeScanArea = await db.getDict("scanArea", {});
+      let newScanArea = {};
+      subjects.map(subject => {
+        let info = { ...subject };
         if (
-          !Object.prototype.hasOwnProperty.call(storeDict, needInitDict[key])
-        ) {
-          await db.initDict(needInitDict[key]);
-        }
-      }
-
-      this.$store.commit(
-        "setCodeArea",
-        storeDict.codeArea ? JSON.parse(storeDict.codeArea) : {}
-      );
-      this.$store.commit(
-        "setTailorArea",
-        storeDict.tailorArea ? JSON.parse(storeDict.tailorArea) : {}
-      );
-      this.$store.commit("setImageRotate", storeDict.imageRotate || 0);
+          storeScanArea[subject.id] &&
+          storeScanArea[subject.id].collectConfig
+        )
+          info.collectConfig = storeScanArea[subject.id].collectConfig;
 
+        newScanArea[subject.id] = info;
+      });
+      this.$store.commit("client/setScanArea", newScanArea);
+    },
+    async initStore() {
       // 今日事今日毕的前提下,采集数和上传数只取当天的记录数。
       const curDate = getLocalDate();
       const scanNo = await db.countUploadList({
@@ -34,8 +29,8 @@ export default {
         isUpload: 1
       });
 
-      this.$store.commit("setScanNo", scanNo);
-      this.$store.commit("setUploadNo", uploadNo);
+      this.$store.commit("client/setScanNo", scanNo);
+      this.$store.commit("client/setUploadNo", uploadNo);
     }
   }
 };

+ 9 - 8
src/mixins/uploadTaskMixin.js

@@ -1,6 +1,6 @@
 import db from "../plugins/db";
 import UploadTask from "../plugins/imageUpload";
-import { getLocalDate } from "../plugins/utils";
+// import { getLocalDate } from "../plugins/utils";
 
 /**
  * 上传流程:
@@ -23,12 +23,13 @@ export default {
   },
   methods: {
     async addUploadTask(task) {
-      await db.saveUploadInfo(task);
-      const savedTask = await db.searchUploadList({
-        url: task.url,
-        fileName: task.fileName
+      const id = await db.saveUploadInfo(task).catch(err => {
+        console.log(err);
       });
-      this.uploadTask.addUploadTask(savedTask[0]);
+      if (id) {
+        task.id = id;
+        this.uploadTask.addUploadTask(task);
+      }
     },
     async uploadSuccessCallback(curUploadTask) {
       await db.updateUploadState(curUploadTask.id);
@@ -37,9 +38,9 @@ export default {
     async initUploadTask() {
       if (this.setT) clearTimeout(this.setT);
       // 今日事今日毕的前提下,上传任务只取当天未上传的记录。
-      const curDate = getLocalDate();
+      // const curDate = getLocalDate();
       const unuploadList = await db.searchUploadList({
-        createdTime: curDate,
+        // createdTime: curDate,
         isUpload: 0
       });
       // 创建上传任务

+ 19 - 2
src/modules/client/api.js

@@ -1,11 +1,28 @@
 import { $get, $post } from "@/plugins/axios";
 
-export const uploadImage = datas => {
-  return $post("/backend/course/updateCourseStatus", datas);
+export const uploadFormalImage = (options, datas, config) => {
+  return $post(
+    `/api/file/image/uploadsheet/${options.examId}/${options.subjectId}/${options.examNumber}`,
+    datas,
+    config
+  );
+};
+export const uploadSliceImage = (options, datas, config) => {
+  return $post(
+    `/api/file/image/upload/${options.examId}/${options.subjectId}/${options.examNumber}`,
+    datas,
+    config
+  );
 };
 export const getStudentGroupByExamNumber = datas => {
   return $post("/backend/course/updateCourseStatus", datas);
 };
+export const uploadStudent = datas => {
+  return $post(`/api/upload/student/${datas.subjectId}`, datas);
+};
+export const saveCollectLog = datas => {
+  return $post(`/marklog/saveCollectLog`, { ...datas, workId: datas.examId });
+};
 
 // course-manage
 export const courseList = datas => {

+ 6 - 6
src/modules/client/router.js

@@ -1,14 +1,9 @@
-import Subject from "./views/Subject.vue";
 import Camera from "./views/Camera.vue";
 import ScanArea from "./views/ScanArea.vue";
+import CheckInfo from "./views/CheckInfo.vue";
 import GroupScan from "./views/GroupScan.vue";
 
 export default [
-  {
-    path: "/subject",
-    name: "Subject",
-    component: Subject
-  },
   {
     path: "/camera",
     name: "Camera",
@@ -19,6 +14,11 @@ export default [
     name: "ScanArea",
     component: ScanArea
   },
+  {
+    path: "/check-info",
+    name: "CheckInfo",
+    component: CheckInfo
+  },
   {
     path: "/group-scan",
     name: "GroupScan",

+ 1 - 1
src/modules/client/store.js

@@ -31,7 +31,7 @@ const mutations = {
 };
 
 const actions = {
-  setCurSubject({ state, commit }, curSubject) {
+  updateCurSubject({ state, commit }, curSubject) {
     const scanArea = Object.assign({}, state.scanArea, {
       [curSubject.id]: curSubject
     });

+ 6 - 9
src/modules/client/views/Camera.vue

@@ -1,10 +1,7 @@
 <template>
-  <div class="camera">
+  <div class="camera home-main">
     <div class="part-head">
-      <i-button size="default" type="default" @click="goBack"
-        ><i class="icon icon-left"></i>返回</i-button
-      >
-      <h2>扫描获取相机编码</h2>
+      <h2>获取相机编号</h2>
     </div>
     <div class="camera-form">
       <Form
@@ -48,6 +45,8 @@
 </template>
 
 <script>
+import { mapMutations } from "vuex";
+
 export default {
   name: "camera",
   data() {
@@ -68,9 +67,7 @@ export default {
     };
   },
   methods: {
-    goBack() {
-      this.$router.push({ name: "Login" });
-    },
+    ...mapMutations("client", ["setCamera"]),
     inputOver(code) {
       this.modalForm.camera = code;
     },
@@ -82,7 +79,7 @@ export default {
       const valid = await this.$refs.modalFormComp.validate();
       if (!valid) return;
 
-      this.$store.commit("setCamera", this.modalForm.camera);
+      this.setCamera(this.modalForm.camera);
       this.$router.push({ name: "ScanArea" });
     }
   }

+ 93 - 0
src/modules/client/views/CheckInfo.vue

@@ -0,0 +1,93 @@
+<template>
+  <div class="check-info home-main">
+    <div class="part-head">
+      <h2>请核对试卷信息</h2>
+    </div>
+    <div class="check-form">
+      <Form
+        ref="modalFormComp"
+        :model="modalForm"
+        :rules="rules"
+        :label-width="100"
+      >
+        <FormItem prop="level" label="档位">
+          <Select v-model="modalForm.level">
+            <Option
+              v-for="(level, lindex) in levels"
+              :key="lindex"
+              :value="level.name"
+              >{{ level.name }}</Option
+            >
+          </Select>
+        </FormItem>
+      </Form>
+    </div>
+    <div class="check-action">
+      <Button type="primary" @click="save">下一步</Button>
+    </div>
+
+    <Modal
+      class="modify-data"
+      v-model="modalIsShow"
+      title="确认试卷信息"
+      :mask-closable="false"
+    >
+      <Form :label-width="100">
+        <FormItem label="科目:"> {{ curSubject.name }}</FormItem>
+        <FormItem label="档位:">
+          {{ modalForm.level }}
+        </FormItem>
+      </Form>
+      <div slot="footer">
+        <Button type="primary" @click="confirm">确认</Button>
+        <Button type="text" @click="modalIsShow = false">取消</Button>
+      </div>
+    </Modal>
+  </div>
+</template>
+
+<script>
+import { mapState } from "vuex";
+
+export default {
+  name: "check-info",
+  data() {
+    return {
+      modalIsShow: false,
+      modalForm: {
+        level: ""
+      },
+      rules: {
+        level: [
+          {
+            required: true,
+            message: "请选择档位",
+            trigger: "change"
+          }
+        ]
+      },
+      levels: [
+        {
+          id: 1,
+          name: "A"
+        }
+      ]
+    };
+  },
+  computed: {
+    ...mapState("client", ["curSubject"])
+  },
+  methods: {
+    async save() {
+      const valid = await this.$refs.modalFormComp.validate();
+      if (!valid) return;
+      this.modalIsShow = true;
+    },
+    confirm() {
+      this.modalIsShow = false;
+      // const scanName = this.config.isPackageMode ? "GroupScan" : "LineScan";
+      // this.$router.push({ name: scanName });
+    }
+  }
+};
+</script>

+ 99 - 57
src/modules/client/views/GroupScan.vue

@@ -1,51 +1,52 @@
 <template>
-  <div class="group-scan">
-    <div class="home-header">
-      <div class="head-logo">
-        <h1>试卷采集</h1>
+  <div class="group-scan scan">
+    <div
+      class="scan-task task-list"
+      id="task-list"
+      v-if="studentSerialList.length"
+    >
+      <div
+        v-for="(student, index) in studentSerialList"
+        :key="index"
+        :class="[
+          'task-item',
+          {
+            'task-current': student.isCurrent,
+            'task-over': student.isClient
+          }
+        ]"
+      >
+        <span>{{ student.name }}</span>
+        <span>{{ student.examNumber }}</span>
       </div>
-      <div class="head-back">
-        <i-button type="default" @click="goBack"
-          ><i class="icon-font icon-left"></i>返回</i-button
+      <div class="scan-task-action">
+        <Button type="primary" @click="scanOver" :disabled="holding"
+          >扫描完毕</Button
         >
+        <Button type="primary" @click="allReScan">整包重扫</Button>
       </div>
     </div>
-    <div class="home-body">
-      <div class="home-main home-main-split">
-        <div
-          class="scan-list student-list"
-          id="student-list"
-          v-if="studentSerialList.length"
-        >
-          <div
-            v-for="(student, index) in studentSerialList"
-            :key="index"
-            :class="[
-              'student-item',
-              {
-                'student-current': student.isCurrent,
-                'student-over': student.isClient
-              }
-            ]"
-          >
-            <span>{{ student.name }}</span>
-            <span>{{ student.examNumber }}</span>
-          </div>
-          <div class="scan-btns">
-            <i-button type="primary" @click="scanOver" :disabled="holding"
-              >扫描完毕</i-button
-            >
-            <i-button type="primary" @click="allReScan">整包重扫</i-button>
-          </div>
-        </div>
-        <div class="scan-image">
-          <div class="scan-main scan-waiting" v-if="isWaiting">
-            <p class="scan-waiting-icon"></p>
-            <p class="scan-waiting-tips">等待采集试卷</p>
-          </div>
-          <div class="scan-main scan-picture" v-else>
-            <img class="img-contain" :src="curImage.url" :alt="curImage.name" />
-          </div>
+    <div class="scan-image">
+      <div class="scan-main scan-waiting" v-if="isWaiting">
+        <p class="scan-waiting-icon"></p>
+        <p class="scan-waiting-tips">等待采集试卷</p>
+      </div>
+      <div class="scan-main scan-picture" v-else>
+        <img class="img-contain" :src="curImage.url" :alt="curImage.name" />
+      </div>
+    </div>
+    <div class="scan-history">
+      <div
+        class="history-item"
+        v-for="(task, tindex) in historyList"
+        :key="tindex"
+      >
+        <p class="history-item-title">
+          <span>{{ task.name }}</span
+          ><span>{{ task.examNumber }}</span>
+        </p>
+        <div class="history-item-body">
+          <img :src="task.url" :alt="task.name" />
         </div>
       </div>
     </div>
@@ -75,9 +76,9 @@ import { getStudentGroupByExamNumber } from "../api";
 import {
   decodeImageCode,
   getEarliestFile,
-  saveFormalImage
+  saveOutputImage
 } from "../../../plugins/imageOcr";
-import { formatDate } from "../../../plugins/utils";
+import { deepCopy } from "../../../plugins/utils";
 import ScanAreaDialog from "../components/ScanAreaDialog";
 import ScanExceptionDialog from "../components/ScanExceptionDialog";
 
@@ -94,6 +95,27 @@ export default {
         showAction: true,
         message: ""
       },
+      historyLimit: 30,
+      historyList: [
+        {
+          name: "张一二三",
+          examNumber: "1901040084",
+          url:
+            "http://127.0.0.1:9000/api/file/image/download/31/1/734/1?random=676176fc-24cd-407a-a7bc-fabc49bd2dbc"
+        },
+        {
+          name: "张一二三",
+          examNumber: "1901040084",
+          url:
+            "http://127.0.0.1:9000/api/file/image/download/31/1/733/1?random=e2966291-ffba-4b19-985d-0cad9ae1b75b"
+        },
+        {
+          name: "张一二三",
+          examNumber: "1901040084",
+          url:
+            "http://127.0.0.1:9000/api/file/image/download/31/1/731/1?random=b305c27c-76d0-4477-a1ef-c9b8554a4671"
+        }
+      ],
       setT: "",
       holding: false,
       curImage: {
@@ -129,8 +151,12 @@ export default {
     }
   },
   mounted() {
+    if (!this.curSubject || !this.curSubject.collectConfig) {
+      this.$Message.error("请先完成采集设置!");
+      return;
+    }
     // this.getInitFile();
-    this.test();
+    // this.test();
   },
   methods: {
     async test() {
@@ -182,7 +208,8 @@ export default {
         if (this.setT) clearTimeout(this.setT);
         return;
       }
-      this.curImage = getEarliestFile();
+
+      this.curImage = getEarliestFile(this.GLOBAL.input);
 
       if (this.curImage.url) {
         if (this.setT) clearTimeout(this.setT);
@@ -213,7 +240,6 @@ export default {
         codeArea
       ).catch(error => {
         const content = `图像:${this.curImage.name},解析错误,错误信息:${error}`;
-
         this.$Notice.error({ title: "错误提示", desc: content, duration: 0 });
       });
 
@@ -246,7 +272,9 @@ export default {
     async checkStudentValid(examNumber) {
       let validInfo = { valid: true, message: "" };
       if (!this.students.length) {
-        const students = await getStudentGroupByExamNumber(examNumber);
+        const students = await getStudentGroupByExamNumber(
+          examNumber
+        ).catch(() => {});
         if (students && students.length) {
           this.students = students.map(item => {
             return {
@@ -271,6 +299,7 @@ export default {
       this.curStudent = this.students.find(
         item => item.examNumber === examNumber
       );
+      // 当前组的第一位考生必定是当前组,但第二位考生则不一定是当前组。
       if (!this.curStudent) {
         validInfo = {
           valid: false,
@@ -278,11 +307,12 @@ export default {
         };
       } else {
         this.curStudent.isCurrent = true;
+        this.curStudent.imgPath = this.curImage.url;
       }
       return validInfo;
     },
     async toSaveStudent(examNumber, type) {
-      const outputFile = await saveFormalImage(
+      const result = await saveOutputImage(
         this.curImage.url,
         {
           workId: this.user.workId,
@@ -292,16 +322,18 @@ export default {
         this.getCurCollectConfig()
       ).catch(error => {
         const content = `${this.curStudent.name}的试卷保存失败,请重新扫描!`;
-
         this.$Notice.error({ title: "错误提示", desc: content, duration: 5 });
       });
 
-      if (outputFile) {
+      if (result) {
         this.curStudent = Object.assign(this.curStudent, {
           isCurrent: false,
           isClient: true,
+          formalImgPath: result.outputFormalPath,
+          sliceImgPath: result.outputSlicelPath,
           isManual: type === "MANUAL"
         });
+        this.updateHistory(this.curStudent);
       }
       // 删除扫描文件,继续开始下一个任务
       fs.unlinkSync(this.curImage.url);
@@ -314,7 +346,7 @@ export default {
       if (this.holding) return;
       this.holding = true;
       if (!this.checkAllStudentIsClient()) {
-        this.$Message.error("当前考场试卷没有扫完!");
+        this.$Message.error("当前整包试卷没有扫描完毕!");
         this.holding = false;
         return;
       }
@@ -322,19 +354,21 @@ export default {
       for (let i = 0, len = this.students.length; i < len; i++) {
         const curStudent = this.students[i];
         await this.$parent.addUploadTask({
-          workId: this.user.workId,
-          workName: this.user.workName,
+          id: null,
+          examId: this.user.examId,
+          examName: this.user.examName,
           subjectId: this.curSubject.id,
           subjectName: this.curSubject.name,
           examNumber: curStudent.examNumber,
           studentName: curStudent.name,
           siteCode: curStudent.siteCode,
           roomCode: curStudent.roomCode,
-          createdTime: formatDate(),
+          formalImgPath: curStudent.formalImgPath,
+          sliceImgPath: curStudent.sliceImgPath,
           isManual: curStudent.isManual,
           clientUserId: this.user.id,
           clientUsername: this.user.name,
-          clientUserLoginTime: this.user.clientUserLoginTime
+          clientUserLoginTime: this.user.loginTime
         });
       }
 
@@ -347,6 +381,14 @@ export default {
     getCurCollectConfig() {
       return this.curStudent.collectConfig || this.curSubject.collectConfig;
     },
+    // history
+    updateHistory(curStudent) {
+      const student = deepCopy(curStudent);
+      if (this.historyList.length >= this.historyLimit) {
+        this.historyList.pop();
+      }
+      this.historyList.unshift(student);
+    },
     // scan-exception
     resetConfig() {
       this.curCollectConfig = this.getCurCollectConfig();

+ 18 - 29
src/modules/client/views/ScanArea.vue

@@ -1,24 +1,14 @@
 <template>
-  <div class="scan-area">
-    <div class="home-header">
-      <div class="head-logo">
-        <h1>试卷采集区域设置</h1>
-      </div>
-      <div class="head-back">
-        <i-button type="default" @click="goback"
-          ><i class="icon icon-left"></i>返回</i-button
-        >
-      </div>
-    </div>
-    <div class="home-body">
-      <div class="home-main">
-        <scan-area-steps
-          :image-url="curImage.url"
-          :cur-setting="curSetting"
-          @on-finished="finished"
-          v-if="curImage.url"
-        ></scan-area-steps>
-      </div>
+  <div class="scan-area home-main">
+    <scan-area-steps
+      :image-url="curImage.url"
+      :cur-setting="curSetting"
+      @on-finished="finished"
+      v-if="curImage.url"
+    ></scan-area-steps>
+    <div class="waiting" v-else>
+      <Icon class="ivu-load-loop" type="ios-loading" />
+      <p>获取图片中,请稍后...</p>
     </div>
   </div>
 </template>
@@ -26,7 +16,7 @@
 <script>
 import ScanAreaSteps from "../components/ScanAreaSteps";
 import { getEarliestFile } from "../../../plugins/imageOcr";
-import { mapActions } from "vuex";
+import { mapState, mapActions } from "vuex";
 
 export default {
   name: "scan-area",
@@ -47,16 +37,14 @@ export default {
     };
   },
   computed: {
-    curSubject() {
-      return this.$store.state.curSubject;
-    }
+    ...mapState("client", ["curSubject"])
   },
-  created() {
+  mounted() {
     this.curSetting = this.curSubject.collectConfig || {};
     this.getInitFile();
   },
   methods: {
-    ...mapActions("client", ["setCurSubject"]),
+    ...mapActions("client", ["updateCurSubject"]),
     getInitFile() {
       this.curImage = getEarliestFile();
       if (this.curImage.url) {
@@ -72,10 +60,11 @@ export default {
       const curSubject = Object.assign({}, this.curSubject, {
         collectConfig: setting
       });
-      this.setCurSubject(curSubject);
+      this.updateCurSubject(curSubject);
 
-      const scanName = this.config.isPackageMode ? "GroupScan" : "LineScan";
-      this.$router.push({ name: scanName });
+      this.$router.push({ name: "CheckInfo" });
+      // const scanName = this.config.isPackageMode ? "GroupScan" : "LineScan";
+      // this.$router.push({ name: scanName });
     }
   },
   beforeDestroy() {

+ 15 - 18
src/modules/client/views/Subject.vue

@@ -18,40 +18,37 @@
 </template>
 
 <script>
-import { mapMutations } from "vuex";
+import { mapState, mapMutations } from "vuex";
 
 export default {
   name: "subject",
   data() {
     return {
-      // subjects: this.$ls.get("subjects", [])
-      subjects: [
-        {
-          id: 1,
-          name: "素描"
-        },
-        {
-          id: 2,
-          name: "绘画"
-        }
-      ]
+      subjects: []
     };
   },
   computed: {
-    scanArea() {
-      return this.$store.state.scanArea;
+    ...mapState("client", ["scanArea"])
+  },
+  mounted() {
+    const user = this.$ls.get("user");
+    if (user && user.subjects) {
+      this.subjects = user.subjects;
     }
   },
   methods: {
     ...mapMutations("client", ["setCurSubject"]),
     selectSubject(subject) {
-      // const curSuject = this.scanArea[subject.id] || subject;
-      // this.$ls.set("curSuject", curSuject);
-      // this.setCurSubject(curSuject);
+      const curSuject = this.scanArea[subject.id] || subject;
+      this.$ls.set("curSuject", curSuject);
+      this.setCurSubject(curSuject);
 
       this.$router.push({
-        name: "Camera"
+        name: "GroupScan"
       });
+      // this.$router.push({
+      //   name: "Camera"
+      // });
     }
   }
 };

+ 19 - 16
src/plugins/db.js

@@ -1,4 +1,4 @@
-import { getDatabasePath } from "./env";
+import { getDatabaseDir } from "./env";
 import { formatDate } from "./utils";
 const path = require("path");
 const fs = require("fs");
@@ -10,7 +10,7 @@ init();
 
 function init() {
   if (db) return;
-  const databasePath = getDatabasePath();
+  const databasePath = getDatabaseDir();
   var orgDb = path.join(databasePath, "org.rdb");
   var clientDb = path.join(databasePath, "client.rdb");
 
@@ -21,20 +21,23 @@ function init() {
 }
 
 function saveUploadInfo(params) {
-  const sql = `INSERT INTO scan (taskId, ticketNumber, fileName, url, camera, type, courseCode, specialtyCode, score, isUpload, userId, username, createdTime, fininshTime) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`;
+  const sql = `INSERT INTO scan (examId, examName, subjectId, subjectName, examNumber, studentName, siteCode, roomCode, formalImgPath, sliceImgPath,isManual, clientUserId, clientUsername, clientUserLoginTime, isUpload,createdTime, fininshTime) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`;
   const datas = [
-    params.taskId,
-    params.ticketNumber,
-    params.fileName,
-    params.url,
-    params.camera,
-    params.type,
-    params.courseCode,
-    params.specialtyCode,
-    params.score,
+    params.examId,
+    params.examName,
+    params.subjectId,
+    params.subjectName,
+    params.examNumber,
+    params.studentName,
+    params.siteCode,
+    params.roomCode,
+    params.formalImgPath,
+    params.sliceImgPath,
+    params.isManual,
+    params.clientUserId,
+    params.clientUsername,
+    params.clientUserLoginTime,
     0, // isUpload
-    params.userId,
-    params.username,
     formatDate(), // createdTime
     null
   ];
@@ -43,7 +46,7 @@ function saveUploadInfo(params) {
       db.run(sql, datas, err => {
         if (err) reject("save upload info to database fail!");
 
-        resolve();
+        resolve(this.lastId);
       });
     });
   });
@@ -125,7 +128,7 @@ function getDict(key, defaultVal = "") {
   return new Promise((resolve, reject) => {
     db.get(sql, key, (err, row) => {
       if (err) reject(`get dict ${key} fail!`);
-      resolve(row.val || defaultVal);
+      resolve((row && row.val) || defaultVal);
     });
   });
 }

+ 41 - 39
src/plugins/env.js

@@ -1,3 +1,5 @@
+import config from "../config";
+
 const path = require("path");
 const fs = require("fs");
 const { getLocalDate } = require("./utils");
@@ -12,59 +14,47 @@ initPath();
 function initPath() {
   const paths = [
     storePath,
-    getInImgPath(),
-    getOurDirPath(),
-    getOutImgPath(),
-    getTmpImgPath()
+    getInputDir(),
+    getStoresDir("out"),
+    getOutputDir("formal"),
+    getOutputDir("slice"),
+    getTmpDir()
   ];
   paths.forEach(path => {
     if (!fs.existsSync(path)) fs.mkdirSync(path);
   });
 }
 // base
-function getPath(name) {
+function getHomeDir(name) {
   return path.join(homePath, name);
 }
 
-function getStoresPath(name) {
+function getStoresDir(name) {
   return path.join(storePath, name);
 }
 
-function getExtraPath(name) {
+function getExtraDir(name) {
   return path.join(extraPath, name);
 }
 
 // stores
-function getInImgPath() {
-  return getStoresPath("in");
-}
-function getOurDirPath() {
-  return getStoresPath("out");
+function getInputDir() {
+  return getStoresDir("in");
 }
-function getOutImgPath() {
-  const outPath = getOurDirPath();
-  return path.join(outPath, getLocalDate());
+function getOutputDir(type) {
+  return path.join(getStoresDir("out"), type);
 }
-function getTmpImgPath() {
-  return getStoresPath("tmp");
+function getTmpDir() {
+  return getStoresDir("tmp");
 }
 
 // extra
-function getDatabasePath() {
-  return getExtraPath("database");
+function getDatabaseDir() {
+  return getExtraDir("database");
 }
 
 function getImgDecodeTool() {
-  return path.join(getExtraPath("zxing"), "zxing.exe");
-}
-
-/**
- *
- * @param {Object} studentInfo 学生信息
- * @param {String} type 路径类型
- */
-function getOutputImagePath(fileName, type) {
-  return path.join(getOutImgPath(), type, fileName);
+  return path.join(getExtraDir("zxing"), "zxing.exe");
 }
 
 /**
@@ -77,8 +67,7 @@ function makeDirSync(pathContent) {
 
   while (!fs.existsSync(curPath)) {
     mkPathList.unshift(curPath);
-    let pos = curPath.lastIndexOf("\\");
-    curPath = curPath.substring(0, pos);
+    curPath = path.dirname(curPath);
   }
 
   mkPathList.forEach(path => {
@@ -86,15 +75,28 @@ function makeDirSync(pathContent) {
   });
 }
 
+function initConfigData(data) {
+  let configData = { ...data };
+  const configPath = path.join(homePath, "config.json");
+  if (fs.existsSync(configPath)) {
+    configData = JSON.parse(fs.readFileSync(configPath, "utf8"));
+    if (!configData.input) configData.input = getInputDir();
+  } else {
+    if (!configData.input) configData.input = getInputDir();
+    fs.writeFileSync(configPath, JSON.stringify(configData), "utf8");
+  }
+
+  return configData;
+}
+
 export {
   initPath,
-  getPath,
-  getExtraPath,
-  getInImgPath,
-  getOutImgPath,
-  getTmpImgPath,
-  getOutputImagePath,
+  getHomeDir,
+  getExtraDir,
+  getInputDir,
+  getTmpDir,
   makeDirSync,
-  getDatabasePath,
-  getImgDecodeTool
+  getDatabaseDir,
+  getImgDecodeTool,
+  initConfigData
 };

+ 89 - 30
src/plugins/imageOcr.js

@@ -1,17 +1,18 @@
 import {
-  getTmpImgPath,
-  getInImgPath,
-  getOutImgPath,
-  getExtraPath,
-  getImgDecodeTool
+  getTmpDir,
+  getInputDir,
+  getOutputDir,
+  getExtraDir,
+  getImgDecodeTool,
+  makeDirSync
 } from "./env";
-import { randomCode } from "./utils";
+import { randomCode, formatDate } from "./utils";
 const fs = require("fs");
 const path = require("path");
 const childProcess = require("child_process");
 const gm = require("gm").subClass({
   imageMagick: true,
-  appPath: getExtraPath("imagemagick/")
+  appPath: getExtraDir("imagemagick/")
 });
 
 /**
@@ -20,7 +21,10 @@ const gm = require("gm").subClass({
  * @param {Object} codeArea code所在文件区域信息
  */
 function decodeImageCode(imgPath, codeArea) {
-  const tmpFile = path.join(getTmpImgPath(), randomCode() + ".jpg");
+  const tmpFile = path.join(
+    getTmpDir(),
+    `${formatDate("YYYYMMDDHHmmss")}_${randomCode(8)}.jpg`
+  );
 
   const imgObj = gm(imgPath);
 
@@ -34,16 +38,11 @@ function decodeImageCode(imgPath, codeArea) {
     // 写入临时文件
     imgObj.write(tmpFile, function(err) {
       if (err) {
-        reject("error:crop barcode area wrong");
+        reject("条形码临时文件获取失败");
         return;
       }
       // 获取条形码解析工具
       const exec = getImgDecodeTool();
-      if (!exec) {
-        reject("error:invalid decoder config, should be zxing or zbar");
-        return;
-      }
-
       // 解析条形码
       let code;
       try {
@@ -56,10 +55,10 @@ function decodeImageCode(imgPath, codeArea) {
           code = codes[codes.length - 1].replace(/\s+/g, "");
         }
       } catch (e) {
-        reject("error:decode exception");
+        reject("解析条形码错误");
         return;
       }
-      // 测试时暂时不删除临时文件
+      // 暂时不删除临时文件
       // fs.unlink(tmpFile, () => {});
 
       resolve(code);
@@ -70,37 +69,97 @@ function decodeImageCode(imgPath, codeArea) {
 /**
  * 旋转图片,并保存为正式文件
  * @param {*} imgPath 图片路径
- * @param {String} fileName 保持文件名称
- * @param {Object} tailorArea 裁剪区域
- * @param {Number} rotate 旋转角度
+ * @param {String} paperInfo 保持文件名称
+ * @param {Object} collectConfig 裁剪区域
  */
-function saveFormalImage(imgPath, fileName, tailorArea, rotate) {
-  // TODO:根据图片生成对应的后缀路径
-  const outputFile = path.join(getOutImgPath(), fileName + ".jpg");
-  const imgObj = gm(imgPath);
+async function saveOutputImage(imgPath, paperInfo, collectConfig) {
+  const outputFormalPath = await saveFormalImage(
+    imgPath,
+    paperInfo,
+    collectConfig
+  ).catch(() => {});
+  const outputSlicelPath = await saveSliceImage(
+    imgPath,
+    paperInfo,
+    collectConfig
+  ).catch(() => {});
+
+  if (outputSlicelPath && outputFormalPath)
+    return { outputSlicelPath, outputFormalPath };
+  return Promise.reject("试卷保存失败");
+}
+
+function saveFormalImage(imgPath, paperInfo, collectConfig) {
+  const outputFormalPath = getOutputImagePath(paperInfo, "formal");
+  const { tailorArea } = collectConfig;
+
+  let imgObj = gm(imgPath);
+  // formal图:只裁切边缘,不覆盖信息
+  imgObj.crop(tailorArea.width, tailorArea.height, tailorArea.x, tailorArea.y);
+
+  return new Promise((resolve, reject) => {
+    imgObj.write(outputFormalPath, function(err) {
+      if (err) {
+        reject(err);
+      }
+      resolve(outputFormalPath);
+    });
+  });
+}
+
+function saveSliceImage(imgPath, paperInfo, collectConfig) {
+  const outputSlicelPath = getOutputImagePath(paperInfo, "slice");
+  const { codeArea, coverArea, tailorArea, imageRotate } = collectConfig;
+
+  // slice图:完整处理流程
+  let imgObj = gm(imgPath);
+  // 条形码覆盖区
+  imgObj
+    .fill("#FFFFFF")
+    .drawRectangle(
+      codeArea.x,
+      codeArea.y,
+      codeArea.x + codeArea.width,
+      codeArea.y + codeArea.height
+    );
+
+  // 保密覆盖区
+  imgObj
+    .fill("#FFFFFF")
+    .drawRectangle(
+      coverArea.x,
+      coverArea.y,
+      coverArea.x + coverArea.width,
+      coverArea.y + coverArea.height
+    );
 
   // 边缘裁切
   imgObj.crop(tailorArea.width, tailorArea.height, tailorArea.x, tailorArea.y);
 
   // 旋转
-  if (rotate) imgObj.rotate("#FFFFFF", rotate);
-
+  if (imageRotate) imgObj.rotate("#FFFFFF", imageRotate);
   return new Promise((resolve, reject) => {
-    imgObj.write(outputFile, function(err) {
+    imgObj.write(outputSlicelPath, function(err) {
       if (err) {
-        reject("error: write file wrong");
+        reject(err);
       }
-      resolve(outputFile);
+      resolve(outputSlicelPath);
     });
   });
 }
 
+function getOutputImagePath({ workId, subjectId, examNumber }, type) {
+  const outputDir = path.join(getOutputDir(type), workId, subjectId);
+  if (!fs.existsSync(outputDir)) makeDirSync(outputDir);
+  return path.join(outputDir, examNumber + ".jpg");
+}
+
 /**
  * 获取最早添加的文件
  * @param {String} dir 图片目录
  */
 function getEarliestFile(dir) {
-  const ddir = dir || getInImgPath();
+  const ddir = dir || getInputDir();
   const files = fs
     .readdirSync(ddir)
     .filter(fileName => fileName.toLowerCase().match(/\.(jpg|png|jpeg)/))
@@ -120,4 +179,4 @@ function getEarliestFile(dir) {
   };
 }
 
-export { decodeImageCode, saveFormalImage, getEarliestFile };
+export { decodeImageCode, saveOutputImage, getEarliestFile };

+ 37 - 22
src/plugins/imageUpload.js

@@ -1,28 +1,44 @@
 const fs = require("fs");
 const crypto = require("crypto");
-import { uploadImage } from "../modules/client/api";
+import {
+  uploadSliceImage,
+  uploadFormalImage,
+  uploadStudent,
+  saveCollectLog
+} from "../modules/client/api";
 
 /**
  * 文件上传
- * @param {String} filePath 文件路径
  * @param {Object} options 上传配置信息
+ * @param {String} type 上传类型:formal=>原图,slice=>剪切图
  */
-function toUploadImg(filePath, options) {
+function toUploadImg(options, type) {
   const formData = new FormData();
+  const filePath =
+    type === "formal" ? options.formalImgPath : options.sliceImgPath;
 
   const buffer = fs.readFileSync(filePath);
   let fsHash = crypto.createHash("md5");
   fsHash.update(buffer);
-  formData.append("md5", fsHash.digest("hex"));
+  let md5 = fsHash.digest("hex");
+  formData.append("md5", md5);
 
-  const file = new File([buffer], options.fileName + ".jpg");
+  const file = new File([buffer], options.examNumber + ".jpg");
   formData.append("file", file);
 
-  Object.entries(options).map(([key, value]) => {
-    formData.append(key, value);
-  });
+  return type === "formal"
+    ? uploadFormalImage(options, formData, { headers: { md5 } })
+    : uploadSliceImage(options, formData, { headers: { md5 } });
+}
 
-  return uploadImage(formData);
+function toUploadStudent(options) {
+  const datas = {
+    subjectId: options.subjectId,
+    examNumber: options.examNumber,
+    absent: false,
+    manual: options.isManual
+  };
+  return uploadStudent(datas);
 }
 
 /**
@@ -62,21 +78,20 @@ class UploadTask {
     }
     this.taskRunning = true;
     const curTask = this.getCurTask();
-    const uploadFilePath = curTask.url;
-
-    let uploadResult = "";
-    if (uploadFilePath) {
-      uploadResult = await toUploadImg(uploadFilePath, curTask).catch(() => {});
-      // 失败一次之后,只再重复传一次
-      if (!uploadResult)
-        uploadResult = await toUploadImg(
-          uploadFilePath,
-          curTask
-        ).catch(() => {});
-    }
+
+    const uploadReq = [
+      toUploadImg(curTask, "formal"),
+      toUploadImg(curTask, "slice")
+    ];
+    const uploadResult = await Promise.all(uploadReq).catch(() => {});
 
     if (uploadResult) {
-      this.uploadSuccessCallback(curTask);
+      const updateReq = [toUploadStudent(curTask), saveCollectLog(curTask)];
+      const updateResult = await Promise.all(updateReq).catch(() => {});
+
+      if (updateResult) {
+        this.uploadSuccessCallback(curTask);
+      }
     }
 
     this.setT = setTimeout(() => {

+ 6 - 0
src/router.js

@@ -3,6 +3,7 @@ import Router from "vue-router";
 
 import Home from "./views/Home";
 import Login from "./views/Login";
+import Subject from "./modules/client/views/Subject.vue";
 // modules
 import client from "./modules/client/router";
 
@@ -26,6 +27,11 @@ export default new Router({
       name: "Login",
       component: Login
     },
+    {
+      path: "/subject",
+      name: "Subject",
+      component: Subject
+    },
     {
       path: "/home",
       name: "Home",

+ 38 - 22
src/views/Home.vue

@@ -1,6 +1,27 @@
 <template>
   <div class="home">
-    <router-view />
+    <div class="home-header">
+      <div class="head-back">
+        <i-button type="default" @click="goback"
+          ><i class="icon icon-left"></i>返回</i-button
+        >
+      </div>
+      <div class="head-user">
+        <span class="user-name"
+          ><Icon type="md-person" size="16" /> {{ username }}</span
+        >
+        <span class="user-logout" @click="logout">
+          <Icon type="md-power" size="20" />
+        </span>
+      </div>
+      <div class="head-info">
+        <span>{{ examName }}</span>
+        <span>{{ curSubject.name }}</span>
+      </div>
+    </div>
+    <div class="home-body">
+      <router-view />
+    </div>
 
     <div class="home-progress" v-if="showProgress">
       <div class="progress-item progress-name">
@@ -20,50 +41,45 @@
         ></i-progress>
       </div>
     </div>
-
-    <div
-      class="home-logout"
-      style="position: fixed;bottom: 40px;right: 20px;z-index: 1000;;height:30px;width:30px;font-size:20px;;background: #fff;color:#333;text-align: center;line-height: 30px;cursor: pointer;"
-      @click="$router.push('Login')"
-    >
-      <Icon type="md-log-out" />
-    </div>
   </div>
 </template>
 
 <script>
 import authUnvalidMixin from "../mixins/authUnvalidMixin";
-import initStoreMixin from "../mixins/initStoreMixin";
 import uploadTaskMixin from "../mixins/uploadTaskMixin";
+import { mapState } from "vuex";
 
 export default {
   name: "home",
-  mixins: [authUnvalidMixin, initStoreMixin, uploadTaskMixin],
+  mixins: [authUnvalidMixin, uploadTaskMixin],
   data() {
     return {
-      showProgress: false
+      showProgress: false,
+      examName: ""
     };
   },
   computed: {
-    scanNo() {
-      return this.$store.state.scanNo;
-    },
-    uploadNo() {
-      return this.$store.state.uploadNo;
-    },
+    ...mapState("client", ["curSubject", "scanNo", "uploadNo"]),
     curProgress() {
       return !this.scanNo ? 0 : Math.ceil((this.uploadNo * 100) / this.scanNo);
+    },
+    username() {
+      return this.$store.state.user.name;
     }
   },
   created() {
+    this.examName = this.$ls.get("user", { examName: "" }).examName;
     // TODO:遵循file-upload项目的逻辑
-    // 探索多线程编程
-    // this.initStore();
     // this.initUploadProgress();
   },
-  methods: {},
+  methods: {
+    logout() {
+      this.$ls.clear();
+      this.$router.push({ name: "Login" });
+    }
+  },
   beforeDestroy() {
-    this.stopUpload();
+    // this.stopUpload();
   }
 };
 </script>

+ 22 - 19
src/views/Login.vue

@@ -8,9 +8,9 @@
         <h2>欢迎登录</h2>
         <div class="login-form">
           <Form ref="loginForm" :model="loginModel" :rules="loginRules">
-            <FormItem prop="loginName">
+            <FormItem prop="loginname">
               <Input
-                v-model.trim="loginModel.loginName"
+                v-model.trim="loginModel.loginname"
                 prefix="md-person"
                 placeholder="请输入用户名"
                 clearable
@@ -26,11 +26,7 @@
               ></Input>
             </FormItem>
             <FormItem>
-              <Button
-                long
-                type="primary"
-                :disabled="isSubmit"
-                @click="submit('loginForm')"
+              <Button long type="primary" :disabled="isSubmit" @click="submit"
                 >登录</Button
               >
             </FormItem>
@@ -43,18 +39,22 @@
 
 <script>
 import { username, password } from "@/plugins/formRules";
-import { login } from "./api";
+import initStoreMixin from "../mixins/initStoreMixin";
+import { formatDate } from "../plugins/utils";
+
+import { login } from "@/api";
 
 export default {
   name: "login",
+  mixins: [initStoreMixin],
   data() {
     return {
       loginModel: {
-        loginName: "test1",
-        password: "123456"
+        loginname: "test1",
+        password: "111111"
       },
       loginRules: {
-        loginName: username,
+        loginname: username,
         password
       },
       isSubmit: false
@@ -64,24 +64,27 @@ export default {
     this.$ls.clear();
   },
   methods: {
-    submit() {
+    submit1() {
       this.$router.push({
         name: "Subject"
       });
     },
-    async submit1(name) {
-      const valid = await this.$refs[name].validate();
+    async submit(name) {
+      const valid = await this.$refs.loginForm.validate();
       if (!valid) return;
 
       if (this.isSubmit) return;
       this.isSubmit = true;
-      const data = await login(this.loginModel).catch(() => {
-        this.isSubmit = false;
-      });
+      const data = await login(this.loginModel).catch(() => {});
+      this.isSubmit = false;
       if (!data) return;
 
-      this.isSubmit = false;
-      this.$ls.set("user", data, this.GLOBAL.authTimeout);
+      await this.initScanArea(data.subjects);
+      await this.initStore();
+
+      data.loginTime = formatDate();
+
+      this.$ls.set("user", data);
       this.$store.commit("setUser", data);
       this.$router.push({
         name: "Subject"

+ 0 - 6
src/views/api.js

@@ -1,6 +0,0 @@
-// import { $post } from "@/plugins/axios";
-
-export const login = datas => {
-  // return $post("/login", datas);
-  return Promise.resolve({ id: 1, name: "demo" });
-};