Browse Source

add rotate md

zhangjie 4 years ago
parent
commit
3cdb244939

BIN
extra/database/org.rdb


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

@@ -110,7 +110,7 @@ body {
     "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
-  font-size: @fontSize;
+  font-size: @font-size;
   color: @fontMain;
   min-width: 1024px;
 }

+ 75 - 17
src/assets/styles/common-component.less

@@ -28,10 +28,11 @@
     text-align: center;
     font-size: 40px;
     color: #fff;
+    text-shadow: 0 0 2px #333;
     cursor: pointer;
 
     &:hover {
-      color: @pink;
+      color: @error-color;
     }
   }
   &-body {
@@ -46,13 +47,15 @@
   &-imgs {
     position: absolute;
     width: 600px;
-    box-shadow: 0px 24px 36px 0px rgba(0, 0, 0, 0.3);
-    cursor: move;
-    transition: width, height, transform 0.1s linear;
+    // box-shadow: 0px 24px 36px 0px rgba(0, 0, 0, 0.3);
+    transition: width, height, transform 0.2s linear;
     z-index: 8;
     &-nosition {
       transition: none;
     }
+    &-move {
+      cursor: move;
+    }
     > img {
       display: block;
       width: 100%;
@@ -68,12 +71,12 @@
     text-align: center;
     color: #d0d0d0;
     z-index: 9;
-    cursor: pointer;
     font-size: 60px;
+    text-shadow: 0 0 2px #333;
+    cursor: pointer;
 
     &:hover {
-      color: #fff;
-      border-color: #b0b0b0;
+      color: #eee;
     }
     > i {
       margin-top: -5px;
@@ -87,14 +90,13 @@
   }
   &-footer {
     position: absolute;
-    width: 100%;
     height: 60px;
     bottom: 0;
-    left: 0;
+    left: 50%;
+    transform: translateX(-50%);
     padding: 10px;
-    text-align: center;
     font-size: 30px;
-    color: #d0d0d0;
+    color: #333;
     z-index: 99;
     li {
       display: inline-block;
@@ -103,11 +105,12 @@
       width: 40px;
       line-height: 40px;
       margin: 0 5px;
+      text-align: center;
       cursor: pointer;
       transition: transform 0.2s linear;
     }
     li:hover {
-      color: #fff;
+      // color: #000;
       transform: scale(1.1, 1.1);
     }
     li.li-disabled {
@@ -117,9 +120,46 @@
     }
   }
 
+  &-loading {
+    position: absolute;
+    width: 60px;
+    height: 60px;
+    top: 50%;
+    left: 50%;
+    margin: -30px 0 0 -30px;
+    color: @main-color;
+    font-size: 50px;
+    text-align: center;
+    line-height: 60px;
+    z-index: 99;
+  }
+
+  &-preload {
+    position: absolute;
+    z-index: 9;
+    width: 100px;
+    height: 100px;
+    overflow: hidden;
+    top: -1000px;
+    left: -1000px;
+  }
+
+  &-none {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    color: #e0e0e0;
+    text-align: center;
+    font-size: 20px;
+    > i {
+      font-size: 30px;
+    }
+  }
+
   // view-ui
   .ivu-modal-content {
-    background: transparent;
+    background: transparent !important;
   }
   .ivu-modal-header {
     display: none;
@@ -216,18 +256,36 @@
 // import-file
 .cc-import-file {
   &-tips {
-    height: 20px;
     line-height: 20px;
   }
+  &-body {
+    color: @dark-color-light;
+  }
   &-footer {
     padding: 10px 0;
     font-size: 14px;
+    color: @dark-color-light;
+    a {
+      color: @main-color;
+    }
     a:hover {
-      color: @mainColor;
+      color: @info-color;
     }
   }
 }
 
+// upload-button
+.cc-upload-button {
+  display: inline-block;
+  vertical-align: middle;
+  .ivu-upload {
+    display: inline-block;
+  }
+  > p[class^="cc-tips"] {
+    margin-top: 10px;
+  }
+}
+
 // rich-editor
 .cc-editor {
   .ql-editor {
@@ -240,9 +298,9 @@
 // tips
 .cc-tips {
   &-success {
-    color: @mainColor;
+    color: @main-color;
   }
   &-error {
-    color: @pink;
+    color: @error-color;
   }
 }

+ 7 - 3
src/assets/styles/home.less

@@ -48,7 +48,7 @@
       line-height: 30px;
       border-radius: 50%;
       color: @fontSub;
-      background-color: @backgroundLight;
+      background-color: @background-light;
       text-align: center;
       cursor: pointer;
 
@@ -59,7 +59,7 @@
   }
 
   .action-logout:hover {
-    color: @pink;
+    color: @pink-color;
   }
   .head-info {
     margin: 0 150px 0 100px;
@@ -87,7 +87,7 @@
   min-height: 490px;
   margin: 0 auto 40px;
   border-radius: 40px;
-  background-color: @backgroundLight;
+  background-color: @background-light;
   overflow: auto;
 }
 .part-head {
@@ -100,6 +100,10 @@
     margin-right: 15px;
   }
 }
+.part-page {
+  margin-top: 15px;
+  text-align: center;
+}
 /* move-bar */
 .move-bar {
   position: fixed;

+ 1 - 0
src/assets/styles/index.less

@@ -5,5 +5,6 @@
 @import "./home.less";
 @import "./login.less";
 @import "./pages.less";
+@import "./manage.less";
 @import "./icons.less";
 @import "./common-component.less";

+ 58 - 30
src/assets/styles/iview-custom.less

@@ -15,55 +15,26 @@
 // select - ok
 .ivu-select {
   .ivu-select-dropdown {
-    margin: 10px 0;
     background: rgba(46, 50, 90, 1);
     box-shadow: 0px 0px 40px 0px rgba(4, 5, 17, 0.3);
-    border-radius: 20px;
-    padding: 20px 15px;
+    border-radius: 10px;
   }
   .ivu-select-item {
-    display: inline-block;
-    vertical-align: top;
-    padding: 0 10px;
-    width: 64px;
-    height: 36px;
-    line-height: 32px;
-    border-radius: 10px;
-    border: 2px solid #3b427a;
     color: @fontSub;
-    margin: 0 5px 10px;
     background-color: transparent;
-    text-align: center;
 
     &:hover,
     &-selected {
-      border: 2px solid #4e7cff;
       color: #4e7cff;
     }
   }
   .ivu-select-selection {
-    height: 60px;
-    padding: 13px;
-    font-size: 16px;
-    border-radius: 20px;
-    text-align: center;
     background-color: #212543 !important;
     border-color: #212543 !important;
   }
   .ivu-select-selected-value {
     color: @fontMain;
   }
-  .ivu-select-arrow {
-    width: 14px;
-    height: 12px;
-    right: 20px;
-    background-image: url(../images/icon-more.png);
-    background-size: 100% 100%;
-
-    &::before {
-      content: "";
-    }
-  }
 }
 .ivu-select-default {
   .ivu-select-item {
@@ -220,3 +191,60 @@
     color: @fontMain;
   }
 }
+
+// ivu-page
+.ivu-page {
+  color: @fontSub;
+  &-item {
+    border-radius: @box-border-radius-small;
+    height: 32px;
+    width: 32px;
+    line-height: 30px;
+    min-width: 32px;
+    background-color: @background-light;
+    border-color: @background-light;
+    a {
+      color: @dark-color-lighter;
+    }
+
+    &-active {
+      background-color: @background-light;
+      border-color: @main-color;
+
+      a {
+        color: @fontSub;
+        &:hover {
+          color: @fontMain;
+        }
+      }
+    }
+  }
+
+  &-prev,
+  &-next {
+    border-radius: @box-border-radius-small;
+    border-color: @background-light;
+    background-color: @background-light;
+    color: @fontMain;
+    height: 32px;
+    width: 32px;
+    line-height: 30px;
+    min-width: 32px;
+  }
+  &-options-elevator {
+    input {
+      border-radius: 10px;
+      background-color: @background !important;
+      border-color: @background-light !important;
+      color: @fontSub;
+    }
+  }
+
+  &-options-sizer {
+    width: 80px;
+    .ivu-select {
+      min-width: auto;
+      width: 100%;
+    }
+  }
+}

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

@@ -0,0 +1,151 @@
+.paper-manage {
+  .part-box-filter {
+    border-radius: 10px;
+    background-color: @background-light;
+    margin: 0 20px 20px;
+    padding: 20px;
+    .ivu-form-item {
+      margin-bottom: 0;
+    }
+  }
+  .paper-list {
+    padding: 20px;
+  }
+}
+
+// image-action-list
+// image-view
+.image-view {
+  display: inline-block;
+  vertical-align: top;
+  font-size: @font-size-base;
+  padding: 10px;
+  width: 310px;
+  text-align: center;
+  &-act {
+    img {
+      // box-shadow: 0px 10px 20px 0px rgba(34, 192, 255, 0.5);
+      box-shadow: 0 0 20px rgba(34, 192, 255, 0.6);
+      // box-shadow: -20px 0px 40px 0px rgba(224, 225, 235, 1);
+    }
+  }
+
+  &-container {
+    padding: 10px;
+  }
+
+  &-title {
+    font-size: 18px;
+    line-height: 25px;
+    margin-bottom: 15px;
+  }
+  &-contain {
+    position: relative;
+    height: 0;
+    padding-bottom: 100%;
+
+    > img {
+      position: absolute;
+      max-width: 100%;
+      max-height: 100%;
+      width: auto;
+      height: auto;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      margin: auto;
+      cursor: pointer;
+    }
+    > .image-info {
+      display: block;
+      position: absolute;
+      bottom: 5px;
+      left: 50%;
+      transform: translateX(-50%);
+      z-index: 9;
+      padding: 5px 8px;
+      line-height: 1;
+      border-radius: @box-border-radius-small;
+      background-color: rgba(255, 255, 255, 0.8);
+      font-size: 16px;
+      color: rgba(255, 112, 129, 1);
+      font-weight: 600;
+    }
+  }
+  &-actions {
+    margin-top: 15px;
+    height: 32px;
+    position: relative;
+    .ivu-btn {
+      position: absolute;
+      top: 0;
+      left: 50%;
+    }
+    .ivu-btn-default {
+      background-color: @background-light;
+    }
+    .view-action-save {
+      margin-left: -78px;
+      background-color: @main-color;
+    }
+    .view-action-rotate {
+      margin-left: -16px;
+    }
+    .view-action-absent {
+      margin-left: 26px;
+    }
+  }
+  &-multibar {
+    width: 30%;
+    border-radius: @box-border-radius-small;
+    background-color: @background-color;
+    height: 20px;
+    margin: 0 auto;
+    cursor: pointer;
+
+    &:hover {
+      background-color: shade(@background-color, 5%);
+    }
+    &.image-view-selected {
+      background-color: @sub-color;
+    }
+    &.image-view-disabled {
+      cursor: not-allowed;
+      background-color: @background-color;
+    }
+  }
+}
+.image-view-list {
+  font-size: 0;
+  margin: -10px;
+  .image-view {
+    width: 20%;
+  }
+
+  &-6 {
+    .image-view {
+      width: 16.66%;
+    }
+  }
+  &-5 {
+    .image-view {
+      width: 20%;
+    }
+  }
+  &-4 {
+    .image-view {
+      width: 25%;
+    }
+  }
+  &-3 {
+    .image-view {
+      width: 33.33%;
+    }
+  }
+  &-2 {
+    .image-view {
+      width: 50%;
+    }
+  }
+}

+ 103 - 11
src/assets/styles/pages.less

@@ -1,3 +1,41 @@
+/* action-type */
+.action-type {
+  .action-list {
+    position: absolute;
+    z-index: 9;
+    width: 100%;
+    height: 260px;
+    top: 50%;
+    margin-top: -130px;
+    color: @fontMain;
+
+    ul {
+      font-size: 0;
+      text-align: center;
+    }
+    li {
+      display: inline-block;
+      vertical-align: top;
+      width: 200px;
+      height: 200px;
+      font-size: 28px;
+      border-radius: 30px;
+      background-color: #4e7cff;
+      line-height: 40px;
+      padding: 80px 10px 0;
+      margin: 0 20px;
+      cursor: pointer;
+
+      &:hover {
+        opacity: 0.8;
+      }
+
+      &:nth-of-type(2) {
+        background-color: @purple-color;
+      }
+    }
+  }
+}
 /* subject */
 .subject-list {
   position: absolute;
@@ -38,11 +76,11 @@
     }
 
     &:nth-of-type(2) {
-      background-color: @purple;
+      background-color: @purple-color;
       background-image: url(../images/subject-back-two.png);
     }
     &:nth-of-type(3) {
-      background-color: @pink;
+      background-color: @pink-color;
       background-image: url(../images/subject-back-three.png);
     }
   }
@@ -88,7 +126,7 @@
   margin-top: 50px;
 }
 .camera-write:hover {
-  color: @subColor;
+  color: @sub-color;
 }
 .camera-btn {
   position: absolute;
@@ -168,7 +206,7 @@
     text-align: center;
     line-height: 36px;
     &:hover {
-      background-color: @blue;
+      background-color: @sub-color;
     }
   }
 }
@@ -176,7 +214,7 @@
 .rotate-icon {
   width: 40px;
   height: 40px;
-  border: 2px solid @blue;
+  border: 2px solid @sub-color;
   border-radius: 16px;
   padding: 10px;
   cursor: pointer;
@@ -205,7 +243,7 @@
   }
 
   &:hover {
-    background-color: @blue;
+    background-color: @sub-color;
   }
 }
 /* .cropper-line */
@@ -214,7 +252,7 @@
     opacity: 0.7;
   }
   &-line {
-    background-color: @blue;
+    background-color: @sub-color;
     opacity: 1;
     &.line-e {
       right: -2px;
@@ -243,7 +281,7 @@
   }
   &-point {
     background-color: rgba(51, 51, 51, 0.7);
-    border: 2px solid @blue;
+    border: 2px solid @sub-color;
     border-radius: 50%;
     opacity: 1;
     width: 10px !important;
@@ -360,6 +398,60 @@
       width: 160px;
     }
   }
