zhangjie 5 年之前
父節點
當前提交
a26dafe065
共有 36 個文件被更改,包括 2400 次插入251 次删除
  1. 1 0
      package.json
  2. 二進制
      public/templates/考生导入模板.xlsx
  3. 93 3
      src/api.js
  4. 147 0
      src/assets/styles/base.less
  5. 6 0
      src/assets/styles/common-component.less
  6. 3 137
      src/assets/styles/home.less
  7. 2 0
      src/assets/styles/index.less
  8. 17 1
      src/assets/styles/iview-style-choosable.less
  9. 159 0
      src/assets/styles/main.less
  10. 1 1
      src/assets/styles/variables.less
  11. 164 0
      src/components/UploadButton.vue
  12. 1 2
      src/components/ViewHeader.vue
  13. 3 3
      src/components/common/ImportFile/ImportFile.vue
  14. 1 1
      src/config.js
  15. 7 0
      src/constants/enumerate.js
  16. 1 1
      src/main.js
  17. 140 4
      src/modules/client-set/ClientAccountSet.vue
  18. 192 3
      src/modules/client-set/ClientParamSet.vue
  19. 9 14
      src/modules/client-set/ClientSet.vue
  20. 153 0
      src/modules/client-set/InspectionAccountSet.vue
  21. 122 0
      src/modules/client-set/components/ModifyClientUser.vue
  22. 0 3
      src/modules/client-set/index.js
  23. 41 48
      src/modules/login/login.vue
  24. 59 3
      src/modules/main/ClientMonitor.vue
  25. 120 3
      src/modules/main/PaperManage.vue
  26. 290 4
      src/modules/main/StudentManage.vue
  27. 144 2
      src/modules/main/WorkManage.vue
  28. 109 3
      src/modules/main/WorkOverview.vue
  29. 114 0
      src/modules/main/components/ImageActionList.vue
  30. 165 0
      src/modules/main/components/ModifyStudent.vue
  31. 41 0
      src/modules/main/components/UploadStudentPaper.vue
  32. 2 2
      src/plugins/axios.js
  33. 1 3
      src/plugins/utils.js
  34. 17 10
      src/routers/main.js
  35. 64 0
      vue.config.js
  36. 11 0
      yarn.lock

+ 1 - 0
package.json

@@ -28,6 +28,7 @@
     "@vue/cli-service": "~4.3.0",
     "@vue/eslint-config-prettier": "^6.0.0",
     "babel-eslint": "^10.1.0",
+    "compression-webpack-plugin": "^4.0.0",
     "eslint": "^6.7.2",
     "eslint-plugin-prettier": "^3.1.1",
     "eslint-plugin-vue": "^6.2.2",

二進制
public/templates/考生导入模板.xlsx


+ 93 - 3
src/api.js

@@ -1,11 +1,101 @@
-import { $post } from "@/plugins/axios";
+import { $get, $post, $del, $patch, $put } from "@/plugins/axios";
 
+// login
 export const login = datas => {
   return $post("/login", datas);
 };
 export const modifyPwd = datas => {
   return $post("/backend/sysuser/resetPwd", datas);
 };
-export const getSmsCode = phone => {
-  return $post("/backend/sysuser/getValidateCode", { phone });
+// work-manage
+export const workList = () => {
+  return $get("/api/admin/works", {});
+};
+export const createWork = datas => {
+  return $post("/api/admin/works", datas);
+};
+export const activeWork = workId => {
+  return $patch(`/api/admin/works/${workId}`, {});
+};
+export const deleteWork = workId => {
+  return $del(`/api/admin/works/${workId}`, {});
+};
+// work-overview
+export const workOverviewDetail = workId => {
+  return $get(`/api/admin/works/${workId}/overview`, {});
+};
+// paper-manage
+export const paperPageList = datas => {
+  return $get("/api/papers/listByQuestion", datas);
+};
+export const rotatePaper = (imageId, degree) => {
+  return $get(`/api/images/${imageId}/rotate`, { degree });
+};
+export const absentPaper = imageId => {
+  return $post(`/api/score/missing/${imageId}`, {});
+};
+// client-monitor
+export const clientMonitorList = datas => {
+  // return $get("/api/papers/listByQuestion", datas);
+  return Promise.resolve(datas);
+};
+// client-set
+// client-user-set
+export const clientUserPageList = datas => {
+  return $get("/api/admin/users/collect", datas);
+};
+export const uploadClientUser = datas => {
+  if (datas.id) {
+    return $put(`/api/admin/users/collect/${datas.id}`, datas);
+  } else {
+    return $post("/api/admin/users/collect", datas);
+  }
+};
+export const deleteClientUser = userId => {
+  return $del(`/api/admin/users/collect/${userId}`, {});
+};
+// inspection-user-set  TODO:换成真是的账号
+export const inspectionUserPageList = datas => {
+  return $get("/api/admin/users/collect", datas);
+};
+export const uploadInspectionUser = datas => {
+  if (datas.id) {
+    return $put(`/api/admin/users/collect/${datas.id}`, datas);
+  } else {
+    return $post("/api/admin/users/collect", datas);
+  }
+};
+export const deleteInspectionUser = userId => {
+  return $del(`/api/admin/users/collect/${userId}`, {});
+};
+// client-param-set
+// subject-set
+export const subjectList = datas => {
+  // return $get("/api/admin/subjects", datas);
+  return Promise.resolve(datas);
+};
+export const uploadSubject = datas => {
+  if (datas.id) {
+    return $put(`/api/admin/subject/${datas.id}`, datas);
+  } else {
+    return $post("/api/admin/subject", datas);
+  }
+};
+export const deleteSubject = subjectId => {
+  return $del(`/api/admin/subject/${subjectId}`, {});
+};
+
+// student-manage
+export const studentPageList = datas => {
+  return $get("/api/students", datas);
+};
+export const uploadStudent = datas => {
+  if (datas.id) {
+    return $put(`/api/students/${datas.id}`, datas);
+  } else {
+    return $post(`/api/students`, datas);
+  }
+};
+export const deleteStudent = studentId => {
+  return $del(`/api/students/${studentId}`, {});
 };

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

@@ -114,3 +114,150 @@ body {
   font-size: @fontSize;
   color: @fontMain;
 }
+
+/* part */
+.part-box {
+  box-shadow: 0 0 2px 1px #e9e9e9;
+  padding: 20px 30px;
+  margin-bottom: 15px;
+  background-color: #fff;
+
+  .ivu-form-item-label {
+    text-align: right;
+  }
+}
+.part-title {
+  font-size: 16px;
+  margin-bottom: 15px;
+  height: 32px;
+  line-height: 32px;
+}
+.part-title h2 {
+  float: left;
+  font-weight: 600;
+}
+.part-title-infos {
+  float: right;
+  > .ivu-btn:not(:first-child) {
+    margin-left: 5px;
+  }
+}
+.part-page {
+  margin-top: 15px;
+  text-align: right;
+}
+.part-filter {
+  border-bottom: 1px dashed #e0e0e0;
+  margin-bottom: 20px;
+}
+.part-none {
+  padding: 100px;
+  font-size: 20px;
+  color: @gray;
+  text-align: center;
+}
+.part-box-top {
+  margin-bottom: 10px;
+  text-align: right;
+}
+// page
+.page {
+  &-navs {
+    margin-bottom: 10px;
+  }
+}
+/* table */
+.table {
+  width: 100%;
+  border-spacing: 0;
+  border-collapse: collapse;
+  text-align: center;
+  margin-bottom: 30px;
+}
+.table th {
+  padding: 10px;
+  line-height: 20px;
+  letter-spacing: 1px;
+  border: 1px solid @shadowGray;
+}
+.table td {
+  padding: 10px;
+  line-height: 20px;
+  border: 1px solid @shadowGray;
+}
+.table .td-th {
+  font-weight: 600;
+  color: @dark;
+}
+tr.row-active td {
+  color: @mainColor;
+}
+
+/* list */
+.list-lr-right {
+  float: right;
+  width: 300px;
+}
+.list-lr-left {
+  margin-right: 320px;
+}
+
+/* user reset */
+h3.account-title {
+  text-align: center;
+  font-weight: 600;
+}
+.account-form {
+  width: 60%;
+  min-width: 600px;
+  margin: 50px auto;
+}
+.vlcode {
+  height: 36px;
+}
+.vlcode-left {
+  margin-right: 135px;
+}
+.vlcode-right {
+  float: right;
+  width: 120px;
+}
+
+// vue-echarts
+.echarts {
+  width: 100% !important;
+}
+.chart-box {
+  padding: 10px;
+  height: 400px;
+  box-shadow: 0 0 1px #e0e0e0;
+  background: #fff;
+  position: relative;
+}
+.chart-box .chart-none {
+  padding-top: 150px;
+  font-size: 30px;
+  text-align: center;
+}
+.chart-box .chart-title {
+  margin: 0;
+  font-size: 18px;
+  font-weight: 600;
+  color: #333;
+  position: absolute;
+  top: 10px;
+  left: 16px;
+  line-height: 30px;
+  z-index: 99;
+}
+
+// other
+.tips-info {
+  font-size: 14px;
+  height: 25px;
+  line-height: 25px;
+  color: @gray;
+}
+.tips-error {
+  color: @pink;
+}

+ 6 - 0
src/assets/styles/common-component.less

@@ -228,6 +228,12 @@
   }
 }
 
+// upload-button
+.cc-upload-button {
+  display: inline-block;
+  vertical-align: middle;
+}
+
 // rich-editor
 .cc-editor {
   .ql-editor {

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

@@ -33,12 +33,12 @@
   z-index: auto;
   padding: 20px 0;
   line-height: 20px;
-  color: @shadowGray;
+  color: @gray;
   text-align: center;
   font-size: 13px;
 }
 .home-footer a {
-  color: @shadowGray;
+  color: @midDark;
 }
 .home-footer a:hover {
   color: @dark;
@@ -62,7 +62,7 @@
   padding: 10px 0;
   line-height: 30px;
   color: #fff;
-  background: @mainColor;
+  background: @pink;
   font-size: 20px;
   text-align: center;
 }
@@ -115,137 +115,3 @@
     }
   }
 }
-
-/* part */
-.part-box {
-  box-shadow: 0 0 2px 1px #e9e9e9;
-  padding: 20px 30px;
-  margin-bottom: 15px;
-  background-color: #fff;
-
-  .ivu-form-item-label {
-    text-align: right;
-  }
-}
-.part-title {
-  font-size: 16px;
-  margin-bottom: 15px;
-  height: 32px;
-  line-height: 32px;
-}
-.part-title h2 {
-  float: left;
-  font-weight: 600;
-}
-.part-title-infos {
-  float: right;
-  > .ivu-btn:not(:first-child) {
-    margin-left: 5px;
-  }
-}
-.part-page {
-  margin-top: 15px;
-  text-align: right;
-}
-.part-filter {
-  border-bottom: 1px dashed #e0e0e0;
-  margin-bottom: 20px;
-}
-.part-none {
-  padding: 100px;
-  font-size: 20px;
-  color: @gray;
-  text-align: center;
-}
-/* table */
-.table {
-  width: 100%;
-  border-spacing: 0;
-  border-collapse: collapse;
-  text-align: center;
-  margin-bottom: 30px;
-}
-.table th {
-  padding: 10px;
-  line-height: 20px;
-  letter-spacing: 1px;
-  border: 1px solid @shadowGray;
-}
-.table td {
-  padding: 10px;
-  line-height: 20px;
-  border: 1px solid @shadowGray;
-}
-.table .td-th {
-  font-weight: 600;
-  color: @dark;
-}
-
-/* list */
-.list-lr-right {
-  float: right;
-  width: 300px;
-}
-.list-lr-left {
-  margin-right: 320px;
-}
-
-/* user reset */
-h3.account-title {
-  text-align: center;
-  font-weight: 600;
-}
-.account-form {
-  width: 60%;
-  min-width: 600px;
-  margin: 50px auto;
-}
-.vlcode {
-  height: 36px;
-}
-.vlcode-left {
-  margin-right: 135px;
-}
-.vlcode-right {
-  float: right;
-  width: 120px;
-}
-
-// vue-echarts
-.echarts {
-  width: 100% !important;
-}
-.chart-box {
-  padding: 10px;
-  height: 400px;
-  box-shadow: 0 0 1px #e0e0e0;
-  background: #fff;
-  position: relative;
-}
-.chart-box .chart-none {
-  padding-top: 150px;
-  font-size: 30px;
-  text-align: center;
-}
-.chart-box .chart-title {
-  margin: 0;
-  font-size: 18px;
-  font-weight: 600;
-  color: #333;
-  position: absolute;
-  top: 10px;
-  left: 16px;
-  line-height: 30px;
-  z-index: 99;
-}
-
-// other
-.tips-info {
-  font-size: 14px;
-  height: 25px;
-  line-height: 25px;
-  color: @gray;
-}
-.tips-error {
-  color: @pink;
-}

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

@@ -4,3 +4,5 @@
 @import "./login.less";
 @import "./account.less";
 @import "./common-component.less";
+@import "./main.less";
+@import "./iview-style-choosable.less";

+ 17 - 1
src/assets/styles/iview-style-choosable.css → src/assets/styles/iview-style-choosable.less

@@ -1,7 +1,18 @@
-/* ivu */
+/* form */
+.ivu-form-inline {
+  .ivu-form-item {
+    margin-bottom: 10px;
+  }
+  .ivu-form-item-label,
+  .ivu-form-item-content {
+    display: inline-block;
+    vertical-align: middle;
+  }
+}
 .ivu-form-item-label {
   font-size: 14px !important;
 }
+// table
 .ivu-table-wrapper {
   border: 0;
 }
@@ -47,3 +58,8 @@
 .ivu-tree-children > li {
   font-size: 14px;
 }
+
+/* button */
+.ivu-btn + .ivu-btn {
+  margin-left: 10px;
+}

+ 159 - 0
src/assets/styles/main.less

@@ -0,0 +1,159 @@
+// work-manage
+
+// work-overview
+.overview {
+  &-head {
+    padding: 20px;
+    text-align: center;
+    background-color: #fff;
+    margin: -20px -20px 0;
+  }
+  &-title {
+    font-size: 30px;
+    margin: 20px 0 30px;
+  }
+  &-actions {
+    margin-top: 20px;
+  }
+  &-body {
+    margin-top: 20px;
+    font-size: 0;
+  }
+
+  &-list {
+    font-size: 0;
+    > li {
+      display: inline-block;
+      vertical-align: top;
+      font-size: 14px;
+      height: 80px;
+      width: 20%;
+      p {
+        &:last-child {
+          font-size: 24px;
+          margin-top: 5px;
+        }
+      }
+    }
+  }
+  &-subject {
+    font-size: 14px;
+    display: inline-block;
+    vertical-align: top;
+    width: 33.33%;
+    padding: 0 10px;
+  }
+
+  .subject {
+    &-content {
+      text-align: center;
+      background-color: #fff;
+      .overview-list li {
+        width: 33.33%;
+      }
+    }
+    &-name,
+    &-infos {
+      background-color: @mainColor;
+      color: #fff;
+    }
+    &-name {
+      font-size: 30px;
+      padding: 20px;
+    }
+
+    &-actions {
+      padding: 20px;
+      > p:first-child {
+        color: @gray;
+        font-size: 16px;
+      }
+      > p:nth-of-type(2) {
+        font-size: 28px;
+        padding: 10px 0 40px;
+      }
+    }
+  }
+}
+
+// image-action-list
+// image-view
+.image-view {
+  display: inline-block;
+  vertical-align: top;
+  font-size: 14px;
+  padding: 10px;
+  width: 300px;
+  text-align: center;
+
+  &-title {
+    font-size: 16px;
+    margin-bottom: 5px;
+  }
+  &-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;
+      border: 5px solid #fff;
+      box-shadow: 0 0 1px #000;
+      cursor: pointer;
+    }
+  }
+  &-actions {
+    margin-top: 10px;
+  }
+}
+.image-action-list {
+  padding: 15px 0;
+  font-size: 0;
+  .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%;
+    }
+  }
+}
+
+// client-param-set
+.client-param {
+  &-set {
+    .part-box {
+      height: 100%;
+    }
+  }
+  &-title {
+    font-size: 24px;
+    text-align: center;
+    margin-bottom: 40px;
+  }
+}

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

@@ -1,5 +1,5 @@
 @fontMain: #2c3e50;
-@background: #ededed;
+@background: #f0f0f0;
 
 @mainColor: #2d8cf0;
 @subColor: #5cadff;

+ 164 - 0
src/components/UploadButton.vue