+
+  .ivu-select {
+    .ivu-select-dropdown {
+      margin: 10px 0;
+      padding: 20px 15px;
+    }
+    .ivu-select-item {
+      display: inline-block;
+      vertical-align: top;
+      padding: 0 10px;
+      width: 64px;
+      height: 36px;
+      line-height: 32px;
+      border-radius: 10px;
+      border: 2px solid #3b427a;
+      margin: 0 5px 10px;
+      text-align: center;
+
+      &:hover,
+      &-selected {
+        border: 2px solid #4e7cff;
+      }
+    }
+    .ivu-select-selection {
+      height: 60px;
+      padding: 13px;
+      font-size: 16px;
+      border-radius: 20px;
+      text-align: center;
+    }
+    .ivu-select-arrow {
+      width: 14px;
+      height: 12px;
+      right: 20px;
+      background-image: url(../images/icon-more.png);
+      background-size: 100% 100%;
+
+      &::before {
+        content: "";
+      }
+    }
+  }
+  .ivu-select-default {
+    .ivu-select-item {
+      font-size: 14px !important;
+    }
+    .ivu-select-selection {
+      .ivu-select-placeholder,
+      .ivu-select-selected-value {
+        padding-left: 16px;
+        font-size: 14px;
+      }
+    }
+  }
 }
 .check-result {
   text-align: center;
@@ -424,7 +516,7 @@
       padding: 12px 10px 12px 58px;
       color: @fontSub;
       border-radius: 10px;
-      background-color: @backgroundLight;
+      background-color: @background-light;
       position: relative;
       margin-bottom: 10px;
 
@@ -516,7 +608,7 @@
 
   &-main {
     height: 456px;
-    background: @backgroundLight;
+    background: @background-light;
     margin-bottom: 20px;
     position: relative;
   }
@@ -642,7 +734,7 @@
       vertical-align: top;
     }
     &:hover {
-      color: @pink;
+      color: @pink-color;
     }
   }
   .exception-code-area {

+ 19 - 17
src/assets/styles/variables.less

@@ -1,28 +1,30 @@
 @fontMain: #fff;
 @fontSub: #737aae;
 @background: #25294a;
-@backgroundLight: #2d325a;
+@background-light: #2d325a;
 
-@mainColor: #4e7cff;
-@subColor: #47c3e9;
-@tinyColor: #e3fdfc;
+@font-color-main: #353d57;
+@background-color: #eff0f5;
 
-@pink: #f65164;
-@hoverPink: #fe697a;
+@main-color: #4e7cff;
+@sub-color: #22c0ff;
+@tiny-color: #e3fdfc;
 
-@purple: #7033ff;
+@error-color-light: fade(@error-color, 5%);
 
-@blue: #22c0ff;
+@disabled-color: #bdc2d1;
 
-@dark: #4f535c;
-@midDark: #777d8a;
-@gray: #999;
+@pink-color: #f65164;
+@purple-color: #6d32f9;
+@brown-color: #dd7755;
 
-@tipsGray: #bfc1c4;
-@unactBtn: #d7d9df;
-@shadowGray: #e0e0e0;
-@borderGray: #dde2ed;
-@boldBorderGray: #d2d3d6;
+@dark-color: #353d57;
+@dark-color-light: #7c86a3;
+@dark-color-lighter: #8c94ac;
+
+@box-border-radius: 20px;
+@box-border-radius-large: 25px;
+@box-border-radius-small: 10px;
 
 // size
-@fontSize: 14px;
+@font-size: 14px;

+ 11 - 0
src/constants/enumerate.js

@@ -9,3 +9,14 @@ export const GENDER_TYPE = {
   MALE: "男",
   FEMALE: "女"
 };
+
+export const SORT_RULE_TYPE = {
+  1: "按时间",
+  2: "按考号"
+};
+
+// cafa-exception
+export const CAFA_EXCEPTION_TYPE = {
+  0: "缺考",
+  1: "手工绑定"
+};

+ 30 - 0
src/mixins/uploadTaskMixin.js

@@ -51,6 +51,36 @@ export default {
           this.setT = setTimeout(() => {
             this.initUploadTask();
           }, 5 * 1000);
+        },
+        uploadErrorCallBack: curUploadTask => {
+          const content = `考生:${curUploadTask.studentName},准考证:${curUploadTask.examNumber},科目:${curUploadTask.subjectName},图片上传失败!`;
+          console.log(content);
+          return;
+
+          // this.$Notice.error({
+          //   duration: 0,
+          //   name: curUploadTask.id,
+          //   render: h => {
+          //     return h("div", [
+          //       h("p", content),
+          //       h(
+          //         "Button",
+          //         {
+          //           props: {
+          //             type: "primary"
+          //           },
+          //           on: {
+          //             click: async () => {
+          //               await db.deleteScanById(curUploadTask.id);
+          //               this.$Notice.close(curUploadTask.id);
+          //             }
+          //           }
+          //         },
+          //         "确定"
+          //       )
+          //     ]);
+          //   }
+          // });
         }
       });
     },

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

@@ -1,3 +1,4 @@
+import ActionType from "./views/ActionType.vue";
 import Subject from "./views/Subject.vue";
 import Camera from "./views/Camera.vue";
 import ScanArea from "./views/ScanArea.vue";
@@ -7,6 +8,11 @@ import GroupScan from "./views/GroupScan.vue";
 import LineScan from "./views/LineScan.vue";
 
 export default [
+  {
+    path: "/action-type",
+    name: "ActionType",
+    component: ActionType
+  },
   {
     path: "/subject",
     name: "Subject",

+ 30 - 0
src/modules/client/views/ActionType.vue

@@ -0,0 +1,30 @@
+<template>
+  <div class="action-type part-box">
+    <div class="part-head">
+      <h2>请选择功能</h2>
+    </div>
+    <div class="action-list">
+      <ul>
+        <li @click="toClient">试卷采集</li>
+        <li @click="toPaperManage">图片旋转</li>
+      </ul>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "action-type",
+  data() {
+    return {};
+  },
+  methods: {
+    toClient() {
+      this.$router.push({ name: "Subject" });
+    },
+    toPaperManage() {
+      this.$router.push({ name: "PaperManage" });
+    }
+  }
+};
+</script>

+ 16 - 0
src/modules/manage/api.js

@@ -0,0 +1,16 @@
+import { $post } from "@/plugins/axios";
+import db from "@/plugins/db";
+
+export const paperPageList = datas => {
+  return db.getPaperList(datas);
+};
+export const areaList = subjectId => {
+  return db.getAreas(subjectId);
+};
+
+export const absentPaper = paperId => {
+  return $post(`/api/score/missing/${paperId}`, {});
+};
+export const absentLocalPaper = (id, missing) => {
+  return db.absentLocalPaper(id, missing);
+};

+ 148 - 0
src/modules/manage/components/ImageActionList.vue

@@ -0,0 +1,148 @@
+<template>
+  <div :class="classes">
+    <div class="image-view" v-for="(image, index) in data" :key="image.id">
+      <div class="image-view-container">
+        <h5 class="image-view-title">{{ image.title }}</h5>
+        <div class="image-view-contain" :style="image.styles">
+          <img
+            :src="image.thumbSrc"
+            :alt="image.title"
+            @click="toReview(index)"
+          />
+        </div>
+        <div class="image-view-actions" v-if="actions.length">
+          <Button
+            class="view-action-save"
+            size="small"
+            type="primary"
+            @click="toSaveRotate(image)"
+            :disabled="saving"
+            v-if="canRotate && image['stepDeg']"
+            >保存</Button
+          >
+          <Button
+            class="view-action-rotate"
+            size="small"
+            icon="md-refresh"
+            @click="toRotate(image)"
+            v-if="canRotate"
+          ></Button>
+          <Button
+            class="view-action-absent"
+            :type="image.missing ? 'error' : 'default'"
+            size="small"
+            @click="toSignAbsent(image)"
+            v-if="canAbsent"
+            >缺考</Button
+          >
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { absentPaper, absentLocalPaper } from "../api";
+import { toUploadImg } from "@/plugins/imageUpload";
+import { rotateImage } from "@/plugins/imageOcr";
+
+export default {
+  name: "image-action-list",
+  props: {
+    data: {
+      type: Array,
+      default() {
+        return [];
+      }
+    },
+    actions: {
+      type: Array,
+      default() {
+        return [];
+      }
+    },
+    columnNumber: {
+      type: Number,
+      default: 5
+    }
+  },
+  data() {
+    return {
+      curImageIndex: 0,
+      stepDeg: 0,
+      saving: false
+    };
+  },
+  computed: {
+    classes() {
+      return [
+        "image-action-list",
+        "image-view-list",
+        `image-view-list-${this.columnNumber}`
+      ];
+    },
+    canRotate() {
+      return this.actions.includes("rotate");
+    },
+    canAbsent() {
+      return this.actions.includes("absent");
+    }
+  },
+  methods: {
+    toRotate(image) {
+      if (!image["stepDeg"]) this.$set(image, "stepDeg", 0);
+      image.deg += 90;
+      if (image.deg === 360) image.deg = 0;
+      image.stepDeg += 90;
+      if (image.stepDeg === 360) image.stepDeg = 0;
+      image.styles = {
+        transform: `rotate(${image.deg}deg)`
+      };
+    },
+    async toSaveRotate(image) {
+      if (this.saving) return;
+      if (!image.stepDeg) return;
+      this.saving = true;
+
+      let result = await this.rotatePaper(image).catch(() => {
+        result = false;
+      });
+      this.saving = false;
+      if (!result) return;
+      image.stepDeg = 0;
+      this.$Message.success("保存成功!");
+    },
+    async toSignAbsent(image) {
+      if (!image.paperId) {
+        this.$Message.error("图片数据有误或没有上传,无法标记缺考!");
+        return;
+      }
+      await absentPaper(image.paperId);
+      await absentLocalPaper(image.id, image.missing ? 0 : 1);
+      image.missing = !image.missing;
+    },
+    toReview(index) {
+      this.$emit("on-review", index);
+    },
+    async rotatePaper(image) {
+      console.log(image);
+      const imagePath = await rotateImage(
+        image.sliceImgPath,
+        image.stepDeg
+      ).catch(() => {
+        this.$Message.error("图片旋转失败,请重新尝试!");
+      });
+
+      if (!imagePath) return;
+
+      let uploadSliceRes = true;
+      await toUploadImg(image, "slice").catch(() => {
+        uploadSliceRes = false;
+        this.$Message.error("保存图片失败!");
+      });
+
+      return uploadSliceRes;
+    }
+  }
+};
+</script>

+ 237 - 0
src/modules/manage/components/SimpleImagePreview.vue

@@ -0,0 +1,237 @@
+<template>
+  <Modal
+    :class="prefixCls"
+    v-model="modalIsShow"
+    title="图片预览"
+    fullscreen
+    footer-hide
+    @on-visible-change="visibleChange"
+  >
+    <div slot="header"></div>
+    <div :class="[`${prefixCls}-close`]" @click="cancel">
+      <i class="el-icon-circle-close"></i>
+      <Icon type="ios-close" />
+    </div>
+
+    <div :class="[`${prefixCls}-body`]" ref="ReviewBody" @click="cancel">
+      <div
+        :class="[`${prefixCls}-guide`, `${prefixCls}-guide-prev`]"
+        @click.stop="showPrev"
+      >
+        <Icon type="ios-arrow-back" />
+      </div>
+      <div
+        :class="[`${prefixCls}-guide`, `${prefixCls}-guide-next`]"
+        @click.stop="showNext"
+      >
+        <Icon type="ios-arrow-forward" />
+      </div>
+      <div
+        :class="[
+          `${prefixCls}-imgs`,
+          { [`${prefixCls}-imgs-nosition`]: nosition }
+        ]"
+        :style="styles"
+        v-show="!loading && curImage.imgSrc"
+        v-if="modalIsShow"
+      >
+        <img
+          :src="curImage.imgSrc"
+          :alt="curImage.name"
+          ref="PreviewImgDetail"
+        />
+      </div>
+      <div :class="[`${prefixCls}-none`]" v-if="!curImage.imgSrc">
+        <Icon type="md-image" />
+        <p>暂无数据</p>
+      </div>
+    </div>
+
+    <div :class="[`${prefixCls}-footer`]">
+      <ul>
+        <li title="旋转" @click.stop="toRotate">
+          <Icon type="ios-refresh-circle" />
+        </li>
+      </ul>
+    </div>
+
+    <div :class="[`${prefixCls}-loading`]" v-show="loading">
+      <Icon class="ivu-load-loop" type="ios-loading" />
+    </div>
+  </Modal>
+</template>
+
+<script>
+const prefixCls = "cc-image-preview";
+
+export default {
+  name: "simple-image-preview",
+  props: {
+    curImage: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  data() {
+    return {
+      prefixCls,
+      modalIsShow: false,
+      styles: { width: "", height: "", top: "", left: "", transform: "" },
+      initWidth: 500,
+      transform: {
+        scale: 1,
+        rotate: 0
+      },
+      loading: false,
+      loadingSetT: null,
+      nosition: false
+    };
+  },
+  // watch: {
+  //   "curImage.imgSrc": {
+  //     handler(val) {
+  //       if (val) this.loading = true;
+  //     }
+  //   }
+  // },
+  methods: {
+    visibleChange(visible) {
+      if (!visible) return;
+      // this.loading = true;
+      this.$nextTick(() => {
+        this.registfileLoad();
+      });
+    },
+    registfileLoad() {
+      const imgDom = this.$refs.PreviewImgDetail;
+      imgDom.onload = () => {
+        this.rezizeImage(imgDom);
+      };
+    },
+    rezizeImage(imgDom) {
+      const { naturalWidth, naturalHeight } = imgDom;
+      const imageSize = this.getImageSizePos({
+        win: {
+          width: this.$refs.ReviewBody.clientWidth,
+          height: this.$refs.ReviewBody.clientHeight
+        },
+        img: {
+          width: naturalWidth,
+          height: naturalHeight
+        },
+        rotate: 0
+      });
+
+      this.styles = Object.assign(this.styles, {
+        width: imageSize.width + "px",
+        height: imageSize.height + "px",
+        top: imageSize.top + "px",
+        left: imageSize.left + "px",
+        transform: ""
+      });
+      this.transform = {
+        scale: 1,
+        rotate: 0
+      };
+      // this.loading = false;
+      console.log(11);
+      setTimeout(() => {
+        this.nosition = false;
+      }, 100);
+    },
+    getImageSizePos({ win, img, rotate }) {
+      const imageSize = {
+        width: 0,
+        height: 0,
+        top: 0,
+        left: 0
+      };
+      const isHorizontal = !!(rotate % 180);
+
+      const rateWin = isHorizontal
+        ? win.height / win.width
+        : win.width / win.height;
+      const hwin = isHorizontal
+        ? {
+            width: win.height,
+            height: win.width
+          }
+        : win;
+
+      const rateImg = img.width / img.height;
+
+      if (rateImg <= rateWin) {
+        imageSize.height = Math.min(hwin.height, img.height);
+        imageSize.width = Math.floor(
+          (imageSize.height * img.width) / img.height
+        );
+      } else {
+        imageSize.width = Math.min(hwin.width, img.width);
+        imageSize.height = Math.floor(
+          (imageSize.width * img.height) / img.width
+        );
+      }
+      imageSize.left = (win.width - imageSize.width) / 2;
+      imageSize.top = (win.height - imageSize.height) / 2;
+      return imageSize;
+    },
+    cancel() {
+      this.modalIsShow = false;
+      this.$emit("on-close");
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    showPrev() {
+      this.$emit("on-prev");
+      // this.initData();
+    },
+    showNext() {
+      this.$emit("on-next");
+      // this.initData();
+    },
+    // dome-move
+    setStyleTransform() {
+      const { scale, rotate } = this.transform;
+      this.styles.transform = `scale(${scale}, ${scale}) rotate(${rotate}deg)`;
+    },
+    toRotate() {
+      this.transform.rotate = this.transform.rotate + 90;
+      this.setStyleTransform();
+      // 调整图片尺寸
+      const { naturalWidth, naturalHeight } = this.$refs.PreviewImgDetail;
+      const imageSize = this.getImageSizePos({
+        win: {
+          width: this.$refs.ReviewBody.clientWidth,
+          height: this.$refs.ReviewBody.clientHeight
+        },
+        img: {
+          width: naturalWidth,
+          height: naturalHeight
+        },
+        rotate: this.transform.rotate
+      });
+
+      this.styles = Object.assign(this.styles, {
+        width: imageSize.width + "px",
+        height: imageSize.height + "px",
+        top: imageSize.top + "px",
+        left: imageSize.left + "px"
+      });
+      // 360度无缝切换到0度
+      if (this.transform.rotate >= 360) {
+        setTimeout(() => {
+          this.nosition = true;
+          this.transform.rotate = 0;
+          this.setStyleTransform();
+          setTimeout(() => {
+            this.nosition = false;
+          }, 100);
+        }, 200);
+      }
+    }
+  }
+};
+</script>

+ 9 - 0
src/modules/manage/router.js

@@ -0,0 +1,9 @@
+import PaperManage from "./views/PaperManage.vue";
+
+export default [
+  {
+    path: "/paper-manage",
+    name: "PaperManage",
+    component: PaperManage
+  }
+];

+ 252 - 0
src/modules/manage/views/PaperManage.vue

@@ -0,0 +1,252 @@
+<template>
+  <div class="paper-manage">
+    <div class="part-box-filter">
+      <Form ref="FilterForm" label-position="left" inline>
+        <FormItem>
+          <Select
+            v-model="filter.subjectId"
+            @on-change="subjectChange"
+            placeholder="科目"
+            style="width: 100px"
+          >
+            <Option
+              v-for="item in subjects"
+              :key="item.id"
+              :value="item.id"
+              :label="item.name"
+            ></Option>
+          </Select>
+        </FormItem>
+        <FormItem>
+          <Select
+            v-model="filter.areaCode"
+            placeholder="选择考区"
+            clearable
+            style="width: 180px"
+          >
+            <Option
+              v-for="(area, index) in areas"
+              :key="index"
+              :value="area"
+              :label="area"
+            ></Option>
+          </Select>
+        </FormItem>
+        <FormItem>
+          <Select
+            v-model="paperType"
+            @on-change="typeChange"
+            placeholder="类型"
+            style="width: 100px"
+          >
+            <Option
+              v-for="(val, key) in CAFA_EXCEPTION_TYPE"
+              :key="key"
+              :value="key"
+              :label="val"
+            ></Option>
+          </Select>
+        </FormItem>
+        <FormItem>
+          <Select
+            v-model="filter.sortBy"
+            placeholder="排序方式"
+            clearable
+            style="width: 120px"
+          >
+            <Option
+              v-for="(val, key) in SORT_RULE_TYPE"
+              :key="key"
+              :value="key"
+              :label="val"
+            ></Option>
+          </Select>
+        </FormItem>
+        <FormItem>
+          <Button
+            size="default"
+            class="btn-form-search"
+            type="primary"
+            @click="toPage(1)"
+            >查询</Button
+          >
+        </FormItem>
+      </Form>
+    </div>
+
+    <div class="paper-list">
+      <image-action-list
+        :data="papers"
+        :actions="['rotate', 'absent']"
+        @on-review="toReview"
+        ref="ImageActionList"
+      ></image-action-list>
+    </div>
+
+    <div class="part-page" v-if="total > size">
+      <Page
+        :current="current"
+        :total="total"
+        :page-size="size"
+        show-total
+        show-elevator
+        @on-change="toPage"
+      ></Page>
+    </div>
+
+    <!-- image-preview -->
+    <simple-image-preview
+      :cur-image="curPaper"
+      @on-prev="toPrevPaper"
+      @on-next="toNextPaper"
+      ref="SimpleImagePreview"
+    ></simple-image-preview>
+  </div>
+</template>
+
+<script>
+import { paperPageList, areaList } from "../api";
+import { SORT_RULE_TYPE, CAFA_EXCEPTION_TYPE } from "@/constants/enumerate";
+import ImageActionList from "../components/ImageActionList";
+import SimpleImagePreview from "../components/SimpleImagePreview";
+
+export default {
+  name: "paper-manage",
+  components: { ImageActionList, SimpleImagePreview },
+  data() {
+    return {
+      filter: {
+        areaCode: "",
+        subjectId: "",
+        sortBy: "",
+        isManual: null,
+        missing: null
+      },
+      SORT_RULE_TYPE,
+      CAFA_EXCEPTION_TYPE: {},
+      paperType: "2",
+      current: 1,
+      size: 5,
+      total: 0,
+      totalPage: 0,
+      papers: [],
+      subjects: [],
+      areas: [],
+      curPaper: {},
+      curPaperIndex: 0
+    };
+  },
+  mounted() {
+    this.CAFA_EXCEPTION_TYPE = { ...CAFA_EXCEPTION_TYPE, 2: "全部" };
+    this.initData();
+  },
+  methods: {
+    initData() {
+      const user = this.$ls.get("user", {});
+      if (user && user.subjects) {
+        this.subjects = user.subjects;
+      }
+    },
+    async getList() {
+      const datas = {
+        ...this.filter,
+        pageNumber: this.current,
+        pageSize: this.size
+      };
+      console.log(datas);
+      const data = await paperPageList(datas);
+      console.log(data);
+      this.papers = data.datas.map(paper => {
+        const time = Date.now();
+        return {
+          ...paper,
+          title: paper.examNumber,
+          imgSrc: paper.sliceImgPath + "?t=" + time,
+          thumbSrc: paper.sliceImgPath + "?t=" + time,
+          styles: {},
+          deg: 0
+        };
+      });
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    subjectChange() {
+      this.filter.areaCode = "";
+      this.areas = [];
+      if (!this.filter.subjectId) return;
+      this.getAreaList();
+    },
+    async getAreaList() {
+      const data = await areaList(this.filter.subjectId);
+      this.areas = data;
+      if (this.areas.length === 1) {
+        this.filter.areaCode = this.areas[0];
+      }
+    },
+    typeChange() {
+      if (this.paperType === "1") {
+        this.filter.isManual = 1;
+        this.filter.missing = null;
+      } else if (this.paperType === "0") {
+        this.filter.isManual = null;
+        this.filter.missing = 1;
+      } else {
+        this.filter.isManual = null;
+        this.filter.missing = null;
+      }
+    },
+    // paper view
+    toReview(index) {
+      this.selectPaper(index);
+      this.$refs.SimpleImagePreview.open();
+    },
+    selectPaper(index) {
+      let nindex = index;
+      if (!this.papers.length) {
+        nindex = 0;
+      } else if (index > this.papers.length - 1) {
+        nindex = this.papers.length - 1;
+      } else if (index < 0) {
+        nindex = 0;
+      }
+      this.curPaperIndex = nindex;
+      this.curPaper = this.papers[nindex] ? { ...this.papers[nindex] } : {};
+    },
+    async toPrevPaper() {
+      if (this.curPaperIndex === 0) {
+        if (this.current > 1) {
+          this.current--;
+          this.curPaperIndex = this.size - 1;
+          await this.getList();
+        } else {
+          this.$Message.warning("当前已经是第一条数据了");
+          return;
+        }
+      } else {
+        this.curPaperIndex--;
+      }
+
+      this.selectPaper(this.curPaperIndex);
+    },
+    async toNextPaper() {
+      if (this.curPaperIndex === this.papers.length - 1) {
+        if (this.current === this.totalPage) {
+          this.$Message.warning("当前已经是最后一条数据了");
+          return;
+        } else {
+          this.current++;
+          this.curPaperIndex = 0;
+          await this.getList();
+        }
+      } else {
+        this.curPaperIndex++;
+      }
+
+      this.selectPaper(this.curPaperIndex);
+    }
+  }
+};
+</script>

+ 118 - 1
src/plugins/db.js

@@ -90,6 +90,7 @@ function getUploadCount(limitTime) {
     });
   });
 }