@@ -0,0 +1,164 @@
+<template>
+  <div :class="prefixCls">
+    <Upload
+      :action="uploadUrl"
+      :headers="headers"
+      :max-size="maxSize"
+      :format="format"
+      :accept="accept"
+      :data="uploadDataDict"
+      :before-upload="handleBeforeUpload"
+      :on-format-error="handleFormatError"
+      :on-exceeded-size="handleExceededSize"
+      :on-error="handleError"
+      :on-success="handleSuccess"
+      :on-remove="handleRemove"
+      :show-upload-list="false"
+      ref="UploadComp"
+    >
+      <Button :type="btnType" :icon="btnIcon" :loading="loading">{{
+        btnContent
+      }}</Button>
+    </Upload>
+    <p
+      :class="[
+        {
+          'cc-tips-success': res.success,
+          'cc-tips-error': !res.success
+        }
+      ]"
+      v-if="res.msg"
+    >
+      {{ res.msg }}
+    </p>
+  </div>
+</template>
+
+<script>
+const prefixCls = "cc-upload-button";
+
+export default {
+  name: "upload-button",
+  props: {
+    btnIcon: {
+      type: String
+    },
+    btnType: {
+      type: String,
+      default: "default"
+    },
+    btnContent: {
+      type: String
+    },
+    format: {
+      type: Array,
+      default() {
+        return ["jpg", "png"];
+      }
+    },
+    uploadUrl: {
+      type: String,
+      required: true
+    },
+    uploadData: {
+      type: Object,
+      default() {
+        return {};
+      }
+    },
+    headers: {
+      type: Object,
+      default() {
+        return {};
+      }
+    },
+    maxSize: {
+      type: Number,
+      default: 10 * 1024
+    },
+    addFilenameParam: {
+      type: String,
+      default: "fileName"
+    }
+  },
+  data() {
+    return {
+      prefixCls,
+      modalIsShow: false,
+      loading: false,
+      uploadDataDict: {},
+      res: {
+        success: true,
+        msg: ""
+      }
+    };
+  },
+  computed: {
+    accept() {
+      return this.format.map(el => `.${el}`).join();
+    }
+  },
+  methods: {
+    visibleChange(visible) {
+      if (!visible) {
+        this.res = {
+          success: true,
+          msg: ""
+        };
+        this.$refs.UploadComp.clearFiles();
+      }
+    },
+    handleBeforeUpload(file) {
+      this.uploadDataDict = { ...this.uploadData };
+      if (this.addFilenameParam)
+        this.uploadDataDict[this.addFilenameParam] = file.name;
+
+      this.loading = true;
+      this.$refs.UploadComp.clearFiles();
+    },
+    handleError(error) {
+      this.loading = false;
+      this.res = {
+        success: false,
+        msg: error.message
+      };
+    },
+    handleSuccess(response) {
+      this.loading = false;
+      if (response) {
+        this.res = {
+          success: true,
+          msg: "导入成功!"
+        };
+        this.$emit("upload-success", response);
+      } else {
+        this.res = {
+          success: false,
+          msg: response.message || "上传失败"
+        };
+      }
+    },
+    handleFormatError() {
+      this.res = {
+        success: false,
+        msg: "只支持文件格式为" + this.format.join("/")
+      };
+    },
+    handleExceededSize() {
+      this.res = {
+        success: false,
+        msg: "文件大小不能超过" + Math.floor(this.maxSize / 1024) + "M"
+      };
+    },
+    handleRemove() {
+      this.resData = "";
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    }
+  }
+};
+</script>

+ 1 - 2
src/components/ViewHeader.vue

@@ -2,8 +2,7 @@
   <div class="view-header">
     <div class="head-logo">
       <slot name="logo">
-        <!-- big logo 160*40 -->
-        <h1>LOGO</h1>
+        <h1>美术阅卷管理端</h1>
       </slot>
     </div>
     <div class="head-user">

+ 3 - 3
src/components/common/ImportFile/ImportFile.vue

@@ -136,16 +136,16 @@ export default {
       };
     },
     handleSuccess(response) {
-      if (response.code === 0) {
+      if (response) {
         this.res = {
           success: true,
           msg: "导入成功!"
         };
-        this.$emit("upload-success");
+        this.$emit("upload-success", response);
       } else {
         this.res = {
           success: false,
-          msg: response.message
+          msg: response.message || "上传失败"
         };
       }
     },

+ 1 - 1
src/config.js

@@ -1,5 +1,5 @@
 export default {
-  domain: process.env.VUE_APP_DOMAIN || window.location.origin,
+  domain: process.env.VUE_APP_DOMAIN || window.location.origin + "/apis",
   timeout: process.env.VUE_APP_TIMEOUT * 1,
   pageSize: process.env.VUE_APP_PAGE_SIZE * 1,
   authTimeout: process.env.VUE_APP_AUTH_TIMEOUT * 1

+ 7 - 0
src/constants/enumerate.js

@@ -31,3 +31,10 @@ export const GENDER_TYPE = {
   MALE: "男",
   FEMALE: "女"
 };
+
+// subject step
+export const SUBJECT_STAGE = {
+  INIT: "采集阶段",
+  LEVEL: "分档阶段",
+  SCORE: "打分阶段"
+};

+ 1 - 1
src/main.js

@@ -15,7 +15,7 @@ import "./assets/styles/index.less";
 // regist v-chart
 import "./plugins/VueCharts";
 
-Vue.use(ViewUI, { size: "large" });
+Vue.use(ViewUI);
 ViewUI.Message.config({
   duration: 3
 });

+ 140 - 4
src/modules/client-set/ClientAccountSet.vue

@@ -1,15 +1,151 @@
 <template>
-  <div class="client-account-set">
-    client-account-set
+  <div class="client-account-set ">
+    <div class="part-box-top">
+      <Button type="success" icon="md-cloud-upload" @click="toEdit({})"
+        >新增</Button
+      >
+    </div>
+    <div class="part-box">
+      <Table
+        ref="TableList"
+        :columns="columes"
+        :data="users"
+        disabled-hover
+        border
+      ></Table>
+
+      <div class="part-page">
+        <Page
+          :current="current"
+          :total="total"
+          :page-size="size"
+          show-total
+          show-elevator
+          @on-change="toPage"
+        ></Page>
+      </div>
+    </div>
+
+    <!-- modify-client-user -->
+    <modify-client-user
+      :instance="curUser"
+      @modified="getList"
+      ref="ModifyClientUser"
+    ></modify-client-user>
   </div>
 </template>
 
 <script>
+import { clientUserPageList, deleteClientUser, uploadClientUser } from "@/api";
+import ModifyClientUser from "./components/ModifyClientUser";
+
 export default {
   name: "client-account-set",
+  components: { ModifyClientUser },
   data() {
-    return {};
+    return {
+      workId: this.$route.params.workId,
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      users: [],
+      curUser: {},
+      columes: [
+        {
+          type: "index",
+          title: "序号",
+          width: 100,
+          align: "center",
+          indexMethod: row => {
+            return (this.current - 1) * this.size + row._index + 1;
+          }
+        },
+        {
+          title: "账号",
+          key: "loginName"
+        },
+        {
+          title: "密码",
+          key: "password"
+        },
+        {
+          title: "操作",
+          key: "action",
+          width: 240,
+          align: "center",
+          render: (h, param) => {
+            let actions = [
+              {
+                name: param.row.enabled ? "禁用" : "启用",
+                type: param.row.enabled ? "error" : "primary",
+                action: () => {
+                  this.toAble(param.row);
+                }
+              },
+              {
+                name: "编辑",
+                action: () => {
+                  this.toEdit(param.row);
+                }
+              },
+              {
+                name: "删除",
+                type: "error",
+                action: () => {
+                  this.toDelete(param.row);
+                }
+              }
+            ];
+            return h("div", this.$tableAction(h, actions));
+          }
+        }
+      ]
+    };
+  },
+  mounted() {
+    this.getList();
   },
-  methods: {}
+  methods: {
+    async getList() {
+      const datas = {
+        workId: this.workId,
+        page: this.current - 1,
+        size: this.size
+      };
+      const data = await clientUserPageList(datas);
+      this.users = data;
+      this.total = 10;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    toEdit(row) {
+      this.curUser = row;
+      this.$refs.ModifyClientUser.open();
+    },
+    async toAble(row) {
+      await uploadClientUser({
+        id: row.id,
+        loginName: row.loginName,
+        enabled: !row.enabled
+      });
+      row.enabled = !row.enabled;
+    },
+    toDelete(row) {
+      this.$Modal.confirm({
+        title: "删除警告",
+        content: "确定要删除当前账号吗?",
+        onOk: () => {
+          this.toDel(row.id);
+        }
+      });
+    },
+    async toDel(id) {
+      await deleteClientUser(id);
+      this.$Message.success("删除成功!");
+      this.deletePageLastItem();
+    }
+  }
 };
 </script>

+ 192 - 3
src/modules/client-set/ClientParamSet.vue

@@ -1,15 +1,204 @@
 <template>
   <div class="client-param-set">
-    client-param-set
+    <Row :gutter="20" type="flex">
+      <Col span="12">
+        <div class="part-box">
+          <h2 class="client-param-title">科目名称设置</h2>
+          <Button type="success" icon="md-add" @click="toAdd">新增</Button>
+          <Table
+            ref="TableList"
+            :columns="columes"
+            :data="subjects"
+            border
+          ></Table></div
+      ></Col>
+      <Col span="12">
+        <div class=" part-box">
+          <h2 class="client-param-title">其他设置</h2>
+          <Form ref="modalFormComp" :model="modalForm" :label-width="100">
+            <FormItem prop="name">
+              <Checkbox v-model="modalForm.isScanPackage"
+                >是否整包扫描</Checkbox
+              >
+            </FormItem>
+            <FormItem prop="name">
+              <Checkbox v-model="modalForm.isImageEncryption"
+                >图片是否加密</Checkbox
+              >
+            </FormItem>
+            <FormItem prop="imageNameRule" label="图片命名规则">
+              <Select
+                v-model="modalForm.imageNameRule"
+                placeholder="请选择图片命名规则"
+              >
+                <Option value=""></Option>
+              </Select>
+            </FormItem>
+            <FormItem prop="paperGrading" label="试卷档位">
+              <Select
+                v-model="modalForm.imageNameRule"
+                placeholder="请选择试卷档位"
+              >
+                <Option value=""></Option>
+              </Select>
+            </FormItem>
+            <FormItem>
+              <Button type="primary" @click="toSubmit">保存</Button>
+            </FormItem>
+          </Form>
+        </div>
+      </Col>
+    </Row>
   </div>
 </template>
 
 <script>
+import { subjectList, uploadSubject, deleteSubject } from "@/api";
+
+const initSubject = {
+  id: "",
+  subjectName: "",
+  subjectCode: "",
+  password: ""
+};
+
 export default {
   name: "client-param-set",
   data() {
-    return {};
+    return {
+      subjects: [],
+      columes: [
+        {
+          type: "index",
+          title: "序号",
+          width: 60,
+          align: "center"
+        },
+        {
+          title: "科目名称",
+          key: "subjectName",
+          render: (h, param) => {
+            return h("Input", {
+              props: {
+                value: param.row.subjectName,
+                clearable: true
+              },
+              style: {
+                width: "120px"
+              },
+              on: {
+                input: function(value) {
+                  param.row.subjectName = value;
+                }
+              }
+            });
+          }
+        },
+        {
+          title: "科目代码",
+          key: "subjectCode",
+          render: (h, param) => {
+            return h("Input", {
+              props: {
+                value: param.row.subjectCode,
+                clearable: true
+              },
+              style: {
+                width: "120px"
+              },
+              on: {
+                input: function(value) {
+                  param.row.subjectCode = value;
+                }
+              }
+            });
+          }
+        },
+        {
+          title: "操作",
+          key: "action",
+          width: 120,
+          align: "center",
+          render: (h, param) => {
+            let actions = [
+              {
+                name: "保存",
+                action: () => {
+                  this.toSave(param.row);
+                }
+              },
+              {
+                name: "删除",
+                type: "error",
+                action: () => {
+                  this.toDelete(param.row);
+                }
+              }
+            ];
+            return h("div", this.$tableAction(h, actions));
+          }
+        }
+      ],
+      // other param
+      modalForm: {
+        isScanPackage: false,
+        isImageEncryption: false,
+        imageNameRule: "",
+        paperGrading: ""
+      }
+    };
   },
-  methods: {}
+  mounted() {
+    this.getList();
+  },
+  methods: {
+    async getList() {
+      const datas = {
+        workId: this.workId
+      };
+      const data = await subjectList(datas);
+
+      console.log(data);
+
+      if (!this.subjects.length) this.toAdd();
+    },
+    toAdd() {
+      this.subjects.push({ ...initSubject });
+    },
+    async toSave(row) {
+      if (!row.subjectName || row.subjectName.length > 20) {
+        this.$Message.error("必须输入科目名称,且科目名称长度不能超过20个字符");
+        return;
+      }
+      if (!row.subjectCode.match(new RegExp(`^[a-zA-Z0-9_]{3,20}$`))) {
+        this.$Message.error(
+          "当前科目代码只能由数字、字母和下划线组成,长度2-20个字符"
+        );
+        return;
+      }
+      await uploadSubject(row);
+      this.$Message.success("保存成功!");
+      this.getList();
+    },
+    toDelete(row) {
+      if (!row.id) {
+        // TODO:
+        return;
+      }
+      this.$Modal.confirm({
+        title: "删除警告",
+        content: "确定要删除当前科目吗?",
+        onOk: () => {
+          this.toDel(row.id);
+        }
+      });
+    },
+    async toDel(id) {
+      await deleteSubject(id);
+      this.$Message.success("删除成功!");
+      this.getList();
+    },
+    toSubmit() {}
+  }
 };
 </script>

+ 9 - 14
src/modules/client-set/ClientSet.vue

@@ -1,21 +1,16 @@
 <template>
   <div class="client-set">
-    <div class="home-navs" style="background: #fff;">
-      <ul>
-        <li
-          :class="{ act: curNav.name === nav.name }"
-          v-for="(nav, index) in navs"
-          :key="index"
-          @click="switchNav(nav)"
-        >
-          <span class="nav-item-cont">{{ nav.title }}</span>
-        </li>
-      </ul>
+    <div class="page-navs">
+      <Button
+        :type="curNav.name === nav.name ? 'primary' : 'default'"
+        v-for="(nav, index) in navs"
+        :key="index"
+        @click="switchNav(nav)"
+        >{{ nav.title }}</Button
+      >
     </div>
 
-    <div class="page-bdoy">
-      <router-view />
-    </div>
+    <router-view />
   </div>
 </template>
 

+ 153 - 0
src/modules/client-set/InspectionAccountSet.vue

@@ -0,0 +1,153 @@
+<template>
+  <div class="client-account-set ">
+    <div class="part-box-top">
+      <Button type="success" icon="md-add" @click="toAdd">新增</Button>
+    </div>
+    <div class="part-box">
+      <Table ref="TableList" :columns="columes" :data="users" border></Table>
+    </div>
+  </div>
+</template>
+
+<script>
+import {
+  inspectionUserPageList,
+  deleteInspectionUser,
+  uploadInspectionUser
+} from "@/api";
+
+const initModalForm = {
+  id: "",
+  roleName: "纪检",
+  loginName: "",
+  password: ""
+};
+
+export default {
+  name: "client-account-set",
+  data() {
+    return {
+      workId: this.$route.params.workId,
+      users: [],
+      curUser: {},
+      columes: [
+        {
+          type: "index",
+          title: "序号",
+          width: 100,
+          align: "center"
+        },
+        {
+          title: "角色",
+          key: "roleName"
+        },
+        {
+          title: "账号",
+          key: "loginName",
+          render: (h, param) => {
+            return h("Input", {
+              props: {
+                value: param.row.loginName,
+                clearable: true
+              },
+              style: {
+                width: "200px"
+              },
+              on: {
+                input: function(value) {
+                  param.row.loginName = value;
+                }
+              }
+            });
+          }
+        },
+        {
+          title: "操作",
+          key: "action",
+          width: 240,
+          align: "center",
+          render: (h, param) => {
+            let actions = [
+              {
+                name: "重置",
+                disabled: !param.row.id,
+                action: () => {
+                  this.toSave(param.row);
+                }
+              },
+              {
+                name: "保存",
+                action: () => {
+                  this.toSave(param.row);
+                }
+              },
+              {
+                name: "删除",
+                type: "error",
+                action: () => {
+                  this.toDelete(param.row);
+                }
+              }
+            ];
+            return h("div", this.$tableAction(h, actions));
+          }
+        }
+      ]
+    };
+  },
+  mounted() {
+    this.getList();
+  },
+  methods: {
+    async getList() {
+      const datas = {
+        workId: this.workId
+      };
+      const data = await inspectionUserPageList(datas);
+
+      this.users = data.map(item => {
+        return {
+          id: item.id,
+          loginName: item.loginName,
+          password: item.password,
+          roleName: "纪检"
+        };
+      });
+
+      if (!this.users.length) this.toAdd();
+    },
+    toAdd() {
+      this.users.push({ ...initModalForm });
+    },
+    async toSave(row) {
+      if (!row.loginName.match(new RegExp(`^[a-zA-Z0-9_]{3,20}$`))) {
+        this.$Message.error(
+          "当前账号名只能由数字、字母和下划线组成,长度3-20个字符"
+        );
+        return;
+      }
+      await uploadInspectionUser({
+        id: row.id,
+        loginName: row.loginName,
+        password: "123456"
+      });
+      this.$Message.success("保存成功!");
+      this.getList();
+    },
+    toDelete(row) {
+      this.$Modal.confirm({
+        title: "删除警告",
+        content: "确定要删除当前账号吗?",
+        onOk: () => {
+          this.toDel(row.id);
+        }
+      });
+    },
+    async toDel(id) {
+      await deleteInspectionUser(id);
+      this.$Message.success("删除成功!");
+      this.getList();
+    }
+  }
+};
+</script>

+ 122 - 0
src/modules/client-set/components/ModifyClientUser.vue

@@ -0,0 +1,122 @@
+<template>
+  <Modal
+    class="modify-client-user"
+    v-model="modalIsShow"
+    :title="title"
+    :mask-closable="false"
+    @on-visible-change="visibleChange"
+  >
+    <Form
+      ref="modalFormComp"
+      :model="modalForm"
+      :rules="rules"
+      :key="modalForm.id"
+      :label-width="100"
+    >
+      <FormItem prop="name" label="姓名">
+        <Input
+          v-model.trim="modalForm.loginName"
+          placeholder="请输入姓名"
+          clearable
+        ></Input>
+      </FormItem>
+      <FormItem prop="examNumber" label="考号">
+        <Input
+          v-model.trim="modalForm.password"
+          placeholder="请输入考号"
+          clearable
+        ></Input>
+      </FormItem>
+    </Form>
+    <div slot="footer">
+      <Button type="text" @click="cancel">取消</Button>
+      <Button type="primary" :disabled="isSubmit" @click="submit">确认</Button>
+    </div>
+  </Modal>
+</template>
+
+<script>
+import { uploadClientUser } from "@/api";
+import { password } from "@/plugins/formRules";
+
+const initModalForm = {
+  id: "",
+  loginName: "",
+  password: ""
+};
+
+export default {
+  name: "modify-client-user",
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  computed: {
+    isEdit() {
+      return !!this.instance.id;
+    },
+    title() {
+      return (this.isEdit ? "编辑" : "新增") + "采集账号";
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      modalForm: { ...initModalForm },
+      rules: {
+        loginName: [
+          {
+            required: true,
+            pattern: /^[a-zA-Z0-9_-]{2,19}$/,
+            message: "账号只能包含字母、数字、下划线以及短横线,长度3-20位",
+            trigger: "change"
+          }
+        ],
+        password
+      }
+    };
+  },
+  methods: {
+    initData(val) {
+      if (val.id) {
+        this.modalForm = this.$objAssign(initModalForm, val);
+      } else {
+        this.modalForm = { ...initModalForm };
+      }
+    },
+    visibleChange(visible) {
+      if (visible) {
+        this.initData(this.instance);
+      }
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate();
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const data = await uploadClientUser(this.modalForm).catch(() => {
+        this.isSubmit = false;
+      });
+
+      if (!data) return;
+
+      this.isSubmit = false;
+      this.$Message.success(this.title + "成功!");
+      this.$emit("modified");
+      this.cancel();
+    }
+  }
+};
+</script>

+ 0 - 3
src/modules/client-set/index.js

@@ -1,3 +0,0 @@
-import ClientSet from "./ClientSet.vue";
-
-export default ClientSet;

+ 41 - 48
src/modules/login/login.vue

@@ -1,40 +1,42 @@
 <template>
-  <div class="login login-box">
-    <div class="login-title">
-      <h1>美术阅卷系统登录</h1>
-    </div>
-    <div class="login-form">
-      <Form ref="loginForm" :model="loginModel" :rules="loginRules">
-        <FormItem prop="loginName">
-          <Input
-            v-model.trim="loginModel.loginName"
-            prefix="md-person"
-            placeholder="请输入用户名"
-            clearable
-          ></Input>
-        </FormItem>
-        <FormItem prop="password">
-          <Input
-            type="password"
-            v-model.trim="loginModel.password"
-            prefix="md-lock"
-            placeholder="请输入密码"
-            clearable
-          ></Input>
-        </FormItem>
-        <FormItem>
-          <Button
-            long
-            type="primary"
-            :disabled="isSubmit"
-            @click="submit('loginForm')"
-            >登录</Button
-          >
-          <div class="login-link">
-            <!-- <router-link :to="{ name: 'ForgetPwd' }">忘记密码</router-link> -->
-          </div>
-        </FormItem>
-      </Form>
+  <div class="login-home login">
+    <div class="login login-box">
+      <div class="login-title">
+        <h1>美术阅卷系统登录</h1>
+      </div>
+      <div class="login-form">
+        <Form ref="loginForm" :model="loginModel" :rules="loginRules">
+          <FormItem prop="loginName">
+            <Input
+              v-model.trim="loginModel.loginName"
+              prefix="md-person"
+              placeholder="请输入用户名"
+              clearable
+            ></Input>
+          </FormItem>
+          <FormItem prop="password">
+            <Input
+              type="password"
+              v-model.trim="loginModel.password"
+              prefix="md-lock"
+              placeholder="请输入密码"
+              clearable
+            ></Input>
+          </FormItem>
+          <FormItem>
+            <Button
+              long
+              type="primary"
+              :disabled="isSubmit"
+              @click="submit('loginForm')"
+              >登录</Button
+            >
+            <div class="login-link">
+              <!-- <router-link :to="{ name: 'ForgetPwd' }">忘记密码</router-link> -->
+            </div>
+          </FormItem>
+        </Form>
+      </div>
     </div>
   </div>
 </template>
@@ -62,14 +64,7 @@ export default {
     this.$ls.clear();
   },
   methods: {
-    submit() {
-      // linshi
-      this.$ls.set("token", "1234");
-      this.$router.push({
-        name: "Home"
-      });
-    },
-    async submit1(name) {
+    async submit(name) {
       const valid = await this.$refs[name].validate();
       if (!valid) return;
 
@@ -83,11 +78,9 @@ export default {
       this.isSubmit = false;
       this.$ls.set("user", data, this.GLOBAL.authTimeout);
       this.$store.commit("setUser", data);
+      // TODO:根据角色跳转不同的路由
       this.$router.push({
-        name: "SubjectHome",
-        params: {
-          workId: 1
-        }
+        name: "WorkManage"
       });
     }
   }

+ 59 - 3
src/modules/main/ClientMonitor.vue

@@ -1,15 +1,71 @@
 <template>
   <div class="client-monitor">
-    client-monitor
+    <div class="part-box">
+      <image-action-list
+        :data="papers"
+        ref="ImageActionList"
+      ></image-action-list>
+
+      <div class="part-page">
+        <Page
+          :current="current"
+          :total="total"
+          :page-size="size"
+          show-total
+          show-elevator
+          @on-change="toPage"
+        ></Page>
+      </div>
+    </div>
   </div>
 </template>
 
 <script>
+import { clientMonitorList } from "@/api";
+import ImageActionList from "./components/ImageActionList";
+
 export default {
   name: "client-monitor",
+  components: { ImageActionList },
   data() {
-    return {};
+    return {
+      workId: this.$route.params.workId,
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      papers: []
+    };
   },
-  methods: {}
+  mounted() {
+    this.getList();
+  },
+  methods: {
+    async getList() {
+      const data = await clientMonitorList({
+        workId: this.workId,
+        page: this.current - 1,
+        size: this.size
+      });
+      console.log(data);
+
+      this.papers = "#"
+        .repeat(10)
+        .split("")
+        .map((item, index) => {
+          return {
+            id: index,
+            title: `Scan ${index}`,
+            url:
+              "http://127.0.0.1:9000/api/file/image/download/33/1/833/1?random=fa8244bb-8ec4-46c1-a16e-1bd6f3b8848e",
+            thumbUrl:
+              "http://127.0.0.1:9000/api/file/image/download/33/1/833/2?random=497cc903-c01a-458a-9b4e-82b391cef176"
+          };
+        });
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    }
+  }
 };
 </script>

+ 120 - 3
src/modules/main/PaperManage.vue

@@ -1,15 +1,132 @@
 <template>
   <div class="papers">
-    papers
+    <div class="part-box">
+      <Form ref="FilterForm" label-position="left" inline>
+        <FormItem label="科目:">
+          <Select v-model="filter.subject" placeholder="请选择科目">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem label="考点:">
+          <Select v-model="filter.examRoom" placeholder="请选择考点">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem label="开始编号:">
+          <InputNumber
+            v-model="filter.startNumber"
+            placeholder="请输入数字"
+            clearable
+          ></InputNumber>
+        </FormItem>
+        <FormItem label="结束编号:">
+          <InputNumber
+            v-model="filter.endNumber"
+            placeholder="请输入数字"
+            clearable
+          ></InputNumber>
+        </FormItem>
+        <FormItem label="类型:">
+          <Select v-model="filter.isAbsent" placeholder="请选择类型">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem label="姓名:">
+          <Input
+            v-model.trim="filter.name"
+            placeholder="工作名称"
+            clearable
+          ></Input>
+        </FormItem>
+        <FormItem label="排列方式:">
+          <Select v-model="filter.upload" placeholder="请选择排列方式">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem>
+          <Button type="primary" icon="ios-search" @click="toPage(1)"
+            >查询</Button
+          >
+        </FormItem>
+      </Form>
+
+      <image-action-list
+        :data="papers"
+        :actions="['rotate', 'absent']"
+        ref="ImageActionList"
+      ></image-action-list>
+
+      <div class="part-page">
+        <Page
+          :current="current"
+          :total="total"
+          :page-size="size"
+          show-total
+          show-elevator
+          @on-change="toPage"
+        ></Page>
+      </div>
+    </div>
   </div>
 </template>
 
 <script>
+import { paperPageList } from "@/api";
+import ImageActionList from "./components/ImageActionList";
+
 export default {
   name: "papers",
+  components: { ImageActionList },
   data() {
-    return {};
+    return {
+      filter: {
+        workId: this.$route.params.workId,
+        name: "",
+        schoolId: "",
+        examRoom: "",
+        startNumber: null,
+        endNumber: null,
+        subject: "SC",
+        areaCode: "1",
+        upload: "",
+        sort: "examNumber",
+        isAbsent: ""
+      },
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      papers: []
+    };
+  },
+  mounted() {
+    this.getList();
   },
-  methods: {}
+  methods: {
+    async getList() {
+      const datas = {
+        ...this.filter,
+        page: this.current - 1,
+        size: this.size
+      };
+      const data = await paperPageList(datas);
+      this.papers = data.data.map(paper => {
+        return {
+          id: paper.id,
+          title: paper.examNumber,
+          url: paper.imgSrc,
+          thumbUrl: paper.thumbSrc,
+          missing: paper.missing,
+          stage: paper.stage,
+          style: {},
+          deg: 0
+        };
+      });
+      this.total = data.totalCount;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    }
+  }
 };
 </script>

+ 290 - 4
src/modules/main/StudentManage.vue

@@ -1,15 +1,301 @@
 <template>
-  <div class="students">
-    students
+  <div class="students ">
+    <div class="part-box-top">
+      <Button
+        type="primary"
+        icon="md-cloud-upload"
+        @click="$refs.ExportStudent.open()"
+        >导入考生信息</Button
+      >
+      <Button
+        type="primary"
+        icon="md-cloud-upload"
+        @click="$refs.ExportRelate.open()"
+        >导入关联信息</Button
+      >
+    </div>
+    <div class="part-box">
+      <Form ref="FilterForm" label-position="left" inline>
+        <FormItem label="考区:">
+          <Select v-model="filter.areaCode" placeholder="请选择考区">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem label="学校:">
+          <Select v-model="filter.schoolId" placeholder="请选择学校">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem label="考场:">
+          <Select v-model="filter.examRoom" placeholder="请选择考场">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem label="科目:">
+          <Select v-model="filter.subject" placeholder="请选择科目">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem label="起始考号:">
+          <InputNumber
+            v-model="filter.startNumber"
+            placeholder="请输入数字"
+            clearable
+          ></InputNumber>
+        </FormItem>
+        <FormItem label="终止考号:">
+          <InputNumber
+            v-model="filter.endNumber"
+            placeholder="请输入数字"
+            clearable
+          ></InputNumber>
+        </FormItem>
+        <FormItem label="姓名:">
+          <Input
+            v-model.trim="filter.name"
+            placeholder="工作名称"
+            clearable
+          ></Input>
+        </FormItem>
+        <FormItem label="是否缺考:">
+          <Select v-model="filter.isAbsent" placeholder="请选择是否缺考">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem label="状态:">
+          <Select v-model="filter.upload" placeholder="请选择状态">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem>
+          <Button type="primary" icon="ios-search" @click="toPage(1)"
+            >查询</Button
+          >
+        </FormItem>
+      </Form>
+
+      <Table
+        ref="TableList"
+        :columns="columes"
+        :data="students"
+        disabled-hover
+        border
+      ></Table>
+
+      <div class="part-page">
+        <Page
+          :current="current"
+          :total="total"
+          :page-size="size"
+          show-total
+          show-elevator
+          @on-change="toPage"
+        ></Page>
+      </div>
+    </div>
+
+    <!-- import student  -->
+    <import-file
+      title="导入考生信息"
+      :upload-url="uploadStudentUrl"
+      :upload-data="uploadData"
+      :download-url="downloadStudentTemplateUrl"
+      :download-filename="downloadStudentTemplateFilename"
+      :headers="headers"
+      :format="['xls', 'xlsx']"
+      @upload-success="fileUploaded"
+      ref="ExportStudent"
+    >
+    </import-file>
+    <!-- import-relate -->
+    <import-file
+      title="导入关联信息"
+      :upload-url="uploadRelateUrl"
+      :upload-data="uploadData"
+      :download-url="downloadRelateTemplateUrl"
+      :download-filename="downloadRelateTemplateFilename"
+      :headers="headers"
+      :format="['xls', 'xlsx']"
+      @upload-success="fileUploaded"
+      ref="ExportRelate"
+    >
+    </import-file>
+    <!-- modify-student -->
+    <modify-student
+      :instance="curStudent"
+      @modified="getList"
+      ref="ModifyStudent"
+    ></modify-student>
   </div>
 </template>
 
 <script>
+import { studentPageList, deleteStudent } from "@/api";
+import ImportFile from "@/components/common/ImportFile";
+import ModifyStudent from "./components/ModifyStudent";
+
 export default {
   name: "students",
+  components: { ImportFile, ModifyStudent },
   data() {
-    return {};
+    return {
+      filter: {
+        workId: this.$route.params.workId,
+        name: "",
+        schoolId: "",
+        examRoom: "",
+        startNumber: null,
+        endNumber: null,
+        subject: "",
+        areaCode: "",
+        upload: "",
+        isAbsent: ""
+      },
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      students: [],
+      curStudent: {},
+      isBuildColumn: false,
+      columes: [
+        {
+          type: "index",
+          title: "序号",
+          width: 80,
+          align: "center",
+          indexMethod: row => {
+            return (this.current - 1) * this.size + row._index + 1;
+          }
+        },
+        {
+          title: "姓名",
+          key: "name",
+          width: 80
+        },
+        {
+          title: "考号",
+          key: "examNumber",
+          minWidth: 120
+        },
+        {
+          title: "考区",
+          key: "areaName",
+          minWidth: 120
+        },
+        {
+          title: "学校",
+          key: "sourceName",
+          minWidth: 150
+        },
+        {
+          title: "考场",
+          key: "examRoom",
+          minWidth: 100
+        },
+        {
+          title: "操作",
+          key: "action",
+          width: 160,
+          align: "center",
+          render: (h, param) => {
+            let actions = [];
+
+            if (param.row.canEdit) {
+              actions.push({
+                name: "编辑",
+                action: () => {
+                  this.toEdit(param.row);
+                }
+              });
+              actions.push({
+                name: "删除",
+                type: "error",
+                action: () => {
+                  this.toDelete(param.row);
+                }
+              });
+            }
+
+            return h("div", this.$tableAction(h, actions));
+          }
+        }
+      ],
+      // upload
+      headers: {
+        Authorization: "token"
+      },
+      uploadData: {
+        workId: this.$route.params.workId
+      },
+      uploadStudentUrl: this.GLOBAL.domain + "/import/students",
+      downloadStudentTemplateUrl: "/templates/考生导入模板.xlsx",
+      downloadStudentTemplateFilename: "考生导入模板.xls",
+      uploadRelateUrl: "",
+      downloadRelateTemplateUrl: "/templates/考生导入模板.xlsx",
+      downloadRelateTemplateFilename: "要下载的文件.xls"
+    };
+  },
+  mounted() {
+    this.getList();
   },
-  methods: {}
+  methods: {
+    async getList() {
+      const datas = {
+        // ...this.filter,
+        workId: this.workId,
+        page: this.current - 1,
+        size: this.size
+      };
+      const data = await studentPageList(datas);
+      this.students = data.data.map(student => {
+        student.uploadStatus.split(",").map(status => {
+          const [subjectCode, subjectUploadStatus] = status.split(":");
+          student[subjectCode] = subjectUploadStatus * 1;
+        });
+        student.canEdit = student.uploadStatus.includes("0");
+        return student;
+      });
+      if (data.data.length && !this.isBuildColumn) {
+        this.isBuildColumn = true;
+        data.data[0].uploadStatus.split(",").map(status => {
+          const subjectCode = status.split(":")[0];
+          const column = {
+            title: subjectCode,
+            key: subjectCode
+          };
+          this.columes.splice(this.columes.length - 1, 0, column);
+        });
+      }
+      this.total = data.totalCount;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    toUploadStudentPaper(row) {
+      console.log(row);
+    },
+    toEdit(row) {
+      this.curStudent = row;
+      this.$refs.ModifyStudent.open();
+    },
+    fileUploaded(response) {
+      console.log(response);
+    },
+    toDelete(row) {
+      this.$Modal.confirm({
+        title: "删除警告",
+        content: "确定要删除当前学生吗?",
+        onOk: () => {
+          this.toDel(row.id);
+        }
+      });
+    },
+    async toDel(id) {
+      await deleteStudent(id);
+      this.$Message.success("删除成功!");
+      this.deletePageLastItem();
+    }
+  }
 };
 </script>

+ 144 - 2
src/modules/main/WorkManage.vue

@@ -6,6 +6,30 @@
 
     <div class="home-body">
       <div class="home-main">
+        <div class="part-box">
+          <Form label-position="left" inline>
+            <FormItem>
+              <Input
+                v-model.trim="modalForm.name"
+                placeholder="工作名称"
+                clearable
+              ></Input>
+            </FormItem>
+            <FormItem :label-width="0">
+              <Button type="primary" icon="md-add" @click="toAdd">新增</Button>
+            </FormItem>
+          </Form>
+
+          <Table
+            ref="TableList"
+            :columns="columes"
+            :data="works"
+            :row-class-name="rowClassName"
+            disabled-hover
+            border
+          ></Table>
+        </div>
+
         <view-footer></view-footer>
       </div>
     </div>
@@ -13,11 +37,129 @@
 </template>
 
 <script>
+import { workList, createWork, activeWork, deleteWork } from "@/api";
 export default {
   name: "work",
   data() {
-    return {};
+    return {
+      modalForm: {
+        name: ""
+      },
+      works: [],
+      columes: [
+        {
+          title: "#",
+          key: "id",
+          width: 80
+        },
+        {
+          title: "名称",
+          key: "name",
+          minWidth: 200
+        },
+        {
+          title: "是否为当前工作",
+          key: "active",
+          render: (h, param) => {
+            return h("div", param.row.active ? "是" : "否");
+          }
+        },
+        {
+          title: "创建时间",
+          key: "createdOn",
+          minWidth: 160
+        },
+        {
+          title: "操作",
+          key: "action",
+          width: 300,
+          align: "center",
+          render: (h, param) => {
+            const actions = [
+              {
+                name: "进入",
+                action: () => {
+                  this.toDetail(param.row);
+                }
+              },
+              {
+                name: "删除",
+                type: "error",
+                action: () => {
+                  this.toDelete(param.row);
+                }
+              }
+            ];
+
+            if (!param.row.active) {
+              actions.unshift({
+                name: "设置为当前工作",
+                action: () => {
+                  this.toActive(param.row);
+                }
+              });
+            }
+            return h("div", this.$tableAction(h, actions));
+          }
+        }
+      ]
+    };
   },
-  methods: {}
+  mounted() {
+    this.getList();
+  },
+  methods: {
+    async getList() {
+      const data = await workList();
+      this.works = data.map(item => {
+        return {
+          id: item.id,
+          name: item.name,
+          active: item.active,
+          createdOn: item.createdOn
+        };
+      });
+    },
+    rowClassName(row) {
+      return row.active ? "row-active" : "";
+    },
+    async toAdd() {
+      if (!this.modalForm.name) {
+        this.$Message.error("请输入工作名称!");
+        return;
+      }
+
+      await createWork(this.modalForm);
+      this.$Message.success("创建工作成功!");
+      this.getList();
+    },
+    async toActive(row) {
+      await activeWork(row.id);
+      this.$Message.success("设置成功!");
+      this.getList();
+    },
+    toDetail(row) {
+      this.$router.push({
+        name: "WorkOverview",
+        params: {
+          workId: row.id
+        }
+      });
+    },
+    toDelete(row) {
+      this.$Modal.confirm({
+        title: "删除警告",
+        content: "确定要删除当前工作吗?",
+        onOk: () => {
+          this.toDel(row.id);
+        }
+      });
+    },
+    async toDel(id) {
+      await deleteWork(id);
+      this.$Message.success("删除成功!");
+      this.getList();
+    }
+  }
 };
 </script>

+ 109 - 3
src/modules/main/WorkOverview.vue

@@ -1,15 +1,121 @@
 <template>
   <div class="overview">
-    overview
+    <div class="overview-head" v-if="overviewInfo.workName">
+      <h1 class="overview-title">{{ overviewInfo.workName }}</h1>
+      <div class="overview-infos">
+        <ul class="overview-list">
+          <li>
+            <p>考生总数</p>
+            <p>{{ overviewInfo.stuTotalCount }}</p>
+          </li>
+          <li>
+            <p>缺考数量</p>
+            <p>{{ overviewInfo.stuAbsentCount }}</p>
+          </li>
+          <li>
+            <p>试卷总数</p>
+            <p>{{ overviewInfo.paperCount }}</p>
+          </li>
+          <li>
+            <p>评卷员总数</p>
+            <p>{{ overviewInfo.markerCount }}</p>
+          </li>
+          <li>
+            <p>试题套数</p>
+            <p>{{ overviewInfo.questionCount }}</p>
+          </li>
+        </ul>
+      </div>
+      <div class="overview-actions">
+        <Button type="primary" @click="download(exportGradeScoreUrl)"
+          >导出档位成绩</Button
+        >
+        <Button type="primary" @click="download(exportScoreUrl)"
+          >导出分数成绩</Button
+        >
+      </div>
+    </div>
+    <div class="overview-body">
+      <div
+        class="overview-subject"
+        v-for="(subject, index) in overviewInfo.subjectOverviews"
+        :key="index"
+      >
+        <div class="subject-content">
+          <h2 class="subject-name">{{ subject.subjectName }}</h2>
+          <div class="subject-infos">
+            <ul class="overview-list">
+              <li>
+                <p>已上传</p>
+                <p>{{ subject.uploadedCount }}</p>
+              </li>
+              <li>
+                <p>未采集</p>
+                <p>{{ subject.leftCount }}</p>
+              </li>
+              <li>
+                <p>进度</p>
+                <p>{{ subject.progress }}</p>
+              </li>
+            </ul>
+          </div>
+          <div class="subject-actions">
+            <p>当前阶段</p>
+            <p>{{ subject.markStageName }}</p>
+            <div class="subject-actions-detail">
+              <Button @click="download(subject.exportAbsentDataUrl)"
+                >导出缺考名单</Button
+              >
+              <Button>评卷管理</Button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
 <script>
+import { workOverviewDetail } from "@/api";
+import { SUBJECT_STAGE } from "@/constants/enumerate";
+
 export default {
   name: "overview",
   data() {
-    return {};
+    return {
+      workId: this.$route.params.workId,
+      SUBJECT_STAGE,
+      exportGradeScoreUrl: "",
+      exportScoreUrl: "",
+      overviewInfo: {
+        subjectOverviews: []
+      }
+    };
+  },
+  mounted() {
+    this.getDetail();
+    this.exportGradeScoreUrl = `${this.GLOBAL.domain}/export/score/exportLevelResult?workId=${this.workId}`;
+    this.exportScoreUrl = `${this.GLOBAL.domain}/export/score/exportScoreResult?workId=${this.workId}`;
   },
-  methods: {}
+  methods: {
+    async getDetail() {
+      const data = await workOverviewDetail(this.workId);
+      data.subjectOverviews.map(item => {
+        item.markStageName = SUBJECT_STAGE[item.markStage];
+        item.exportAbsentDataUrl = `${this.GLOBAL.domain}/export/users/${this.workId}/${item.subject}/export`;
+        item.progress =
+          item.uploadedCount + item.leftCount
+            ? (
+                (100 * item.uploadedCount) /
+                (item.uploadedCount + item.leftCount)
+              ).toFixed(1) + "%"
+            : "0.0%";
+      });
+      this.overviewInfo = data;
+    },
+    download(url) {
+      window.open(url);
+    }
+  }
 };
 </script>

+ 114 - 0
src/modules/main/components/ImageActionList.vue

@@ -0,0 +1,114 @@
+<template>
+  <div :class="classes">
+    <div class="image-view" v-for="(image, index) in data" :key="index">
+      <h5 class="image-view-title">{{ image.title }}</h5>
+      <div
+        class="image-view-contain"
+        :style="image.styles"
+        @click="toReview(index)"
+      >
+        <img :src="image.thumbUrl" :alt="image.title" />
+      </div>
+      <div class="image-view-actions" v-if="actions.length">
+        <Button
+          size="small"
+          type="success"
+          @click="toRotate(image)"
+          v-if="canRotate"
+          >旋转</Button
+        >
+        <Button
+          size="small"
+          type="primary"
+          @click="toSaveRotate(image)"
+          v-if="canRotate && image['deg']"
+          >保存</Button
+        >
+        <Button
+          :type="image.missing ? 'error' : 'default'"
+          size="small"
+          @click="toSignAbsent(image)"
+          v-if="canAbsent"
+          >缺考</Button
+        >
+      </div>
+    </div>
+
+    <!-- image-preview -->
+    <image-preview
+      :image-list="data"
+      :init-index="curImageIndex"
+      header-hide
+      ref="ImagePreview"
+      v-if="data.length"
+    ></image-preview>
+  </div>
+</template>
+
+<script>
+import { rotatePaper, absentPaper } from "@/api";
+import ImagePreview from "@/components/common/ImagePreview";
+
+export default {
+  name: "image-action-list",
+  components: { ImagePreview },
+  props: {
+    data: {
+      type: Array,
+      default() {
+        return [];
+      }
+    },
+    actions: {
+      type: Array,
+      default() {
+        return [];
+      }
+    },
+    columnNumber: {
+      type: Number,
+      default: 5
+    }
+  },
+  data() {
+    return {
+      curImageIndex: 0,
+      imageList: []
+    };
+  },
+  computed: {
+    classes() {
+      return ["image-action-list", `image-action-list-${this.columnNumber}`];
+    },
+    canRotate() {
+      return this.actions.includes("rotate");
+    },
+    canAbsent() {
+      return this.actions.includes("absent");
+    }
+  },
+  methods: {
+    toRotate(image) {
+      image.deg += 90;
+      if (image.deg === 360) image.deg = 0;
+      image.styles = {
+        transform: `rotate(${image.deg}deg)`
+      };
+    },
+    async toSaveRotate(image) {
+      if (!image.deg) return;
+
+      await rotatePaper(image.id, image.deg);
+      this.$Message.success("保存成功!");
+    },
+    async toSignAbsent(image) {
+      await absentPaper(image.id);
+      image.missing = !image.missing;
+    },
+    toReview(index) {
+      this.curImageIndex = index;
+      this.$refs.ImagePreview.open();
+    }
+  }
+};
+</script>

+ 165 - 0
src/modules/main/components/ModifyStudent.vue

@@ -0,0 +1,165 @@
+<template>
+  <Modal
+    class="modify-student"
+    v-model="modalIsShow"
+    :title="title"
+    :mask-closable="false"
+    @on-visible-change="visibleChange"
+  >
+    <Form
+      ref="modalFormComp"
+      :model="modalForm"
+      :rules="rules"
+      :key="modalForm.id"
+      :label-width="100"
+    >
+      <FormItem prop="name" label="姓名">
+        <Input
+          v-model.trim="modalForm.name"
+          placeholder="请输入姓名"
+          clearable
+        ></Input>
+      </FormItem>
+      <FormItem prop="examNumber" label="考号">
+        <Input
+          v-model.trim="modalForm.examNumber"
+          placeholder="请输入考号"
+          clearable
+        ></Input>
+      </FormItem>
+      <FormItem prop="areaCode" label="考区">
+        <Select v-model="modalForm.areaCode" placeholder="请选择考区">
+          <Option value=""></Option>
+        </Select>
+      </FormItem>
+      <FormItem prop="schoolId" label="学校">
+        <Select v-model="modalForm.schoolId" placeholder="请选择学校">
+          <Option value=""></Option>
+        </Select>
+      </FormItem>
+      <FormItem prop="examRoom" label="考场">
+        <Select v-model="modalForm.examRoom" placeholder="请选择考场">
+          <Option value=""></Option>
+        </Select>
+      </FormItem>
+    </Form>
+    <div slot="footer">
+      <Button type="text" @click="cancel">取消</Button>
+      <Button type="primary" :disabled="isSubmit" @click="submit">确认</Button>
+    </div>
+  </Modal>
+</template>
+
+<script>
+import { uploadStudent } from "@/api";
+
+const initModalForm = {
+  id: "",
+  name: "",
+  examNumber: "",
+  areaCode: "",
+  schoolID: "",
+  examRoom: ""
+};
+
+export default {
+  name: "modify-student",
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  computed: {
+    isEdit() {
+      return !!this.instance.id;
+    },
+    title() {
+      return (this.isEdit ? "编辑" : "新增") + "考生信息";
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      modalForm: { ...initModalForm },
+      rules: {
+        name: [
+          {
+            required: true,
+            message: "请输入姓名",
+            trigger: "change"
+          }
+        ],
+        examNumber: [
+          {
+            required: true,
+            message: "请输入考号",
+            trigger: "change"
+          }
+        ],
+        areaCode: [
+          {
+            required: true,
+            message: "请选择考区",
+            trigger: "change"
+          }
+        ],
+        schoolId: [
+          {
+            required: true,
+            message: "请选择学校",
+            trigger: "change"
+          }
+        ],
+        examRoom: [
+          {
+            required: true,
+            message: "请选择考场",
+            trigger: "change"
+          }
+        ]
+      }
+    };
+  },
+  methods: {
+    initData(val) {
+      if (val.id) {
+        this.modalForm = this.$objAssign(initModalForm, val);
+      } else {
+        this.modalForm = { ...initModalForm };
+      }
+    },
+    visibleChange(visible) {
+      if (visible) {
+        this.initData(this.instance);
+      }
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate();
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const data = await uploadStudent(this.modalForm).catch(() => {
+        this.isSubmit = false;
+      });
+
+      if (!data) return;
+
+      this.isSubmit = false;
+      this.$Message.success(this.title + "成功!");
+      this.$emit("modified");
+      this.cancel();
+    }
+  }
+};
+</script>

+ 41 - 0
src/modules/main/components/UploadStudentPaper.vue

@@ -0,0 +1,41 @@
+<template>
+  <Modal
+    class="upload-student-paper"
+    v-model="modalIsShow"
+    title="上传考生图片"
+    :mask-closable="false"
+    @on-visible-change="visibleChange"
+  >
+    <Form
+      ref="modalFormComp"
+      :model="modalForm"
+      :rules="rules"
+      :key="modalForm.id"
+      :label-width="100"
+    >
+      <FormItem prop="subject" label="科目">
+        <Select v-model="modalForm.subject" placeholder="请选择科目">
+          <Option value=""></Option>
+        </Select>
+      </FormItem>
+      <FormItem prop="schoolId" label="学校">
+        <Checkbox v-model="modalForm.schoolId">Checkbox</Checkbox>
+      </FormItem>
+    </Form>
+    <div slot="footer">
+      <Button type="text" @click="cancel">取消</Button>
+      <Button type="primary" :disabled="isSubmit" @click="submit">确认</Button>
+    </div>
+  </Modal>
+</template>
+
+<script>
+// TODO: 原型中没有描述,暂时不做
+export default {
+  name: "upload-student-paper",
+  data() {
+    return {};
+  },
+  methods: {}
+};
+</script>

+ 2 - 2
src/plugins/axios.js

@@ -126,7 +126,7 @@ const $del = (url, datas) => {
   return axios
     .delete(url)
     .then(rep => {
-      return rep.data;
+      return successCallback(rep.data);
     })
     .catch(error => {
       throw new Error(errorCallback(error));
@@ -158,7 +158,7 @@ const $patch = (url, datas) => {
   return axios
     .patch(url, datas)
     .then(rep => {
-      return rep.data;
+      return successCallback(rep.data);
     })
     .catch(error => {
       throw new Error(errorCallback(error));

+ 1 - 3
src/plugins/utils.js

@@ -114,9 +114,7 @@ function tableAction(h, actions) {
         size: "small",
         disabled: !!item.disabled
       },
-      style: {
-        marginRight: "5px"
-      },
+      style: {},
       on: {
         click: () => {
           item.action();

+ 17 - 10
src/routers/main.js

@@ -11,6 +11,7 @@ import StudentScore from "../modules/main/StudentScore";
 import ClientSet from "../modules/client-set/ClientSet";
 import ClientAccountSet from "../modules/client-set/ClientAccountSet";
 import ClientParamSet from "../modules/client-set/ClientParamSet";
+import InspectionAccountSet from "../modules/client-set/InspectionAccountSet";
 
 // grading-set
 import GradingSet from "../modules/grading-set/GradingSet";
@@ -22,15 +23,6 @@ import MarkSet from "../modules/mark-set/MarkSet";
 import MarkRuleSet from "../modules/mark-set/MarkRuleSet";
 import ExportPaper from "../modules/mark-set/ExportPaper";
 
-const workRouter = {
-  path: "/work-manage",
-  name: "WorkManage",
-  component: WorkManage,
-  meta: {
-    title: "工作"
-  }
-};
-
 const clientSetRoutes = [
   {
     path: "client-account-set",
@@ -47,6 +39,14 @@ const clientSetRoutes = [
     meta: {
       title: "参数设置"
     }
+  },
+  {
+    path: "inspection-account-set",
+    name: "InspectionAccountSet",
+    component: InspectionAccountSet,
+    meta: {
+      title: "纪检账号"
+    }
   }
 ];
 
@@ -184,7 +184,14 @@ export const gradingSetNavs = getNavs(gradingSetRoutes);
 export const markSetNavs = getNavs(markSetRoutes);
 
 export default [
-  { ...workRouter },
+  {
+    path: "/work-manage",
+    name: "WorkManage",
+    component: WorkManage,
+    meta: {
+      title: "工作"
+    }
+  },
   {
     path: "/main/:workId(\\d+)",
     name: "Main",

+ 64 - 0
vue.config.js

@@ -0,0 +1,64 @@
+var webpack = require("webpack");
+var TerserPlugin = require("terser-webpack-plugin");
+var CompressionPlugin = require("compression-webpack-plugin");
+var devProxy = {};
+try {
+  devProxy = require("./dev-proxy");
+} catch (error) {}
+
+var proxy = process.env.NODE_ENV === "production" ? {} : devProxy;
+
+// 配置手册: https://cli.vuejs.org/zh/config/#vue-config-js
+var config = {
+  // publicPath: './',
+  devServer: {
+    port: 8076
+  },
+  chainWebpack: config => {
+    // webpack-chain配置手册:github.com/neutrinojs/webpack-chain#getting-started
+    // config.plugin("provide").use(webpack.ProvidePlugin, [
+    //   {
+    //     "window.Quill": "quill/dist/quill.js",
+    //     Quill: "quill/dist/quill.js"
+    //   }
+    // ]);
+  }
+};
+
+// compress配置手册:https://github.com/mishoo/UglifyJS2/tree/harmony#compress-options
+if (process.env.NODE_ENV === "production") {
+  config.configureWebpack = {
+    plugins: [
+      // gzip
+      new CompressionPlugin({
+        filename: "[path].gz[query]",
+        algorithm: "gzip",
+        test: new RegExp("\\.(js|css)$"),
+        threshold: 10240,
+        minRatio: 0.8
+      })
+    ],
+    optimization: {
+      minimizer: [
+        new TerserPlugin({
+          terserOptions: { compress: { drop_console: true } }
+        })
+      ]
+    }
+  };
+}
+
+if (proxy && Object.keys(proxy).length) {
+  config.devServer.proxy = proxy;
+}
+
+// 解决iview自定义主题导入less报错
+config.css = {
+  loaderOptions: {
+    less: {
+      javascriptEnabled: true
+    }
+  }
+};
+
+module.exports = config;

+ 11 - 0
yarn.lock

@@ -2391,6 +2391,17 @@ compressible@~2.0.16:
   dependencies:
     mime-db ">= 1.43.0 < 2"
 
+compression-webpack-plugin@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.npm.taobao.org/compression-webpack-plugin/download/compression-webpack-plugin-4.0.0.tgz?cache=0&sync_timestamp=1589291140068&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcompression-webpack-plugin%2Fdownload%2Fcompression-webpack-plugin-4.0.0.tgz#7599f592050002a49cd3ad3ee18ae7371e266bca"
+  integrity sha1-dZn1kgUAAqSc060+4YrnNx4ma8o=
+  dependencies:
+    cacache "^15.0.3"
+    find-cache-dir "^3.3.1"
+    schema-utils "^2.6.6"
+    serialize-javascript "^3.0.0"
+    webpack-sources "^1.4.3"
+
 compression@^1.7.4:
   version "1.7.4"
   resolved "https://registry.npm.taobao.org/compression/download/compression-1.7.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcompression%2Fdownload%2Fcompression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"