+
 function getScanCount(limitTime, subjectId) {
   const sql = `SELECT COUNT(DISTINCT examNumber) AS count FROM scan WHERE strftime('%s',createdTime, 'utc') >= '${limitTime}' AND subjectId = '${subjectId}'`;
 
@@ -201,6 +202,117 @@ function serializeWhere(params) {
   };
 }
 
+function getAreas(subjectId) {
+  const sql = `SELECT DISTINCT siteCode from scan WHERE subjectId = '${subjectId}'`;
+
+  return new Promise((resolve, reject) => {
+    db.all(sql, (err, rows) => {
+      if (err) reject("get list fail!");
+      resolve(rows.map(item => item.siteCode));
+    });
+  });
+}
+
+function getPaperList({
+  subjectId,
+  areaCode,
+  isManual,
+  missing,
+  sortBy,
+  pageNumber,
+  pageSize
+}) {
+  const orderBy = sortBy === "1" ? "id DESC" : "examNumber ASC";
+
+  let options = [];
+  if (subjectId) {
+    options.push(`subjectId = '${subjectId}'`);
+  }
+  if (areaCode) {
+    options.push(`siteCode = '${areaCode}'`);
+  }
+  if (isManual !== null) {
+    options.push(`isManual = ${isManual}`);
+  }
+  if (missing !== null) {
+    options.push(`missing = ${missing}`);
+  }
+  const optionStr = options.length ? "WHERE " + options.join(" and ") : "";
+
+  const condition = `${optionStr} GROUP BY examNumber ORDER BY ${orderBy}`;
+  const countSql = `SELECT count(id) count FROM ( SELECT max(id) id FROM scan ${condition})`;
+
+  console.log(countSql);
+
+  return new Promise((resolve, reject) => {
+    db.all(countSql, (err, rows) => {
+      if (err) reject("count list fail!");
+
+      console.log(rows);
+
+      const total = rows[0].count;
+      const offset = (pageNumber - 1) * pageSize;
+      const sql = `SELECT max(id) pid, * FROM scan ${condition} LIMIT ${pageSize} OFFSET ${offset}`;
+
+      console.log(sql);
+
+      db.all(sql, (err, rows) => {
+        if (err) reject("get list fail!");
+        resolve({
+          datas: rows,
+          pageNumber,
+          pageSize,
+          total
+        });
+      });
+    });
+  });
+}
+
+function absentLocalPaper(id, missing) {
+  const sql = `UPDATE scan SET missing=$missing WHERE id=$id`;
+  const datas = {
+    $missing: missing,
+    $id: id
+  };
+  return new Promise((resolve, reject) => {
+    db.run(sql, datas, err => {
+      if (err) reject("update absent info fail!");
+
+      resolve();
+    });
+  });
+}
+
+function uploadLocalPaperId(id, paperId) {
+  const sql = `UPDATE scan SET paperId=$paperId WHERE id=$id`;
+  const datas = {
+    $paperId: paperId,
+    $id: id
+  };
+  return new Promise((resolve, reject) => {
+    db.run(sql, datas, err => {
+      if (err) reject("update paperId info fail!");
+
+      resolve();
+    });
+  });
+}
+
+function deleteScanById(id) {
+  const sql = `DELETE FROM scan WHERE id=$id`;
+  const datas = {
+    $id: id
+  };
+  return new Promise((resolve, reject) => {
+    db.run(sql, datas, err => {
+      if (err) reject("delete scan fail!");
+
+      resolve(true);
+    });
+  });
+}
+
 export default {
   init,
   saveUploadInfo,
@@ -213,5 +325,10 @@ export default {
   getDict,
   setDict,
   getAllDict,
-  initDict
+  initDict,
+  getAreas,
+  getPaperList,
+  absentLocalPaper,
+  uploadLocalPaperId,
+  deleteScanById
 };

+ 16 - 1
src/plugins/imageOcr.js

@@ -132,6 +132,21 @@ function saveSliceImage(imgPath, paperInfo, collectConfig) {
   });
 }
 
+function rotateImage(imgPath, imageRotate) {
+  let imgObj = gm(imgPath);
+
+  if (imageRotate) imgObj.rotate("#FFFFFF", imageRotate);
+
+  return new Promise((resolve, reject) => {
+    imgObj.write(imgPath, function(err) {
+      if (err) {
+        reject(err);
+      }
+      resolve(imgPath);
+    });
+  });
+}
+
 function getOutputImagePath(paperInfo, type) {
   const outputDir = path.join(
     getOutputDir(type),
@@ -168,4 +183,4 @@ function getEarliestFile(dir) {
   };
 }
 
-export { decodeImageCode, saveOutputImage, getEarliestFile };
+export { decodeImageCode, saveOutputImage, getEarliestFile, rotateImage };

+ 20 - 3
src/plugins/imageUpload.js

@@ -7,6 +7,7 @@ import {
   uploadStudent,
   saveCollectLog
 } from "../modules/client/api";
+import db from "@/plugins/db";
 
 /**
  * 文件上传
@@ -71,12 +72,18 @@ function toSaveCollectLog(options) {
 // }
 
 class UploadTask {
-  constructor({ taskList, uploadSuccessCallback, uploadTaskOverCallback }) {
+  constructor({
+    taskList,
+    uploadSuccessCallback,
+    uploadTaskOverCallback,
+    uploadErrorCallBack
+  }) {
     this.taskList = taskList;
     this.setT = "";
     this.taskRunning = false;
     this.uploadSuccessCallback = uploadSuccessCallback;
     this.uploadTaskOverCallback = uploadTaskOverCallback;
+    this.uploadErrorCallBack = uploadErrorCallBack;
 
     this.startUploadTask();
   }
@@ -112,14 +119,22 @@ class UploadTask {
       uploadSliceRes = false;
     });
 
+    let updateStdRes, saveLogRes;
+
     if (uploadFormalRes && uploadSliceRes) {
-      const updateStdRes = await toUploadStudent(curTask).catch(() => {});
-      const saveLogRes = await toSaveCollectLog(curTask).catch(() => {});
+      updateStdRes = await toUploadStudent(curTask).catch(() => {});
+      saveLogRes = await toSaveCollectLog(curTask).catch(() => {});
 
       if (updateStdRes && saveLogRes) {
+        // 更新paperId
+        await db.uploadLocalPaperId(curTask.id, updateStdRes.paperId);
         this.uploadSuccessCallback(curTask);
       }
     }
+    // 待定:只要有一个接口出错,则提示上传失败!
+    if (!uploadFormalRes || !uploadSliceRes || !updateStdRes || !saveLogRes) {
+      this.uploadErrorCallBack(curTask);
+    }
 
     this.setT = setTimeout(() => {
       this.runUploadTask();
@@ -138,3 +153,5 @@ class UploadTask {
 }
 
 export default UploadTask;
+
+export { toUploadImg };

+ 2 - 1
src/router.js

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

+ 2 - 1
src/views/Home.vue

@@ -50,7 +50,8 @@ export default {
       isDev: process.env.NODE_ENV === "development",
       // 路由历史只前进,不后退
       backRouters: {
-        Subject: "Login",
+        ActionType: "Login",
+        Subject: "ActionType",
         Camera: "Subject",
         ScanArea: "Camera",
         ScanWait: "ScanArea",

+ 1 - 1
src/views/Login.vue

@@ -127,7 +127,7 @@ export default {
         paperStage: data.paramSetting.paperStage
       });
       this.$router.push({
-        name: "Subject"
+        name: "ActionType"
       });
     }
   }