瀏覽代碼

命题管理完成

zhangjie 4 年之前
父節點
當前提交
a606aab8f1
共有 55 個文件被更改,包括 4008 次插入470 次删除
  1. 10 0
      card/assets/styles/element-ui-costom.scss
  2. 199 5
      src/assets/styles/base.scss
  3. 6 14
      src/assets/styles/element-ui-costom.scss
  4. 0 181
      src/assets/styles/home.scss
  5. 3 0
      src/assets/styles/icons.scss
  6. 69 88
      src/assets/styles/pages.scss
  7. 14 5
      src/components/UploadFileView.vue
  8. 73 0
      src/components/base/CardRuleSelect.vue
  9. 69 0
      src/components/base/CourseSelect.vue
  10. 68 0
      src/components/base/PaperNumberSelect.vue
  11. 78 0
      src/components/base/QuestionTeacherSelect.vue
  12. 15 4
      src/constants/enumerate.js
  13. 11 61
      src/constants/navs.js
  14. 24 14
      src/modules/base/api.js
  15. 5 5
      src/modules/base/components/ModifyCardRule.vue
  16. 8 8
      src/modules/base/components/ModifyCourse.vue
  17. 9 8
      src/modules/base/components/ModifyOrganization.vue
  18. 4 4
      src/modules/base/components/ModifyRole.vue
  19. 5 5
      src/modules/base/components/ModifyTemplate.vue
  20. 12 17
      src/modules/base/components/ModifyUser.vue
  21. 1 1
      src/modules/base/components/SelectOrgs.vue
  22. 12 9
      src/modules/base/views/CardRule.vue
  23. 1 1
      src/modules/base/views/CommonRule.vue
  24. 6 6
      src/modules/base/views/CourseManage.vue
  25. 4 4
      src/modules/base/views/OrganizationManage.vue
  26. 10 5
      src/modules/base/views/RoleManage.vue
  27. 12 6
      src/modules/base/views/TemplateManage.vue
  28. 7 6
      src/modules/base/views/UserManage.vue
  29. 65 0
      src/modules/exam/api.js
  30. 63 0
      src/modules/exam/components/ApplyAuditHistory.vue
  31. 455 0
      src/modules/exam/components/ApplyContent.vue
  32. 237 0
      src/modules/exam/components/BatchAddExamTask.vue
  33. 303 0
      src/modules/exam/components/CardOptionDialog.vue
  34. 255 0
      src/modules/exam/components/ModifyExamTask.vue
  35. 179 0
      src/modules/exam/components/ModifyTaskApply.vue
  36. 112 0
      src/modules/exam/components/UploadPaperDialog.vue
  37. 40 0
      src/modules/exam/router.js
  38. 15 0
      src/modules/exam/views/DoneTask.vue
  39. 350 0
      src/modules/exam/views/ExamTaskManage.vue
  40. 318 0
      src/modules/exam/views/TaskApplyManage.vue
  41. 269 0
      src/modules/exam/views/TaskPaperManage.vue
  42. 435 0
      src/modules/exam/views/TaskReviewManage.vue
  43. 15 0
      src/modules/exam/views/WaitTask.vue
  44. 2 2
      src/modules/login/api.js
  45. 0 0
      src/modules/print/api.js
  46. 40 0
      src/modules/print/router.js
  47. 15 0
      src/modules/print/views/BusinessDataDetail.vue
  48. 15 0
      src/modules/print/views/BusinessDataExport.vue
  49. 15 0
      src/modules/print/views/PlanLinkPaper.vue
  50. 15 0
      src/modules/print/views/PrintPlanManage.vue
  51. 15 0
      src/modules/print/views/PrintProgressManage.vue
  52. 15 0
      src/modules/print/views/PrintTaskManage.vue
  53. 26 6
      src/plugins/filters.js
  54. 3 4
      src/router.js
  55. 1 1
      src/views/Home.vue

+ 10 - 0
card/assets/styles/element-ui-costom.scss

@@ -245,6 +245,16 @@
       transform: scale(1.2);
     }
   }
+  &.btn-table-primary {
+    > i {
+      color: $--color-primary;
+    }
+  }
+  &.btn-table-danger {
+    > i {
+      color: $--color-danger;
+    }
+  }
 }
 // table
 .el-table {

+ 199 - 5
src/assets/styles/base.scss

@@ -119,6 +119,149 @@ body {
   font-weight: bold;
 }
 
+/* part */
+.part-box {
+  margin-bottom: 20px;
+  background-color: #fff;
+  border-radius: $--border-radius;
+
+  &-border {
+    border: 1px solid $--color-border;
+  }
+  &-pad {
+    padding: 15px;
+  }
+
+  &-filter {
+    padding: 15px 15px 0;
+    border: 1px solid $--color-border;
+
+    .el-form-item {
+      margin-bottom: 15px;
+    }
+  }
+
+  &-flex {
+    display: flex;
+    align-items: stretch;
+    justify-content: space-between;
+  }
+
+  &-action {
+    padding-bottom: 15px;
+    white-space: nowrap;
+    display: flex;
+    align-items: flex-end;
+  }
+  &-tips {
+    font-size: 14px;
+    line-height: 25px;
+    color: $--color-text-regular;
+    margin-bottom: 15px;
+  }
+}
+.part-title {
+  font-size: 16px;
+  font-weight: bold;
+  padding: 15px 20px;
+  line-height: 30px;
+  overflow: hidden;
+
+  h2 {
+    float: left;
+  }
+  &-infos {
+    float: right;
+  }
+}
+.part-body {
+  padding: 25px;
+}
+.part-page {
+  margin-top: 15px;
+  text-align: right;
+}
+.part-none {
+  padding: 100px;
+  font-size: 20px;
+  color: $--color-text-secondary;
+  text-align: center;
+}
+
+/* table */
+.table {
+  width: 100%;
+  border-spacing: 0;
+  border-collapse: collapse;
+  text-align: center;
+  margin-bottom: 25px;
+  border-radius: 20px;
+
+  th {
+    padding: 17px;
+    line-height: 20px;
+    letter-spacing: 1px;
+    border: 1px solid $--border-color-light;
+  }
+  td {
+    padding: 17px;
+    line-height: 20px;
+    border: 1px solid $--border-color-light;
+
+    &.td-link {
+      span {
+        cursor: pointer;
+        &:hover {
+          color: $--color-text-primary;
+        }
+      }
+    }
+  }
+  .td-th {
+    font-weight: 600;
+    color: $--color-text-primary;
+  }
+
+  &-striped {
+    tr:nth-of-type(odd) {
+      background-color: $--color-white;
+    }
+  }
+
+  &.table-white {
+    background-color: #fff;
+  }
+  &.table-narrow {
+    border-radius: 0;
+    margin: 0;
+
+    td,
+    th {
+      padding: 8px;
+    }
+  }
+}
+
+/* list */
+.list-lr-right {
+  float: right;
+  width: 300px;
+}
+.list-lr-left {
+  margin-right: 320px;
+}
+
+.vlcode {
+  height: 36px;
+}
+.vlcode-left {
+  margin-right: 135px;
+}
+.vlcode-right {
+  float: right;
+  width: 120px;
+}
+
 // color
 .color-primary {
   color: $--color-primary !important;
@@ -135,10 +278,19 @@ body {
 .color-info {
   color: $--color-info;
 }
+.color-dark {
+  color: $--color-dark;
+}
 // text
 .text-center {
   text-align: center;
 }
+.text-left {
+  text-align: left;
+}
+.text-right {
+  text-align: right;
+}
 
 // other
 .btn--danger {
@@ -171,14 +323,56 @@ body {
     color: $--color-success;
   }
 }
-.tb-action-column {
-  .cell {
-    overflow: visible;
-  }
-}
 .mr-1 {
   margin-right: 5px;
 }
 .mr-2 {
   margin-right: 10px;
 }
+.mb-2 {
+  margin-bottom: 10px;
+}
+.mb-4 {
+  margin-bottom: 20px;
+}
+
+// other
+.tips-info {
+  font-size: 14px;
+  line-height: 25px;
+  color: $--color-text-secondary;
+}
+.tips-dark {
+  color: $--color-text-primary;
+}
+.tips-error {
+  color: $--color-danger;
+}
+.tips-icon {
+  display: inline-block;
+  vertical-align: middle;
+  color: $--color-text-secondary;
+  font-size: 18px;
+  margin: 0 10px;
+  cursor: pointer;
+}
+.form-item-content {
+  color: $--color-text-regular;
+}
+.action-column {
+  .cell {
+    overflow: visible;
+  }
+}
+.inline-block {
+  display: inline-block;
+  vertical-align: top;
+}
+.custom-tree-node {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 14px;
+  padding-right: 8px;
+}

+ 6 - 14
src/assets/styles/element-ui-costom.scss

@@ -23,20 +23,6 @@
   position: relative;
   border-top: 1px solid #e5e5e5;
 
-  &::after {
-    content: "";
-    position: absolute;
-    top: 0;
-    left: 0;
-    z-index: auto;
-    width: 100%;
-    height: 10px;
-    background: linear-gradient(
-      0deg,
-      rgba(245, 245, 245, 0) 0%,
-      rgba(238, 238, 238, 1) 100%
-    );
-  }
   .el-form-item__label {
     font-weight: bold;
     color: #545454;
@@ -99,6 +85,12 @@
       margin-right: 20px;
     }
   }
+  // form-info
+  &.form-info {
+    .el-form-item {
+      margin-bottom: 0;
+    }
+  }
 }
 // input
 .el-input {

+ 0 - 181
src/assets/styles/home.scss

@@ -306,184 +306,3 @@
     color: $--color-text-primary;
   }
 }
-
-/* part */
-.part-box {
-  margin-bottom: 20px;
-  background-color: #fff;
-  border-radius: $--border-radius;
-
-  &-border {
-    border: 1px solid $--color-border;
-  }
-  &-pad {
-    padding: 15px;
-  }
-
-  &-filter {
-    padding: 15px 15px 0;
-    border: 1px solid $--color-border;
-
-    .el-form-item {
-      margin-bottom: 15px;
-    }
-  }
-
-  &-flex {
-    display: flex;
-    align-items: stretch;
-    justify-content: space-between;
-  }
-
-  &-action {
-    padding-bottom: 15px;
-    white-space: nowrap;
-    display: flex;
-    align-items: flex-end;
-  }
-  &-tips {
-    font-size: 14px;
-    line-height: 25px;
-    color: $--color-text-regular;
-    margin-bottom: 15px;
-  }
-}
-.part-title {
-  font-size: 16px;
-  font-weight: bold;
-  padding: 15px 20px;
-  line-height: 30px;
-  overflow: hidden;
-
-  h2 {
-    float: left;
-  }
-  &-infos {
-    float: right;
-  }
-}
-.part-body {
-  padding: 25px;
-}
-.part-page {
-  margin-top: 15px;
-  text-align: right;
-}
-.part-none {
-  padding: 100px;
-  font-size: 20px;
-  color: $--color-text-secondary;
-  text-align: center;
-}
-
-/* table */
-.table {
-  width: 100%;
-  border-spacing: 0;
-  border-collapse: collapse;
-  text-align: center;
-  margin-bottom: 25px;
-  border-radius: 20px;
-
-  th {
-    padding: 17px;
-    line-height: 20px;
-    letter-spacing: 1px;
-    border: 1px solid $--border-color-light;
-  }
-  td {
-    padding: 17px;
-    line-height: 20px;
-    border: 1px solid $--border-color-light;
-
-    &.td-link {
-      span {
-        cursor: pointer;
-        &:hover {
-          color: $--color-text-primary;
-        }
-      }
-    }
-  }
-  .td-th {
-    font-weight: 600;
-    color: $--color-text-primary;
-  }
-
-  &-striped {
-    tr:nth-of-type(odd) {
-      background-color: $--color-white;
-    }
-  }
-
-  &.table-white {
-    background-color: #fff;
-  }
-  &.table-narrow {
-    border-radius: 0;
-    margin: 0;
-
-    td,
-    th {
-      padding: 8px;
-    }
-  }
-}
-
-/* list */
-.list-lr-right {
-  float: right;
-  width: 300px;
-}
-.list-lr-left {
-  margin-right: 320px;
-}
-
-.vlcode {
-  height: 36px;
-}
-.vlcode-left {
-  margin-right: 135px;
-}
-.vlcode-right {
-  float: right;
-  width: 120px;
-}
-
-// other
-.tips-info {
-  font-size: 14px;
-  line-height: 25px;
-  color: $--color-text-secondary;
-}
-.tips-error {
-  color: $--color-danger;
-}
-.tips-icon {
-  display: inline-block;
-  vertical-align: middle;
-  color: $--color-text-secondary;
-  font-size: 18px;
-  margin: 0 10px;
-  cursor: pointer;
-}
-.form-item-content {
-  color: $--color-text-regular;
-}
-.action-column {
-  .cell {
-    overflow: visible;
-  }
-}
-.inline-block {
-  display: inline-block;
-  vertical-align: top;
-}
-.custom-tree-node {
-  flex: 1;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  font-size: 14px;
-  padding-right: 8px;
-}

+ 3 - 0
src/assets/styles/icons.scss

@@ -146,6 +146,9 @@
   &-circle-lock {
     background-image: url(../images/icon-circle-lock.png);
   }
+  &-close-act {
+    background-image: url(../images/icon-close-act.png);
+  }
   &-files {
     background-image: url(../images/icon-files.png);
     width: 18px;

+ 69 - 88
src/assets/styles/pages.scss

@@ -1,113 +1,94 @@
 // task-detail
 .task-detail {
-  border-radius: $--border-radius;
-  border: 1px solid $--color-border;
-
-  .task-title {
-    font-size: 16px;
-    font-weight: bold;
-    padding: 15px 20px;
-    line-height: 30px;
-    border-bottom: 1px solid $--color-border;
+  .table {
+    border-radius: $--border-radius;
+    color: $--color-text-primary;
+    border: 1px solid $--color-border;
+    border-bottom: 0;
+    border-right: 0;
+    border-collapse: separate;
     overflow: hidden;
 
-    h2 {
-      float: left;
-
-      span:not(:last-child) {
-        margin-right: 50px;
-      }
+    th {
+      border-left: 0;
+      border-top: 0;
+      color: $--color-text-regular;
+      padding: 10px;
     }
-    &-infos {
-      float: right;
+    td {
+      border-left: 0;
+      border-top: 0;
+      padding: 10px;
     }
-    &-add {
-      margin-left: 15px;
-      cursor: pointer;
-      i {
-        margin-right: 5px;
-      }
-
-      &:hover {
-        color: #333;
-      }
+    tr:nth-of-type(even) {
+      background-color: $--color-background;
     }
-    .el-checkbox__label {
-      font-size: 16px;
-      color: $--color-text-primary !important;
+    td[rowspan="2"] {
+      background-color: $--color-white;
     }
   }
-  .task-audit {
-    margin-bottom: 20px;
-
-    &-history {
-      color: $--color-text-regular;
-      margin-left: 5px;
-      cursor: pointer;
-      > i {
-        margin-top: -3px;
-        margin-left: -6px;
-      }
+  .icon {
+    margin-right: 10px;
+  }
 
-      &:hover {
-        color: $--color-text-primary;
-      }
-    }
+  .task-action {
+    margin-top: 50px;
+    text-align: center;
   }
-  .task-body {
-    padding: 25px;
+
+  .image-item {
+    display: inline-block;
+    vertical-align: top;
+    width: 80px;
+    height: 80px;
+    margin: 0 10px 10px 0;
+    border: 1px solid $--color-border;
     position: relative;
-    &::before {
-      content: "";
-      display: block;
+
+    img {
       position: absolute;
-      width: 100%;
-      height: 18px;
+      margin: 0;
+      max-width: 100%;
+      max-height: 100%;
       top: 0;
       left: 0;
-      background: linear-gradient(
-        0deg,
-        rgba(245, 245, 245, 0) 0%,
-        rgba(245, 245, 245, 1) 100%
-      );
+      right: 0;
+      bottom: 0;
+      z-index: auto;
     }
 
-    .table {
-      border-radius: $--border-radius;
-      color: $--color-text-regular;
-      border: 1px solid $--color-border;
-      border-bottom: 0;
-      border-right: 0;
-      border-collapse: separate;
-      overflow: hidden;
+    .image-delete {
+      display: none;
+      position: absolute;
+      z-index: 99;
+      width: 100%;
+      height: 30px;
+      line-height: 30px;
+      text-align: center;
+      font-size: 20px;
+      bottom: 0;
+      left: 0;
+      background-color: rgba($color: #000000, $alpha: 0.3);
+      color: $--color-danger;
+      cursor: pointer;
+    }
 
-      th {
-        border-left: 0;
-        border-top: 0;
-        font-size: 16px;
-        color: $--color-text-primary;
-      }
-      td {
-        border-left: 0;
-        border-top: 0;
-      }
-      tr:nth-of-type(even) {
-        background-color: $--color-background;
-      }
-      td[rowspan="2"] {
-        background-color: $--color-white;
+    &:hover {
+      .image-delete {
+        display: block;
       }
     }
-    .icon {
-      margin-right: 10px;
-    }
   }
+  .image-add {
+    font-size: 40px;
+    line-height: 1;
+    padding: 19px;
+    text-align: center;
+    cursor: pointer;
+    color: $--border-color-base;
 
-  .task-action {
-    text-align: right;
-    .el-button--default {
-      color: $--color-text-regular;
-      font-weight: bold;
+    &:hover {
+      color: $--color-text-secondary;
     }
   }
 }

+ 14 - 5
src/components/UploadFileView.vue

@@ -16,19 +16,21 @@
       :before-upload="handleBeforeUpload"
       :on-error="handleError"
       :on-success="handleSuccess"
+      :on-progress="handleProgress"
       :http-request="upload"
       :show-file-list="false"
-      :auto-upload="false"
       :disabled="disabled"
       style="display:inline-block;margin: 0 10px;"
       ref="UploadComp"
     >
-      <el-button type="primary" :disabled="disabled">选择</el-button>
+      <el-button type="primary" :disabled="disabled" :loading="loading"
+        >选择</el-button
+      >
     </el-upload>
     <el-button
       type="primary"
       @click="startUpload"
-      v-if="canUpload"
+      v-if="canUpload && !autoUpload"
       :loading="loading"
       style="margin-right: 10px;"
       >开始上传</el-button
@@ -45,7 +47,7 @@ export default {
   props: {
     inputWidth: {
       type: String,
-      default: "439px"
+      default: "400px"
     },
     format: {
       type: Array,
@@ -71,6 +73,10 @@ export default {
       type: String,
       default: "filename"
     },
+    autoUpload: {
+      type: Boolean,
+      default: true
+    },
     disabled: {
       type: Boolean,
       default: false
@@ -128,7 +134,7 @@ export default {
       const md5 = await fileMD5(file);
       this.headers["md5"] = md5;
 
-      return true;
+      return this.autoUpload;
     },
     upload(options) {
       return ajax(options);
@@ -155,6 +161,9 @@ export default {
         this.handleError(response);
       }
     },
+    handleProgress() {
+      this.loading = true;
+    },
     handleFormatError() {
       const content = "只支持文件格式为" + this.format.join("/");
       this.res = {

+ 73 - 0
src/components/base/CardRuleSelect.vue

@@ -0,0 +1,73 @@
+<template>
+  <el-select
+    v-model="selected"
+    class="card-rule-select"
+    placeholder="请选择"
+    :style="styles"
+    filterable
+    remote
+    :remote-method="search"
+    :clearable="clearable"
+    :disabled="disabled"
+    @change="select"
+  >
+    <el-option
+      v-for="item in optionList"
+      :key="item.id"
+      :value="item.id"
+      :label="item.name"
+    >
+      <span>{{ item.name }}</span>
+    </el-option>
+  </el-select>
+</template>
+
+<script>
+import { cardRuleQuery } from "../../modules/base/api";
+
+export default {
+  name: "card-rule-select",
+  props: {
+    disabled: { type: Boolean, default: false },
+    value: { type: [Number, String], default: "" },
+    styles: { type: String, default: "" },
+    clearable: { type: Boolean, default: true }
+  },
+  data() {
+    return {
+      optionList: [],
+      selected: ""
+    };
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(val) {
+        this.selected = val;
+      }
+    }
+  },
+  async created() {
+    this.search();
+  },
+  methods: {
+    async search(query) {
+      const res = await cardRuleQuery(query);
+      this.optionList = res;
+      this.optionList.unshift({
+        id: "GENENAL",
+        name: "全部通卡"
+      });
+    },
+    select() {
+      this.$emit("input", this.selected);
+      this.$emit(
+        "change",
+        this.optionList.find(item => item.id === this.selected)
+      );
+    }
+  }
+};
+</script>
+
+<style></style>

+ 69 - 0
src/components/base/CourseSelect.vue

@@ -0,0 +1,69 @@
+<template>
+  <el-select
+    v-model="selected"
+    class="course-select"
+    placeholder="请选择"
+    :style="styles"
+    filterable
+    remote
+    :remote-method="search"
+    :clearable="clearable"
+    :disabled="disabled"
+    @change="select"
+  >
+    <el-option
+      v-for="item in optionList"
+      :key="item.code"
+      :value="item.code"
+      :label="`${item.name}(${item.code})`"
+    >
+      <span>{{ `${item.name}(${item.code})` }}</span>
+    </el-option>
+  </el-select>
+</template>
+
+<script>
+import { courseQuery } from "../../modules/base/api";
+
+export default {
+  name: "course-select",
+  props: {
+    disabled: { type: Boolean, default: false },
+    value: { type: [Number, String], default: "" },
+    styles: { type: String, default: "" },
+    clearable: { type: Boolean, default: true }
+  },
+  data() {
+    return {
+      optionList: [],
+      selected: ""
+    };
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(val) {
+        this.selected = val;
+      }
+    }
+  },
+  async created() {
+    this.search();
+  },
+  methods: {
+    async search(query) {
+      const res = await courseQuery(query);
+      this.optionList = res;
+    },
+    select() {
+      this.$emit("input", this.selected);
+      this.$emit(
+        "change",
+        this.optionList.find(item => item.code === this.selected)
+      );
+    }
+  }
+};
+</script>
+
+<style></style>

+ 68 - 0
src/components/base/PaperNumberSelect.vue

@@ -0,0 +1,68 @@
+<template>
+  <el-select
+    v-model="selected"
+    class="paper-number-select"
+    placeholder="请选择"
+    :style="styles"
+    filterable
+    remote
+    :remote-method="search"
+    :clearable="clearable"
+    :disabled="disabled"
+    @change="select"
+  >
+    <el-option
+      v-for="(item, index) in optionList"
+      :key="index"
+      :value="item.pageNumber"
+    >
+      <span>{{ item.pageNumber }}</span>
+    </el-option>
+  </el-select>
+</template>
+
+<script>
+import { pageNumberQuery } from "../../modules/exam/api";
+
+export default {
+  name: "paper-number-select",
+  props: {
+    disabled: { type: Boolean, default: false },
+    value: { type: [Number, String], default: "" },
+    styles: { type: String, default: "" },
+    clearable: { type: Boolean, default: true }
+  },
+  data() {
+    return {
+      optionList: [],
+      selected: ""
+    };
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(val) {
+        this.selected = val;
+      }
+    }
+  },
+  async created() {
+    this.search();
+  },
+  methods: {
+    async search(query) {
+      const res = await pageNumberQuery(query);
+      this.optionList = res;
+    },
+    select() {
+      this.$emit("input", this.selected);
+      this.$emit(
+        "change",
+        this.optionList.find(item => item.id === this.selected)
+      );
+    }
+  }
+};
+</script>
+
+<style></style>

+ 78 - 0
src/components/base/QuestionTeacherSelect.vue

@@ -0,0 +1,78 @@
+<template>
+  <el-select
+    v-model="selected"
+    class="question-teacher-select"
+    placeholder="请选择"
+    :style="styles"
+    filterable
+    remote
+    :remote-method="search"
+    :clearable="clearable"
+    :disabled="disabled"
+    @change="select"
+  >
+    <el-option
+      v-for="item in optionList"
+      :key="item.id"
+      :value="item.id"
+      :label="item.name"
+    ></el-option>
+  </el-select>
+</template>
+
+<script>
+import { questionTeatherQuery } from "../../modules/exam/api";
+
+export default {
+  name: "question-teacher-select",
+  props: {
+    disabled: { type: Boolean, default: false },
+    value: { type: [Number, String], default: "" },
+    styles: { type: String, default: "" },
+    clearable: { type: Boolean, default: true },
+    courseCode: { type: String, default: "" }
+  },
+  data() {
+    return {
+      optionList: [],
+      selected: ""
+    };
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(val) {
+        this.selected = val;
+      }
+    },
+    courseCode(val, oldval) {
+      if (val !== oldval) {
+        this.search("");
+        this.$emit("input", "");
+        this.$emit("change", {});
+      }
+    }
+  },
+  async created() {
+    this.search();
+  },
+  methods: {
+    async search(query) {
+      const res = await questionTeatherQuery({
+        param: query,
+        courseCode: this.courseCode
+      });
+      this.optionList = res;
+    },
+    select() {
+      this.$emit("input", this.selected);
+      this.$emit(
+        "change",
+        this.optionList.find(item => item.id === this.selected)
+      );
+    }
+  }
+};
+</script>
+
+<style></style>

+ 15 - 4
src/constants/enumerate.js

@@ -57,9 +57,13 @@ export const REVOKE_STATUS = {
 };
 // 审核状态
 export const AUDITING_STATUS = {
-  0: "未审核",
-  1: "已审核",
-  9: "无"
+  NOT_AUDITED: "未审核",
+  AUDITED: "已审核"
+};
+// 审核结果
+export const AUDITING_RESULT = {
+  NOT_PASS: "未通过",
+  PASS: "通过"
 };
 
 export const CARD_BUSINESS_FIELDS = {
@@ -97,7 +101,7 @@ export const RESERVE_TYPE = {
 export const CARD_SOURCE_TYPE = {
   0: "选择已有答题卡",
   1: "自助创建",
-  2: "申请客服制卡" // 暂时隐藏
+  2: "申请客服制卡"
 };
 
 export const EXAM_NUMBER_STYLE = {
@@ -142,6 +146,13 @@ export const PRIVILEGE_TYPE = {
   F: "操作"
 };
 
+export const EXAM_TASK_STATUS = {
+  NEW: "新建任务",
+  STAGE: "暂存",
+  SUBMIT: "确认提交",
+  CANCEL: "撤回"
+};
+
 export const keepAliveRoutesPairs = [
   ["wait-task", ["wait-task-detail"]],
   ["done-task", ["done-task-detail"]],

+ 11 - 61
src/constants/navs.js

@@ -10,24 +10,12 @@ const navs = [
         children: [
           {
             title: "待办任务",
-            router: "WaitTask",
-            children: [
-              {
-                title: "任务详情",
-                router: "WaitTaskDetail"
-              }
-            ]
-          },
-          {
-            title: "已办任务",
-            router: "DoneTask",
-            children: [
-              {
-                title: "任务详情",
-                router: "DoneTaskDetail"
-              }
-            ]
+            router: "WaitTask"
           }
+          // {
+          //   title: "已办任务",
+          //   router: "DoneTask"
+          // }
         ]
       },
       {
@@ -37,47 +25,19 @@ const navs = [
         children: [
           {
             title: "命题任务管理",
-            router: "TaskManage",
-            children: [
-              {
-                title: "查看命题任务",
-                router: "TaskDetail"
-              },
-              {
-                title: "新建命题任务",
-                router: "TaskAdd"
-              },
-              {
-                title: "编辑命题任务",
-                router: "TaskEdit"
-              },
-              {
-                title: "批量新增命题任务",
-                router: "ExamBatchAdd"
-              }
-            ]
+            router: "ExamTaskManage"
           },
           {
             title: "入库申请",
-            router: "ApplyManage",
-            children: [
-              {
-                title: "申请入库",
-                router: "ApplyAdd"
-              },
-              {
-                title: "申请详情",
-                router: "ApplyDetail"
-              }
-            ]
+            router: "TaskApplyManage"
           },
           {
             title: "入库审核",
-            router: "CheckApply"
+            router: "TaskReviewManage"
           },
           {
             title: "卷库查询",
-            router: "PaperManage"
+            router: "TaskPaperManage"
           }
         ]
       },
@@ -88,7 +48,7 @@ const navs = [
         children: [
           {
             title: "印刷计划管理",
-            router: "PlanManage"
+            router: "PrintPlanManage"
           },
           {
             title: "考务数据导入",
@@ -104,7 +64,7 @@ const navs = [
           },
           {
             title: "印刷任务管理",
-            router: "PrintManage"
+            router: "PrintTaskManage"
           },
           {
             title: "印刷监控查询",
@@ -114,16 +74,6 @@ const navs = [
       }
     ]
   },
-  {
-    title: "教研分析",
-    name: "analyze",
-    children: [
-      {
-        title: "分数分析",
-        router: "ScoreAnalyze"
-      }
-    ]
-  },
   {
     title: "基础配置",
     name: "base",

+ 24 - 14
src/modules/base/api.js

@@ -10,15 +10,16 @@ export const updateUser = datas => {
 export const ableUser = ({ id, enable }) => {
   return $post("/api/sys/user/enable", { id, enable });
 };
-export const updatePwd = id => {
-  return $post("/api/sys/user/reset", { id });
+export const resetPwd = id => {
+  return $post("/api/sys/user/reset_password", { id });
 };
-export const userBindRoles = ({ id, roleIds }) => {
-  return $post("/api/sys/user/bindRoles", { id, roleIds });
+export const updatePwd = datas => {
+  return $post("/api/sys/user/update_password", datas);
 };
-export const userBoundRoles = userId => {
-  return $get("/api/sys/role/boundRoles", { userId });
+export const userBindRoles = ({ id, roleIds }) => {
+  return $post("/api/sys/user/bind_roles", { id, roleIds });
 };
+
 // role-manage
 export const roleListPage = datas => {
   return $get("/api/sys/role/list", datas);
@@ -32,8 +33,8 @@ export const updateRole = datas => {
 export const deleteRole = datas => {
   return $post("/api/sys/role/remove", datas);
 };
-export const roleBoundPrivileges = roleId => {
-  return $get("/api/sys/privilege/boundPrivileges", { roleId });
+export const userBoundRoles = userId => {
+  return $get("/api/sys/role/get_user_roles", { userId });
 };
 // menu-manage
 export const menuList = datas => {
@@ -45,6 +46,9 @@ export const updateMenu = datas => {
 export const deleteMenu = datas => {
   return $post("/api/sys/privilege/remove", datas);
 };
+export const roleBoundPrivileges = roleId => {
+  return $get("/api/sys/privilege/get_role_privileges", { roleId });
+};
 // organization-manage
 export const organizationList = datas => {
   return $get("/api/sys/org/list", datas);
@@ -62,23 +66,26 @@ export const deleteOrganization = id => {
 // --------------------------------->
 // common-rule
 export const commonRuleDetail = schoolId => {
-  return $get("/api/basic/examRule/list", { schoolId });
+  return $get("/api/basic/exam_rule/list", { schoolId });
 };
 export const saveCommonBusinessRule = datas => {
-  return $post("/api/basic/examRule/save", datas);
+  return $post("/api/basic/exam_rule/save", datas);
 };
 // card-rule
 export const cardRuleListPage = datas => {
-  return $get("/api/basic/cardRule/list", datas);
+  return $get("/api/basic/card_rule/list", datas);
+};
+export const cardRuleQuery = param => {
+  return $get("/api/basic/card_rule/query", { param });
 };
 export const cardRuleDetail = id => {
-  return $get("/api/basic/cardRule/getOne", { id });
+  return $get("/api/basic/card_rule/get_one", { id });
 };
 export const saveCardRule = datas => {
-  return $post("/api/basic/cardRule/save", datas);
+  return $post("/api/basic/card_rule/save", datas);
 };
 export const ableCardRule = ({ id, enable }) => {
-  return $post("/api/basic/cardRule/enable", { id, enable });
+  return $post("/api/basic/card_rule/enable", { id, enable });
 };
 // common-card-template / common-print-template / param-print-template
 export const templateListPage = datas => {
@@ -97,6 +104,9 @@ export const templateCategoryList = type => {
 export const courseListPage = datas => {
   return $get("/api/basic/course/list", datas);
 };
+export const courseQuery = param => {
+  return $get("/api/basic/course/query", { param });
+};
 export const courseList = datas => {
   return $get("/api/basic/course/list", datas);
 };

+ 5 - 5
src/modules/base/components/ModifyCardRule.vue

@@ -17,9 +17,9 @@
         :rules="rules"
         :model="modalForm"
       >
-        <el-form-item prop="cardRuleName" label="题卡规则名称:">
+        <el-form-item prop="name" label="题卡规则名称:">
           <el-input
-            v-model.trim="modalForm.cardRuleName"
+            v-model.trim="modalForm.name"
             placeholder="建议不超过30个字,规则名称不允许重复"
             style="width: 100%"
             clearable
@@ -158,7 +158,7 @@ import SelectOrgs from "./SelectOrgs";
 
 const initModalForm = {
   id: null,
-  cardRuleName: "",
+  name: "",
   remark: "",
   examNumberStyle: "",
   paperType: "",
@@ -207,7 +207,7 @@ export default {
       requiredFields: [],
       extendFields: [],
       rules: {
-        cardRuleName: [
+        name: [
           {
             required: true,
             message: "题卡规则名称不能超过30个字",
@@ -302,7 +302,7 @@ export default {
       if (val.id) {
         this.modalForm.examAbsent = Boolean(val.examAbsent);
         this.modalForm.writeSign = Boolean(val.writeSign);
-        this.modalForm.orgs = val.orgs.map(item => item.orgId);
+        this.modalForm.orgs = val.orgs.map(item => item.id);
       }
     },
     visibleChange() {

+ 8 - 8
src/modules/base/components/ModifyCourse.vue

@@ -17,16 +17,16 @@
       :key="modalForm.id"
       label-width="100px"
     >
-      <el-form-item prop="courseName" label="课程名称:">
+      <el-form-item prop="name" label="课程名称:">
         <el-input
-          v-model.trim="modalForm.courseName"
+          v-model.trim="modalForm.name"
           placeholder="请输入课程名称"
           clearable
         ></el-input>
       </el-form-item>
-      <el-form-item prop="courseCode" label="课程编码:">
+      <el-form-item prop="code" label="课程编码:">
         <el-input
-          v-model.trim="modalForm.courseCode"
+          v-model.trim="modalForm.code"
           placeholder="请输入课程编码"
           clearable
         ></el-input>
@@ -46,8 +46,8 @@ import { updateCourse } from "../api";
 
 const initModalForm = {
   id: "",
-  courseName: "",
-  courseCode: ""
+  name: "",
+  code: ""
 };
 
 export default {
@@ -74,7 +74,7 @@ export default {
       isSubmit: false,
       modalForm: { ...initModalForm },
       rules: {
-        courseName: [
+        name: [
           {
             required: true,
             pattern: /^[0-9a-zA-Z\u4E00-\u9FA5]{1,20}$/,
@@ -82,7 +82,7 @@ export default {
             trigger: "change"
           }
         ],
-        courseCode: [
+        code: [
           {
             required: true,
             pattern: /^[0-9a-zA-Z_-]{3,30}$/,

+ 9 - 8
src/modules/base/components/ModifyOrganization.vue

@@ -17,18 +17,18 @@
         :rules="rules"
         label-width="100px"
       >
-        <el-form-item prop="orgName" label="机构名称:">
+        <el-form-item prop="name" label="机构名称:">
           <el-input
             style="width:282px;"
-            v-model.trim="modalForm.orgName"
+            v-model.trim="modalForm.name"
             placeholder="请输入机构名称"
             clearable
           ></el-input>
         </el-form-item>
-        <el-form-item prop="orgCode" label="机构代码:">
+        <el-form-item prop="code" label="机构代码:">
           <el-input
             style="width:282px;"
-            v-model.trim="modalForm.orgCode"
+            v-model.trim="modalForm.code"
             placeholder="请输入机构代码"
             clearable
           ></el-input>
@@ -71,8 +71,8 @@ import { updateOrganization } from "../api";
 
 const initModalForm = {
   id: null,
-  orgName: "",
-  orgCode: "",
+  name: "",
+  code: "",
   parentId: null,
   parentName: "",
   enable: 1
@@ -103,7 +103,7 @@ export default {
       modalForm: {},
       ABLE_TYPE,
       rules: {
-        orgName: [
+        name: [
           {
             required: true,
             pattern: /^[0-9a-zA-Z\u4E00-\u9FA5]{1,30}$/,
@@ -111,7 +111,7 @@ export default {
             trigger: "change"
           }
         ],
-        orgCode: [
+        code: [
           {
             required: true,
             pattern: /^[0-9a-zA-Z_-]{3,30}$/,
@@ -145,6 +145,7 @@ export default {
       if (this.isSubmit) return;
       this.isSubmit = true;
       const datas = { ...this.modalForm };
+      datas.enable = !!datas.enable;
       const data = await updateOrganization(datas).catch(() => {});
       this.isSubmit = false;
       if (!data) return;

+ 4 - 4
src/modules/base/components/ModifyRole.vue

@@ -17,10 +17,10 @@
         :rules="rules"
         label-width="100px"
       >
-        <el-form-item prop="roleName" label="角色名称:">
+        <el-form-item prop="name" label="角色名称:">
           <el-input
             style="width:100%;"
-            v-model.trim="modalForm.roleName"
+            v-model.trim="modalForm.name"
             placeholder="请输入角色名称"
             clearable
           ></el-input>
@@ -55,7 +55,7 @@
 import { updateRole, roleBoundPrivileges } from "../api";
 const initModalForm = {
   id: null,
-  roleName: "",
+  name: "",
   privilegeIds: []
 };
 
@@ -91,7 +91,7 @@ export default {
         label: "name"
       },
       rules: {
-        roleName: [
+        name: [
           {
             required: true,
             pattern: /^[\u4E00-\u9FA5]{1,10}$/,

+ 5 - 5
src/modules/base/components/ModifyTemplate.vue

@@ -17,9 +17,9 @@
         :rules="rules"
         :model="modalForm"
       >
-        <el-form-item prop="templateName" label="模板名称:">
+        <el-form-item prop="name" label="模板名称:">
           <el-input
-            v-model.trim="modalForm.templateName"
+            v-model.trim="modalForm.name"
             placeholder="建议不超过30个字,规则名称不允许重复"
             style="width: 100%"
             clearable
@@ -90,7 +90,7 @@ import SelectOrgs from "./SelectOrgs";
 
 const initModalForm = {
   id: null,
-  templateName: "",
+  name: "",
   templateType: "GENERIC",
   remark: "",
   classify: "",
@@ -143,7 +143,7 @@ export default {
       ],
       categories: [],
       rules: {
-        templateName: [
+        name: [
           {
             required: true,
             message: "题卡规则名称不能超过30个字",
@@ -199,7 +199,7 @@ export default {
     initData(val) {
       this.modalForm = this.$objAssign(initModalForm, val);
       if (val.id) {
-        this.modalForm.orgs = val.orgs.map(item => item.orgId);
+        this.modalForm.orgs = val.orgs.map(item => item.id);
         this.getAttachment();
       }
     },

+ 12 - 17
src/modules/base/components/ModifyUser.vue

@@ -33,10 +33,10 @@
             clearable
           ></el-input>
         </el-form-item>
-        <el-form-item prop="phone" label="手机号:">
+        <el-form-item prop="mobileNumber" label="手机号:">
           <el-input
             style="width:100%;"
-            v-model.trim="modalForm.phone"
+            v-model.trim="modalForm.mobileNumber"
             placeholder="请输入手机号"
             clearable
           ></el-input>
@@ -51,8 +51,8 @@
             <el-option
               v-for="item in roles"
               :key="item.id"
-              :label="item.roleName"
               :value="item.id"
+              :label="item.name"
             >
             </el-option>
           </el-select>
@@ -71,9 +71,9 @@
           >
             <el-option
               v-for="item in courses"
-              :key="item.id"
+              :key="item.code"
+              :value="item.code"
               :label="item.name"
-              :value="item.id"
             >
             </el-option>
           </el-select>
@@ -90,7 +90,7 @@
 </template>
 
 <script>
-import { updateUser, courseList } from "../api";
+import { updateUser, courseQuery } from "../api";
 import { logout } from "../../login/api";
 import { phone } from "@/plugins/formRules";
 
@@ -98,7 +98,7 @@ const initModalForm = {
   id: "",
   loginName: "",
   realName: "",
-  phone: "",
+  mobileNumber: "",
   roleIds: "",
   courseCodes: []
 };
@@ -141,7 +141,7 @@ export default {
       isSubmit: false,
       modalForm: {},
       rules: {
-        phone,
+        mobileNumber: phone,
         name: [
           {
             required: true,
@@ -182,8 +182,8 @@ export default {
     initData(val) {
       if (val.id) {
         this.modalForm = this.$objAssign(initModalForm, val);
-        this.modalForm.roleIds = val.roles.map(item => item.roleId);
-        this.modalForm.courseCodes = val.courses.map(item => item.courseCode);
+        this.modalForm.roleIds = val.roles.map(item => item.id);
+        this.modalForm.courseCodes = val.courses.map(item => item.code);
       } else {
         this.modalForm = { ...initModalForm };
       }
@@ -198,13 +198,8 @@ export default {
       this.modalIsShow = true;
     },
     async getCourseList() {
-      const data = await courseList();
-      this.courses = data.records.map(course => {
-        return {
-          id: course.courseCode,
-          name: `${course.courseName}(${course.courseCode})`
-        };
-      });
+      const data = await courseQuery();
+      this.courses = data;
     },
     async submit() {
       const valid = await this.$refs.modalFormComp.validate().catch(() => {});

+ 1 - 1
src/modules/base/components/SelectOrgs.vue

@@ -43,7 +43,7 @@ export default {
       orgs: [],
       isTree: false,
       defaultProps: {
-        label: "orgName"
+        label: "name"
       }
     };
   },

+ 12 - 9
src/modules/base/views/CardRule.vue

@@ -5,7 +5,7 @@
         <el-form-item label="规则名称:">
           <el-input
             style="width: 200px;"
-            v-model.trim="filter.cardRuleName"
+            v-model.trim="filter.name"
             placeholder="请输入内容"
             clearable
           ></el-input>
@@ -14,6 +14,7 @@
           <el-date-picker
             v-model="filter.createTime"
             type="date"
+            value-format="timestamp"
             placeholder="选择日期时间"
           >
           </el-date-picker>
@@ -54,10 +55,7 @@
           align="center"
           :index="indexMethod"
         ></el-table-column>
-        <el-table-column
-          prop="cardRuleName"
-          label="题卡规则名称"
-        ></el-table-column>
+        <el-table-column prop="name" label="题卡规则名称"></el-table-column>
         <el-table-column prop="orgs" label="适用学院">
           <template slot-scope="scope">
             {{ scope.row.orgs | orgsFilter }}
@@ -69,7 +67,11 @@
             {{ scope.row.enable | enableFilter }}
           </template>
         </el-table-column>
-        <el-table-column prop="createTime" label="创建时间"></el-table-column>
+        <el-table-column prop="createTime" label="创建时间">
+          <span slot-scope="scope">{{
+            scope.row.createTime | timestampFilter
+          }}</span>
+        </el-table-column>
         <el-table-column
           class-name="action-column"
           label="操作"
@@ -141,8 +143,8 @@ export default {
     return {
       filter: {
         enable: null,
-        cardRuleName: "",
-        createTime: ""
+        name: "",
+        createTime: null
       },
       current: 1,
       size: this.GLOBAL.pageSize,
@@ -168,6 +170,7 @@ export default {
         pageNumber: this.current,
         pageSize: this.size
       };
+      datas.enable = !!datas.enable;
       const data = await cardRuleListPage(datas);
       this.rules = data.records;
       this.total = data.total;
@@ -202,7 +205,7 @@ export default {
       this.$refs.ModifyCardRule.open();
     },
     async toEnable(row) {
-      const enable = Number(!row.enable);
+      const enable = !row.enable;
       await ableCardRule({
         id: row.id,
         enable

+ 1 - 1
src/modules/base/views/CommonRule.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="common-rule">
-    <div style="margin-bottom: 20px;">
+    <div class="mb-4">
       <el-button
         v-for="item in menus"
         :key="item.id"

+ 6 - 6
src/modules/base/views/CourseManage.vue

@@ -5,7 +5,7 @@
         <el-form-item label="课程名称:">
           <el-input
             style="width: 142px;"
-            v-model.trim="filter.courseName"
+            v-model.trim="filter.name"
             placeholder="请输入内容"
             clearable
           ></el-input>
@@ -13,7 +13,7 @@
         <el-form-item label="课程编码:">
           <el-input
             style="width: 142px;"
-            v-model.trim="filter.courseCode"
+            v-model.trim="filter.code"
             placeholder="请输入内容"
             clearable
           ></el-input>
@@ -32,8 +32,8 @@
     </div>
     <div class="part-box">
       <el-table ref="TableList" :data="courses" border stripe>
-        <el-table-column prop="courseName" label="课程名称"></el-table-column>
-        <el-table-column prop="courseCode" label="课程编码"></el-table-column>
+        <el-table-column prop="name" label="课程名称"></el-table-column>
+        <el-table-column prop="code" label="课程编码"></el-table-column>
         <el-table-column label="操作" align="center" width="120px">
           <template slot-scope="scope">
             <el-button
@@ -84,8 +84,8 @@ export default {
   data() {
     return {
       filter: {
-        courseName: "",
-        courseCode: ""
+        name: "",
+        code: ""
       },
       current: 1,
       size: this.GLOBAL.pageSize,

+ 4 - 4
src/modules/base/views/OrganizationManage.vue

@@ -63,7 +63,7 @@ export default {
       orgs: [],
       curOrg: {},
       defaultProps: {
-        label: "orgName"
+        label: "name"
       }
     };
   },
@@ -81,12 +81,12 @@ export default {
     edit(node, data) {
       this.curOrg = {
         ...data,
-        parentName: data.parentId && node.parent.data.orgName
+        parentName: data.parentId && node.parent.data.name
       };
       this.$refs.ModifyOrganization.open();
     },
     remove(node, data) {
-      this.$confirm(`确定要删除【${data.orgName}】吗?`, "提示", {
+      this.$confirm(`确定要删除【${data.name}】吗?`, "提示", {
         cancelButtonClass: "el-button--danger is-plain",
         confirmButtonClass: "el-button--primary",
         type: "warning"
@@ -102,7 +102,7 @@ export default {
       });
     },
     append(data) {
-      this.curOrg = { parentId: data.id, parentName: data.orgName };
+      this.curOrg = { parentId: data.id, parentName: data.name };
       this.$refs.ModifyOrganization.open();
     }
   }

+ 10 - 5
src/modules/base/views/RoleManage.vue

@@ -5,7 +5,7 @@
         <el-form-item label="角色名称:">
           <el-input
             style="width: 200px;"
-            v-model.trim="filter.roleName"
+            v-model.trim="filter.name"
             placeholder="请输入内容"
             clearable
           ></el-input>
@@ -46,13 +46,17 @@
           align="center"
           :index="indexMethod"
         ></el-table-column>
-        <el-table-column prop="roleName" label="角色名称"></el-table-column>
+        <el-table-column prop="name" label="角色名称"></el-table-column>
         <el-table-column prop="enable" label="启用/禁用">
           <template slot-scope="scope">
             {{ scope.row.enable | enableFilter }}
           </template>
         </el-table-column>
-        <el-table-column prop="createTime" label="创建时间"></el-table-column>
+        <el-table-column prop="createTime" label="创建时间">
+          <span slot-scope="scope">{{
+            scope.row.createTime | timestampFilter
+          }}</span>
+        </el-table-column>
         <el-table-column
           class-name="action-column"
           label="操作"
@@ -133,7 +137,7 @@ export default {
     return {
       filter: {
         enable: null,
-        roleName: ""
+        name: ""
       },
       current: 1,
       size: this.GLOBAL.pageSize,
@@ -159,6 +163,7 @@ export default {
         pageNumber: this.current,
         pageSize: this.size
       };
+      datas.enable = !!datas.enable;
       const data = await roleListPage(datas);
       this.roles = data.records;
       this.total = data.total;
@@ -187,7 +192,7 @@ export default {
       });
     },
     async toEnable(row) {
-      const enable = Number(!row.enable);
+      const enable = !row.enable;
       await ableRole({
         id: row.id,
         enable

+ 12 - 6
src/modules/base/views/TemplateManage.vue

@@ -5,7 +5,7 @@
         <el-form-item label="模板名称:">
           <el-input
             style="width: 200px;"
-            v-model.trim="filter.templateName"
+            v-model.trim="filter.name"
             placeholder="请输入内容"
             clearable
           ></el-input>
@@ -14,6 +14,7 @@
           <el-date-picker
             v-model="filter.createTime"
             type="date"
+            value-format="timestamp"
             placeholder="选择日期时间"
           >
           </el-date-picker>
@@ -54,7 +55,7 @@
           align="center"
           :index="indexMethod"
         ></el-table-column>
-        <el-table-column prop="templateName" label="模板名称"></el-table-column>
+        <el-table-column prop="name" label="模板名称"></el-table-column>
         <el-table-column
           prop="classify"
           label="分类"
@@ -75,7 +76,11 @@
             {{ scope.row.enable | enableFilter }}
           </template>
         </el-table-column>
-        <el-table-column prop="createTime" label="创建时间"></el-table-column>
+        <el-table-column prop="createTime" label="创建时间">
+          <span slot-scope="scope">{{
+            scope.row.createTime | timestampFilter
+          }}</span>
+        </el-table-column>
         <el-table-column
           class-name="action-column"
           label="操作"
@@ -157,8 +162,8 @@ export default {
     return {
       filter: {
         enable: null,
-        templateName: "",
-        createTime: ""
+        name: "",
+        createTime: null
       },
       current: 1,
       size: this.GLOBAL.pageSize,
@@ -180,6 +185,7 @@ export default {
         pageNumber: this.current,
         pageSize: this.size
       };
+      datas.enable = !!datas.enable;
       const data = await templateListPage(datas);
       this.templates = data.records;
       this.total = data.total;
@@ -204,7 +210,7 @@ export default {
       this.$refs.ModifyTemplate.open();
     },
     async toEnable(row) {
-      const enable = Number(!row.enable);
+      const enable = !row.enable;
       await ableTemplate({
         id: row.id,
         enable

+ 7 - 6
src/modules/base/views/UserManage.vue

@@ -19,9 +19,9 @@
           >
             <el-option
               v-for="item in roles"
-              :key="item.roleId"
-              :value="item.roleId"
-              :label="item.roleName"
+              :key="item.id"
+              :value="item.id"
+              :label="item.name"
             ></el-option>
           </el-select>
         </el-form-item>
@@ -83,7 +83,7 @@
             {{ scope.row.roles | rolesFilter }}
           </template>
         </el-table-column>
-        <el-table-column prop="phone" label="手机号"></el-table-column>
+        <el-table-column prop="mobileNumber" label="手机号"></el-table-column>
         <el-table-column prop="courses" label="课程名称(编码)">
           <template slot-scope="scope">
             {{ scope.row.courses | coursesFilter }}
@@ -193,7 +193,7 @@ export default {
   },
   methods: {
     async getRoleList() {
-      const data = await roleListPage();
+      const data = await roleListPage({ pageNumber: 1, pageSize: 100 });
       this.roles = data.records;
     },
     async getList() {
@@ -202,6 +202,7 @@ export default {
         pageNumber: this.current,
         pageSize: this.size
       };
+      datas.enable = !!datas.enable;
       const data = await userListPage(datas);
       this.users = data.records;
       this.total = data.total;
@@ -211,7 +212,7 @@ export default {
       this.getList();
     },
     async toEnable(row) {
-      const enable = Number(!row.enable);
+      const enable = !row.enable;
       await ableUser({
         id: row.id,
         enable

+ 65 - 0
src/modules/exam/api.js

@@ -0,0 +1,65 @@
+import { $get, $post } from "@/plugins/axios";
+
+// other
+// 试卷编号模糊查询
+export const pageNumberQuery = param => {
+  return $get("/api/exam/task/page_number_query", { param });
+};
+// 命题老师模糊查询
+export const questionTeatherQuery = ({ param, courseCode }) => {
+  return $get("/api/exam/task/user_query", { param, courseCode });
+};
+
+// exam-task-manage
+export const examTaskListPage = datas => {
+  return $get("/api/exam/task/list", datas);
+};
+export const updateExamTask = datas => {
+  return $post("/api/exam/task/save", datas);
+};
+export const ableExamTask = ({ id, enable }) => {
+  return $post("/api/exam/task/enable", { id, enable });
+};
+export const batchAddExamTask = datas => {
+  return $post("/api/exam/task/save_batch", datas);
+};
+export const taskApplyAuditHistory = id => {
+  return $get("/api/exam/task/review_list", { id });
+};
+
+// task-apply-manage
+export const taskApplyListPage = datas => {
+  return $get("/api/exam/task_apply/list", datas);
+};
+export const taskApplyDetail = id => {
+  return $get("/api/exam/task_apply/get_one", { id });
+};
+export const updateTaskApply = datas => {
+  return $post("/api/exam/task_apply/save", datas);
+};
+export const cancelOrRestartTaskApply = ({ id, status }) => {
+  return $post("/api/exam/task_apply/status", { id, status });
+};
+
+// task-review-manage
+export const taskReviewListPage = datas => {
+  return $get("/api/exam/task_review/list", datas);
+};
+export const updateTaskReview = datas => {
+  return $post("/api/exam/task_review/save", datas);
+};
+export const batchUpdateTaskReview = datas => {
+  return $post("/api/exam/task_review/save_batch", datas);
+};
+// task-paper-manage
+export const taskPaperListPage = datas => {
+  return $get("/api/exam/task_paper/list", datas);
+};
+export const ableTaskPaper = ({ id, enable }) => {
+  return $post("/api/exam/task_paper/enable", { id, enable });
+};
+
+// card
+export const cardList = datas => {
+  return $get("/api/card/list", datas);
+};

+ 63 - 0
src/modules/exam/components/ApplyAuditHistory.vue

@@ -0,0 +1,63 @@
+<template>
+  <div class="apply-audit-history">
+    <div v-if="auditHistory.length">
+      <p class="tips-info mb-2" v-if="latestHistory">
+        <span>审核结果:</span>
+        <span
+          :class="
+            latestHistory.reviewStatus === 'PASS'
+              ? 'color-dark'
+              : 'color-danger'
+          "
+          >{{ latestHistory.reviewStatus | reviewStatusFilter }}</span
+        >
+      </p>
+      <el-table :data="auditHistory" border stripe>
+        <el-table-column prop="operateTime" label="审核日期">
+          <span slot-scope="scope"
+            >{{ scope.row.operateTime | timestampFilter }}
+          </span>
+        </el-table-column>
+        <el-table-column prop="reviewStatus" label="审核结果">
+          <template slot-scope="scope">
+            {{ scope.row.reviewStatus | reviewStatusFilter }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="reason" label="审核意见"></el-table-column>
+        <el-table-column prop="operateName" label="审核人"></el-table-column>
+      </el-table>
+    </div>
+    <div class="history-none" v-else>
+      <p>暂无数据</p>
+    </div>
+  </div>
+</template>
+
+<script>
+import { taskApplyAuditHistory } from "../api";
+
+export default {
+  name: "apply-audit-history",
+  props: {
+    examTaskId: {
+      type: String,
+      default: ""
+    }
+  },
+  data() {
+    return {
+      auditHistory: [],
+      latestHistory: null
+    };
+  },
+  mounted() {
+    this.getData();
+  },
+  methods: {
+    async getData() {
+      this.auditHistory = await taskApplyAuditHistory(this.examTaskId);
+      this.latestHistory = this.auditHistory[0];
+    }
+  }
+};
+</script>

+ 455 - 0
src/modules/exam/components/ApplyContent.vue

@@ -0,0 +1,455 @@
+<template>
+  <div class="apply-content task-detail">
+    <p class="tips-info tips-dark mb-2">
+      提示:多卷型试卷由于会绑定一个答题卡模板,因此试卷结构必须相同。多卷型试卷之间客观题要求试题内容相同,可允许大题内的小题题序不同。
+    </p>
+    <div class="task-body">
+      <div v-if="IS_APPLY" class="mb-2 text-right">
+        <el-button @click="addAtachment" icon="icon icon-plus-act"
+          >增加卷型</el-button
+        >
+      </div>
+      <table class="table">
+        <tr>
+          <th>试卷类型</th>
+          <th>试卷文件</th>
+          <th>答题卡</th>
+          <th v-if="IS_APPLY">操作</th>
+        </tr>
+        <tr v-for="(attachment, index) in paperAttachments" :key="index">
+          <td>{{ attachment.name }}卷</td>
+          <td class="td-link">
+            <span
+              v-if="IS_APPLY"
+              @click="toUpload(attachment)"
+              title="点击上传试卷"
+            >
+              <i
+                :class="[
+                  'icon',
+                  attachment.attachmentId ? 'icon-files-act' : 'icon-files'
+                ]"
+              ></i
+              >{{
+                attachment.attachmentId
+                  ? attachment.filename
+                  : "点击上传试卷文件"
+              }}
+            </span>
+            <span
+              v-else
+              @click="downloadPaper(attachment)"
+              title="点击下载试卷"
+            >
+              <i class="icon icon-download-act"></i>{{ attachment.filename }}
+            </span>
+          </td>
+          <td
+            class="td-link"
+            :rowspan="paperAttachments.length"
+            v-if="index === 0"
+          >
+            <span @click="toCreateCard"
+              ><i class="icon icon-plus-act"></i>{{ cardTodoName }}</span
+            >
+          </td>
+          <td v-if="IS_APPLY">
+            <el-button
+              class="btn-table-icon"
+              type="text"
+              icon="icon icon-delete"
+              title="删除"
+              @click="deleteAttachment(index)"
+            ></el-button>
+          </td>
+        </tr>
+      </table>
+
+      <h4 class="mb-2">备注说明:</h4>
+      <el-input
+        class="mb-2"
+        v-model.trim="curTaskApply.remark"
+        type="textarea"
+        resize="none"
+        :rows="2"
+        :maxlength="100"
+        clearable
+        show-word-limit
+        placeholder="建议不超过100个字"
+        :disabled="!IS_APPLY"
+      ></el-input>
+
+      <h4 class="mb-2">上传入库审核表(最多4张)</h4>
+      <div class="image-list">
+        <div
+          class="image-item"
+          v-for="(img, index) in paperConfirmAttachments"
+          :key="index"
+        >
+          <img :src="img.path" :alt="img.filename" />
+          <div v-if="IS_APPLY" class="image-delete">
+            <i
+              class="el-icon-delete-solid"
+              @click="deletePaperConfirmAttachment(index)"
+            ></i>
+          </div>
+        </div>
+        <div
+          v-if="paperConfirmAttachments.length < 4 && IS_APPLY"
+          class="image-item image-add"
+          title="上传入库审核表"
+          @click="toUploadPaperConfirm"
+        >
+          <i class="el-icon-plus"></i>
+        </div>
+      </div>
+
+      <!-- audit -->
+      <h4 v-if="IS_AUDIT" class="mb-2">审核意见:</h4>
+      <el-input
+        v-if="IS_AUDIT"
+        class="mb-2"
+        v-model.trim="reason"
+        type="textarea"
+        resize="none"
+        :rows="5"
+        :maxlength="1000"
+        clearable
+        show-word-limit
+        placeholder="建议不超过1000个字"
+      ></el-input>
+    </div>
+    <div class="task-action">
+      <el-button
+        v-if="IS_APPLY"
+        type="primary"
+        :disabled="isSubmit"
+        @click="submit"
+        >确认提交</el-button
+      >
+      <el-button
+        v-if="IS_APPLY"
+        type="primary"
+        :disabled="isSubmit"
+        style="width:88px;"
+        @click="toSave"
+        >暂存</el-button
+      >
+      <el-button
+        v-if="IS_AUDIT"
+        type="primary"
+        :disabled="isSubmit"
+        @click="toAuditApply('PASS')"
+        >通过</el-button
+      >
+      <el-button
+        v-if="IS_AUDIT"
+        type="danger"
+        plain
+        @click="toAuditApply('NOT_PASS')"
+        >不通过</el-button
+      >
+      <el-button type="danger" @click="cancel" plain>取消</el-button>
+    </div>
+
+    <!-- upload-paper-dialog -->
+    <upload-paper-dialog
+      :paper-attachment="curAttachment"
+      :upload-type="curUploadType"
+      @confirm="uploadConfirm"
+      ref="UploadPaperDialog"
+    ></upload-paper-dialog>
+    <!-- card-option-dialog -->
+    <card-option-dialog
+      :data="task"
+      ref="CardOptionDialog"
+      @upload-sample-over="initData"
+      @draft-task="silentSave"
+      @confirm="cardConfirm"
+    ></card-option-dialog>
+  </div>
+</template>
+
+<script>
+import UploadPaperDialog from "./UploadPaperDialog";
+import CardOptionDialog from "./CardOptionDialog";
+import { taskApplyDetail, updateTaskApply, updateTaskReview } from "../api";
+
+const initTaskApply = {
+  id: "",
+  examTaskId: "",
+  paperType: "A",
+  paperAttachmentIds: "",
+  paperConfirmAttachmentIds: "",
+  cardId: "",
+  cardSource: "",
+  refCardId: "",
+  remark: ""
+};
+
+export default {
+  name: "apply-content",
+  components: { UploadPaperDialog, CardOptionDialog },
+  props: {
+    examTask: {
+      type: Object,
+      default() {
+        return {};
+      }
+    },
+    viewType: {
+      type: String,
+      default: "APPLY",
+      validator: val => ["APPLY", "VIEW", "AUDIT"].includes(val)
+    }
+  },
+  data() {
+    return {
+      isSubmit: false,
+      curTaskApply: { ...initTaskApply },
+      paperConfirmAttachmentId: { attachmentId: "", filename: "", path: "" },
+      paperAttachments: [],
+      paperConfirmAttachments: [],
+      curAttachment: {},
+      curUploadType: "paper",
+      attachmentLimitCount: 26,
+      abc: "abcdefghijklmnopqrstuvwxyz".toUpperCase(),
+      task: {},
+      reason: ""
+    };
+  },
+  computed: {
+    IS_APPLY() {
+      return this.viewType === "APPLY";
+    },
+    IS_VIEW() {
+      return this.viewType === "VIEW";
+    },
+    IS_AUDIT() {
+      return this.viewType === "AUDIT";
+    },
+    cardTodoName() {
+      let name = "创建答题卡";
+      if (this.curTaskApply.cardId) {
+        if (this.curTaskApply.cardSource === 0) {
+          name = "选择题卡";
+        } else if (this.curTaskApply.cardSource === 1) {
+          name = "编辑题卡";
+        } else {
+          // 已经审核的题卡可以自行编辑,未审核的题卡只能查看
+          // name = this.curTaskApply.auditingStatus ? "编辑题卡" : "查看题卡";
+        }
+      }
+      return name;
+    }
+  },
+  mounted() {
+    this.initData();
+  },
+  methods: {
+    async initData() {
+      const data = await taskApplyDetail(this.examTask.id);
+      this.curTaskApply = this.$objAssign(initTaskApply, data);
+      this.paperAttachments = data.paperAttachmentIds
+        ? JSON.parse(data.paperAttachmentIds)
+        : [];
+      this.paperConfirmAttachments = data.paperConfirmAttachmentIds
+        ? JSON.parse(data.paperConfirmAttachmentIds)
+        : [];
+    },
+    addAtachment() {
+      if (this.paperAttachments.length >= this.attachmentLimitCount) return;
+
+      const newAttachment = {
+        name: this.abc[this.paperAttachments.length],
+        attachmentId: "",
+        filename: "",
+        pages: 0
+      };
+      this.paperAttachments.push(newAttachment);
+    },
+    deleteAttachment(index) {
+      if (this.paperAttachments.length <= 1) {
+        this.$message.error("试卷类型数量不得少于1");
+        return;
+      }
+      this.paperAttachments.splice(index, 1);
+      this.paperAttachments.forEach((item, itemIndex) => {
+        item.name = this.abc[itemIndex];
+      });
+    },
+    toUpload(attachment) {
+      this.curUploadType = "paper";
+      this.curAttachment = {
+        ...attachment
+      };
+      this.$refs.UploadPaperDialog.open();
+    },
+    toUploadPaperConfirm() {
+      if (this.paperConfirmAttachments.length >= 4) return;
+      this.curUploadType = "paperConfirm";
+      this.curAttachment = {
+        ...this.paperConfirmAttachmentId
+      };
+      this.$refs.UploadPaperDialog.open();
+    },
+    uploadConfirm(attachment, uploadType) {
+      if (uploadType === "paper") {
+        const index = this.paperAttachments.findIndex(
+          item => item.name === attachment.name
+        );
+        this.paperAttachments.splice(index, 1, { ...attachment });
+      } else {
+        this.paperConfirmAttachments.push(attachment);
+      }
+    },
+    deletePaperConfirmAttachment(index) {
+      this.paperConfirmAttachments.splice(index, 1);
+    },
+    toCreateCard() {
+      this.task = {
+        ...this.getTaskData(),
+        auditStatus: this.examTask.auditStatus,
+        examTaskId: this.examTask.id,
+        cardRuleId: this.examTask.cardRuleId
+      };
+      if (this.curTaskApply.cardId) {
+        if (this.curTaskApply.cardSource === 0) {
+          this.$refs.CardOptionDialog.open();
+        } else if (this.curTaskApply.cardSource === 1) {
+          this.$router.push({
+            name: "CardDesign",
+            params: {
+              cardId: this.curTaskApply.cardId
+            }
+          });
+        } else {
+          if (this.curTaskApply.auditingStatus) {
+            this.$router.push({
+              name: "CardDesign",
+              params: {
+                cardId: this.curTaskApply.cardId
+              }
+            });
+          } else {
+            window.open(
+              this.getRouterPath({
+                name: "CardPreview",
+                params: {
+                  cardId: this.curTaskApply.cardId,
+                  viewType: "view"
+                }
+              })
+            );
+          }
+        }
+        return;
+      }
+      this.$refs.CardOptionDialog.open();
+    },
+    cancel() {
+      this.$emit("cancel");
+    },
+    downloadPaper(attachment) {
+      window.open(attachment.path);
+    },
+    cardConfirm(data) {
+      this.curTaskApply = this.$objAssign(this.curTaskApply, data);
+    },
+    getTaskData() {
+      let data = { ...this.curTaskApply };
+      data.paperType = this.paperAttachments.map(item => item.name).join();
+      data.paperAttachmentIds = JSON.stringify(this.paperAttachments);
+      data.paperConfirmAttachmentIds = JSON.stringify(
+        this.paperConfirmAttachments
+      );
+      console.log(data);
+      return data;
+    },
+    checkDataValid() {
+      const attachmentValid = !this.paperAttachments.some(
+        item => !item.attachmentId
+      );
+      if (!attachmentValid) {
+        this.$message.error("请完成试卷文件上传!");
+        return;
+      }
+      if (!this.paperConfirmAttachments.length) {
+        this.$message.error("请上传入库审核表!");
+        return;
+      }
+
+      if (!this.task.cardId && !this.task.refCardId) {
+        this.$message.error("请选择题卡创建方式!");
+        return;
+      }
+      return true;
+    },
+    async toSave() {
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const datas = this.getTaskData();
+      datas.operateType = "STAGE";
+      const data = await updateTaskApply(datas).catch(() => {});
+      this.isSubmit = false;
+      if (!data) return;
+
+      this.$message.success("保存成功!");
+    },
+    async silentSave() {
+      const datas = this.getTaskData();
+      datas.operateType = "STAGE";
+      await updateTaskApply(datas).catch(() => {});
+    },
+    async submit() {
+      if (!this.checkDataValid()) return;
+
+      this.$confirm(
+        "任务确定提交后,则不可更改试卷及答题卡内容,确定提交该任务?",
+        "提示",
+        {
+          cancelButtonClass: "el-button--danger is-plain",
+          confirmButtonClass: "el-button--primary",
+          type: "warning"
+        }
+      ).then(async () => {
+        const datas = this.getTaskData();
+        datas.operateType = "SUBMIT";
+        const data = await updateTaskApply(datas).catch(() => {});
+        if (!data) return;
+        this.$message.success("提交成功!");
+        this.goback();
+        this.$emit("modified");
+      });
+    },
+    toAuditApply(type) {
+      if (type === "PASS") {
+        this.auditApply(type);
+      } else {
+        if (!this.reason) {
+          this.$message.error("请输入审核意见!");
+          return;
+        }
+
+        this.$confirm("确定不通过该申请吗?", "提示", {
+          cancelButtonClass: "el-button--danger is-plain",
+          confirmButtonClass: "el-button--primary",
+          type: "warning"
+        }).then(() => {
+          this.auditApply(type);
+        });
+      }
+    },
+    async auditApply(type) {
+      const data = await updateTaskReview({
+        reviewStatus: type,
+        examTaskId: this.examTask.id,
+        reason: this.reason
+      }).catch(() => {});
+      if (!data) return;
+      this.$message.success("审核成功!");
+      this.$emit("modified");
+    }
+  }
+};
+</script>

+ 237 - 0
src/modules/exam/components/BatchAddExamTask.vue

@@ -0,0 +1,237 @@
+<template>
+  <el-dialog
+    class="batch-add-exam-task"
+    :visible.sync="modalIsShow"
+    title="批量新建命题任务"
+    top="10vh"
+    width="900px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @open="visibleChange"
+  >
+    <div class=" part-box part-box-border part-box-pad">
+      <el-form
+        ref="modalFormComp"
+        :model="modalForm"
+        :rules="rules"
+        label-width="120px"
+      >
+        <el-form-item prop="batchNo" label="命题任务表:">
+          <upload-file-view
+            :upload-url="uploadExamTaskUrl"
+            @upload-error="uplaodError"
+            @upload-success="uploadExamTaskSuccess"
+            ref="ExamTaskUploadFileView"
+            v-if="modalIsShow"
+          ></upload-file-view>
+          <el-button
+            type="warning"
+            icon="icon icon-download"
+            style="margin-left:10px;"
+          >
+            <a :href="downloadUrl" download="命题任务导入模板.xlsx">模板下载</a>
+          </el-button>
+        </el-form-item>
+        <el-form-item prop="startTime" label="命题时间:">
+          <el-date-picker
+            v-model="createTime"
+            type="datetimerange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            value-format="timestamp"
+            align="right"
+            unlink-panels
+            @change="dateChange"
+          >
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item prop="cardRuleId" label="题卡规则:">
+          <card-rule-select
+            ref="CardRuleSelect"
+            v-model.trim="modalForm.cardRuleId"
+            placeholder="请选择"
+            clearable
+          ></card-rule-select>
+          <p class="tips-info">
+            说明:若选择全部通卡,则命题老师只能选择通卡,若选择题卡规则,则专卡和通卡均可选择
+          </p>
+        </el-form-item>
+        <el-form-item label="指派命题老师:">
+          <el-table class="el-table--noback" :data="tasks" border>
+            <el-table-column prop="courseName" label="科目(代码)">
+              <span slot-scope="scope">
+                {{ scope.row.courseName }}({{ scope.row.courseCode }})
+              </span>
+            </el-table-column>
+            <el-table-column prop="paperNumber" label="试卷编号">
+              <template slot-scope="scope">
+                <el-input
+                  v-model.trim="scope.row.paperNumber"
+                  placeholder="请输入"
+                  clearable
+                ></el-input>
+              </template>
+            </el-table-column>
+            <el-table-column prop="specialty" label="适用专业(方向)">
+              <template slot-scope="scope">
+                <el-input
+                  v-model.trim="scope.row.specialty"
+                  placeholder="请输入"
+                  clearable
+                ></el-input>
+              </template>
+            </el-table-column>
+            <el-table-column label="命题老师" width="180px">
+              <template slot-scope="scope">
+                <el-select
+                  v-model="scope.row.userId"
+                  size="small"
+                  style="width: 150px;"
+                  placeholder="请选择"
+                >
+                  <el-option
+                    v-for="user in scope.row.users"
+                    :key="user.userId"
+                    :value="user.userId"
+                    :label="user.userName"
+                  ></el-option>
+                </el-select>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div slot="footer">
+      <el-button type="primary" :disabled="isSubmit" @click="save"
+        >确认</el-button
+      >
+      <el-button type="danger" @click="cancel" plain>取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { batchAddExamTask } from "../api";
+import UploadFileView from "@/components/UploadFileView";
+import CardRuleSelect from "../../../components/base/CardRuleSelect";
+
+const initModalForm = {
+  cardRuleId: "",
+  startTime: "",
+  endTime: "",
+  batchNo: "",
+  userIds: []
+};
+
+export default {
+  name: "batch-add-exam-task",
+  components: { UploadFileView, CardRuleSelect },
+  data() {
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      modalForm: { ...initModalForm },
+      createTime: [],
+      tasks: [],
+      rules: {
+        batchNo: [
+          {
+            required: true,
+            message: "请上传考试计划表",
+            trigger: "change"
+          }
+        ],
+        startTime: [
+          {
+            required: true,
+            message: "请设置命题时间",
+            trigger: "change"
+          }
+        ],
+        cardRuleId: [
+          {
+            required: true,
+            message: "请选择题卡规则",
+            trigger: "change"
+          }
+        ]
+      },
+      downloadUrl: this.GLOBAL.domain + "/temps/考试计划导入模板.xlsx",
+      // import
+      uploadExamTaskUrl: "/api/exam/task/import"
+    };
+  },
+  methods: {
+    visibleChange() {
+      this.modalForm = { ...initModalForm };
+      this.tasks = [];
+      this.createTime = [];
+
+      this.$nextTick(() => {
+        this.$refs.modalFormComp.clearValidate();
+      });
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    dateChange() {
+      if (this.createTime) {
+        this.modalForm.startTime = this.createTime[0];
+        this.modalForm.endTime = this.createTime[1];
+      } else {
+        this.modalForm.startTime = "";
+        this.modalForm.endTime = "";
+      }
+    },
+    async save() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      let datas = {
+        ...this.modalForm
+      };
+      datas.userIds = this.tasks.map(task => {
+        return {
+          courseCode: task.courseCode,
+          courseName: task.courseName,
+          paperNumber: task.paperNumber,
+          specialty: task.specialty,
+          userId: task.userId
+        };
+      });
+
+      const data = await batchAddExamTask(datas).catch(() => {});
+      this.isSubmit = false;
+      if (!data) return;
+
+      this.$message.success("保存成功!");
+      this.$emit("modified");
+      this.cancel();
+    },
+    // upload-handler
+    uplaodError(errorData) {
+      this.$notify.error({ title: "错误提示", message: errorData.message });
+    },
+    uploadExamTaskSuccess(res) {
+      this.$message.success("上传成功!");
+      const data = res.data;
+
+      this.modalForm.batchNo = data.batchNo;
+      this.tasks = data.tasks.map(item => {
+        item.userId = "";
+        return item;
+      });
+      this.$refs.modalFormComp.validateField("batchNo");
+    }
+  }
+};
+</script>

+ 303 - 0
src/modules/exam/components/CardOptionDialog.vue

@@ -0,0 +1,303 @@
+<template>
+  <div class="card-option-dialog">
+    <el-dialog
+      class="card-option-dialog"
+      :visible.sync="modalIsShow"
+      title="选择制卡方式"
+      top="10vh"
+      width="560px"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      append-to-body
+      @open="visibleChange"
+    >
+      <div class="card-option-body part-box part-box-pad part-box-border">
+        <p>请您选择创建题卡方式:</p>
+        <p>1.自助创建:使用题卡工具自助设计题卡,操作简单,即刻生成;</p>
+        <p>
+          2.申请客服制卡:由客服后台统一处理,处理完毕后您可查看或微调。
+        </p>
+        <div class="card-type">
+          <el-radio-group v-model="modalForm.cardSource">
+            <el-radio
+              v-for="item in cardSourceTypes"
+              :key="item.type"
+              :label="item.type"
+              :disabled="item.disabled"
+              >{{ item.name }}</el-radio
+            >
+          </el-radio-group>
+        </div>
+        <!-- card-select -->
+        <div class="card-select" v-if="modalForm.cardSource === 0">
+          <el-form
+            ref="ModalForm"
+            :model="modalForm"
+            :rules="rules"
+            label-width="90px"
+          >
+            <el-form-item prop="refCardId" label="已选题卡:">
+              <el-select
+                v-model="modalForm.refCardId"
+                style="width: 300px;"
+                placeholder="请选择"
+                clearable
+              >
+                <el-option
+                  v-for="item in cards"
+                  :key="item.id"
+                  :value="item.id"
+                  :label="item.name"
+                >
+                </el-option>
+              </el-select>
+              <span class="card-view" @click="toPreview">预览</span>
+            </el-form-item>
+          </el-form>
+        </div>
+        <!-- apply-customer -->
+        <div class="card-apply" v-if="modalForm.cardSource === 2">
+          <el-form
+            ref="ApplyModalForm"
+            :model="applyModalForm"
+            :rules="applyRules"
+            label-width="90px"
+          >
+            <el-form-item prop="title" label="文件标题:">
+              <el-input
+                style="width:300px"
+                v-model.trim="applyModalForm.title"
+                placeholder="请输入文件标题"
+              ></el-input>
+            </el-form-item>
+            <el-form-item prop="paperAttachmentId">
+              <upload-button
+                btn-icon="icon icon-upload"
+                btn-type="default"
+                btn-content="选择上传文件"
+                :upload-url="uploadUrl"
+                :format="format"
+                :max-size="maxSize"
+                :upload-data="uploadData"
+                @upload-error="uplaodError"
+                @upload-success="uploadSuccess"
+                style="margin: 0;"
+              ></upload-button>
+              <span style="margin-left: 10px;"
+                >上传的单个试卷文件大小不超过20M</span
+              >
+            </el-form-item>
+          </el-form>
+        </div>
+      </div>
+      <div slot="footer">
+        <el-button type="primary" @click="confirm">确定</el-button>
+        <el-button type="danger" @click="cancel" plain>返回</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { CARD_SOURCE_TYPE } from "@/constants/enumerate";
+import { cardList } from "../api";
+import UploadButton from "@/components/UploadButton";
+
+const initModalForm = {
+  examTaskId: "",
+  paperType: "",
+  courseName: "",
+  courseCode: "",
+  cardId: "",
+  cardSource: "",
+  refCardId: ""
+};
+
+export default {
+  name: "card-option-dialog-view",
+  props: {
+    data: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  components: { UploadButton },
+  data() {
+    return {
+      modalIsShow: false,
+      modalForm: { ...initModalForm },
+      rules: {
+        refCardId: [
+          {
+            required: true,
+            message: "请选择已选题卡",
+            trigger: "change"
+          }
+        ]
+      },
+      onlySelect: false,
+      cards: [],
+      cardSourceTypes: [],
+      CARD_SOURCE_TYPE,
+      // apply-customer
+      applyModalForm: {
+        title: "",
+        paperAttachmentId: ""
+      },
+      applyRules: {
+        title: [
+          {
+            required: true,
+            message: "请输入文件标题",
+            trigger: "change"
+          }
+        ],
+        paperAttachmentId: [
+          {
+            required: true,
+            message: "请上传样卷或试卷结构文件",
+            trigger: "change"
+          }
+        ]
+      },
+      // upload
+      format: ["pdf"],
+      uploadData: {
+        schoolId: this.$ls.get("schoolId"),
+        userId: this.$ls.get("user", { id: "" }).id
+      },
+      maxSize: 20 * 1024 * 1024,
+      uploadUrl: "/api/common/file/upload"
+    };
+  },
+  methods: {
+    visibleChange() {
+      this.getCardSourceTypes();
+      this.modalForm.cardSource = this.cardSourceTypes[0].type;
+      this.modalForm = this.$objAssign(initModalForm, this.data);
+
+      this.getCardList();
+    },
+    getCardSourceTypes() {
+      let cardSourceTypes = [];
+      Object.keys(CARD_SOURCE_TYPE).forEach(key => {
+        // 自助创建 =>
+        // 若题卡规则为专卡,命题人可即时创建专卡;
+        // 若题卡规则为全部通卡,则不显示该制卡方式;
+        // 申请客服制卡 =>
+        // 若题卡规则为专卡,命题人可即时创建专卡;
+        // 若题卡规则为全部通卡,则不显示该制卡方式;
+        if (this.data.curRuleId === "GENERAL" && key !== "0") return;
+
+        cardSourceTypes.push({
+          type: key * 1,
+          name: CARD_SOURCE_TYPE[key]
+        });
+      });
+      this.cardSourceTypes = cardSourceTypes;
+    },
+    async getCardList() {
+      // 选择已有题卡 =>
+      // 若题卡规则为专卡,选择已有题卡时,下拉列表将显示适用的专卡和通卡;
+      // 若题卡规则为“全部通卡”,则下拉列表中只显示适用的通卡模板;
+      const data = await cardList({
+        courseCode: this.modalForm.courseCode,
+        paperType: this.modalForm.paperType,
+        curRuleId: this.modalForm.curRuleId === "GENERAL" ? "GENERAL" : null
+      });
+      this.cards = data.map(item => {
+        return {
+          id: item.id,
+          name: `${item.title}(${item.code})`
+        };
+      });
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    uplaodError(errorData) {
+      this.$notify.error({ title: "错误提示", message: errorData.message });
+    },
+    uploadSuccess(res) {
+      this.$message.success("上传成功!");
+      this.applyModalForm.paperAttachmentId = res.data.id;
+      this.$refs.ApplyModalForm.validateField("paperAttachmentId");
+    },
+    confirm() {
+      if (this.modalForm.cardSource !== "0" && !this.modalForm.courseCode) {
+        this.$message.error("请选择科目!");
+        return;
+      }
+
+      if (this.modalForm.cardSource === "2") {
+        this.$emit("draft-task");
+        this.$refs.UploadSamplePaperDialog.open();
+        return;
+      }
+
+      if (this.modalForm.cardSource === "1") {
+        // 暂存任务,确保以上传的试卷信息正常保存
+        this.$emit("draft-task");
+        // 打开题卡编辑页,创建题卡,并预设需要绑定的任务
+        this.$ls.set("prepareTcPCard", this.modalForm);
+        this.$router.push({
+          name: "CardDesign"
+        });
+        return;
+      }
+
+      if (this.modalForm.cardSource === "0" && !this.modalForm.refCardId) {
+        this.$message.error("请选择已有的题卡!");
+        return;
+      }
+
+      const data = { ...this.modalForm };
+      if (data.cardSource !== "0") data.refCardId = "";
+      data.cardSource = data.cardSource * 1;
+      this.$emit("confirm", data);
+      this.cancel();
+    },
+    toPreview() {
+      if (!this.modalForm.refCardId) {
+        this.$message.error("请先选择题卡!");
+        return;
+      }
+      window.open(
+        this.getRouterPath({
+          name: "CardPreview",
+          params: {
+            cardId: this.modalForm.refCardId,
+            viewType: "view"
+          }
+        })
+      );
+    }
+  }
+};
+</script>
+
+<style scoped>
+.card-option-body {
+  color: #878787;
+}
+.card-type {
+  margin: 32px 0;
+}
+.card-select {
+  margin-bottom: 10px;
+}
+.card-view {
+  display: inline-block;
+  margin-left: 10px;
+  color: rgba(35, 196, 185, 1);
+  cursor: pointer;
+}
+.card-view:hover {
+  color: rgba(28, 208, 161, 1);
+}
+</style>

+ 255 - 0
src/modules/exam/components/ModifyExamTask.vue

@@ -0,0 +1,255 @@
+<template>
+  <el-dialog
+    class="modify-exam-task"
+    :visible.sync="modalIsShow"
+    :title="title"
+    top="10vh"
+    width="700px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @open="visibleChange"
+  >
+    <div class="part-box part-box-pad part-box-border">
+      <el-form
+        ref="modalFormComp"
+        label-width="130px"
+        :rules="rules"
+        :model="modalForm"
+      >
+        <el-form-item prop="courseCode" label="课程(代码):">
+          <course-select
+            v-if="editable"
+            ref="CourseSelect"
+            v-model.trim="modalForm.courseCode"
+            placeholder="请选择"
+            clearable
+          ></course-select>
+          <span v-else
+            >{{ modalForm.courseName }}({{ modalForm.courseCode }})</span
+          >
+        </el-form-item>
+        <el-form-item prop="specialty" label="适用专业(方向):">
+          <el-input
+            v-if="editable"
+            v-model.trim="modalForm.specialty"
+            type="textarea"
+            resize="none"
+            :rows="2"
+            :maxlength="100"
+            clearable
+            show-word-limit
+            placeholder="建议不超过100个字"
+          ></el-input>
+          <span v-else>{{ modalForm.specialty }}</span>
+        </el-form-item>
+        <el-form-item prop="paperNumber" label="试卷编号:">
+          <el-input
+            v-if="editable"
+            v-model="modalForm.paperNumber"
+            clearable
+          ></el-input>
+          <span v-else>{{ modalForm.paperNumber }}</span>
+        </el-form-item>
+        <el-form-item prop="startTime" label="命题时间:">
+          <el-date-picker
+            v-if="editable"
+            v-model="createTime"
+            type="datetimerange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            value-format="timestamp"
+            align="right"
+            unlink-panels
+            @change="dateChange"
+          >
+          </el-date-picker>
+          <span v-else>
+            {{ modalForm.startTime | timestampFilter }} 至
+            {{ modalForm.endTime | timestampFilter }}</span
+          >
+        </el-form-item>
+        <el-form-item prop="cardRuleId" label="题卡规则:">
+          <card-rule-select
+            v-if="editable"
+            ref="CardRuleSelect"
+            v-model.trim="modalForm.cardRuleId"
+            placeholder="请选择"
+            clearable
+          ></card-rule-select>
+          <span v-else>{{ modalForm.cardRuleName }}</span>
+          <p class="tips-info">
+            说明:若选择全部通卡,则命题老师只能选择通卡,若选择题卡规则,则专卡和通卡均可选择
+          </p>
+        </el-form-item>
+        <el-form-item prop="userId" label="命题老师:">
+          <question-teacher-select
+            v-if="editType !== 'PREVIEW'"
+            ref="QuestionTeacherSelect"
+            v-model="modalForm.userId"
+            :course-code="modalForm.courseCode"
+            placeholder="请选择"
+          ></question-teacher-select>
+          <span v-else>{{ modalForm.userName }}</span>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div slot="footer">
+      <el-button type="primary" :disabled="isSubmit" @click="submit"
+        >确认</el-button
+      >
+      <el-button type="danger" @click="cancel" plain>取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { updateExamTask } from "../api";
+import CardRuleSelect from "../../../components/base/CardRuleSelect";
+import CourseSelect from "../../../components/base/CourseSelect";
+import QuestionTeacherSelect from "../../../components/base/QuestionTeacherSelect";
+
+const initModalForm = {
+  id: null,
+  courseCode: "",
+  courseName: "",
+  specialty: "",
+  paperNumber: "",
+  startTime: "",
+  endTime: "",
+  cardRuleId: "",
+  cardRuleName: "",
+  userId: "",
+  userName: ""
+};
+
+export default {
+  name: "modify-exam-task",
+  components: { CardRuleSelect, CourseSelect, QuestionTeacherSelect },
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      }
+    },
+    editType: {
+      type: String,
+      default: "ADD",
+      validator: val => ["ADD", "PREVIEW", "EDIT"].includes(val)
+    }
+  },
+  computed: {
+    isEdit() {
+      return !!this.instance.id;
+    },
+    title() {
+      return (this.isEdit ? "编辑" : "新增") + "命题任务";
+    },
+    editable() {
+      return this.editType === "ADD";
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      modalForm: {},
+      createTime: [],
+      rules: {
+        courseCode: [
+          {
+            required: true,
+            message: "请选择课程",
+            trigger: "change"
+          }
+        ],
+        paperNumber: [
+          {
+            required: false,
+            validator: (rule, value, callback) => {
+              if (!value) {
+                return callback();
+              }
+
+              if (/^[0-9a-zA-Z]{3,30}$/.test(value)) {
+                callback();
+              } else {
+                callback(new Error("试卷编号只能包好字母数字,长度在3-30之间"));
+              }
+            },
+            trigger: "change"
+          }
+        ],
+        startTime: [
+          {
+            required: true,
+            message: "请设置命题时间",
+            trigger: "change"
+          }
+        ],
+        cardRuleId: [
+          {
+            required: true,
+            message: "请选择题卡规则",
+            trigger: "change"
+          }
+        ],
+        userId: [
+          {
+            required: true,
+            message: "请选择命题老师",
+            trigger: "change"
+          }
+        ]
+      }
+    };
+  },
+  methods: {
+    initData(val) {
+      this.modalForm = this.$objAssign(initModalForm, val);
+    },
+    visibleChange() {
+      this.initData(this.instance);
+
+      this.$nextTick(() => {
+        this.$refs.modalFormComp.clearValidate();
+      });
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    dateChange() {
+      if (this.createTime) {
+        this.modalForm.startTime = this.createTime[0];
+        this.modalForm.endTime = this.createTime[1];
+      } else {
+        this.modalForm.startTime = "";
+        this.modalForm.endTime = "";
+      }
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      let modals = {
+        ...this.modalForm
+      };
+      const data = await updateExamTask(modals).catch(() => {});
+      this.isSubmit = false;
+      if (!data) return;
+
+      this.$message.success("保存成功!");
+      this.$emit("modified");
+      this.cancel();
+    }
+  }
+};
+</script>

+ 179 - 0
src/modules/exam/components/ModifyTaskApply.vue

@@ -0,0 +1,179 @@
+<template>
+  <el-dialog
+    class="modify-task-apply"
+    :visible.sync="modalIsShow"
+    :title="title"
+    top="10px"
+    width="900px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @open="visibleChange"
+  >
+    <div class="part-box part-box-pad part-box-border">
+      <el-form class="form-info" label-width="130px">
+        <el-row>
+          <el-col :span="10">
+            <el-form-item label="课程(代码):">
+              <span
+                >{{ modalForm.courseName }}({{ modalForm.courseCode }})</span
+              >
+            </el-form-item>
+          </el-col>
+          <el-col :span="14">
+            <el-form-item label="适用专业(方向):">
+              <span>{{ modalForm.specialty }}</span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="10">
+            <el-form-item label="试卷编号:">
+              <span>{{ modalForm.paperNumber }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="14">
+            <el-form-item label="命题时间:">
+              <span>
+                {{ modalForm.startTime | timestampFilter }} 至
+                {{ modalForm.endTime | timestampFilter }}</span
+              >
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="10">
+            <el-form-item label="题卡规则:">
+              <span>{{ modalForm.cardRuleName }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="14">
+            <el-form-item label="命题老师:">
+              <span>{{ modalForm.userName }}</span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="10">
+            <el-form-item label="审核状态:">
+              <span>{{ modalForm.auditStatus | auditStatusFilter }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="14">
+            <el-form-item label="审核结果:">
+              <span>{{ modalForm.reviewStatus | reviewStatusFilter }}</span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </div>
+
+    <div class="mb-2">
+      <el-button
+        v-for="item in menus"
+        :key="item.id"
+        :type="item.id == curMenu.id ? 'primary' : 'default'"
+        @click="selectMenu(item)"
+        >{{ item.name }}</el-button
+      >
+    </div>
+    <div class="part-box part-box-pad part-box-border">
+      <!-- <component :is="curMenu.component"></component> -->
+      <apply-content
+        ref="ApplyContent"
+        :exam-task="modalForm"
+        :view-type="viewType"
+        v-show="curMenu.id === '1'"
+        @cancel="cancel"
+        @modified="modified"
+      ></apply-content>
+      <apply-audit-history
+        ref="ApplyAuditHistory"
+        :exam-task-id="modalForm.id"
+        v-show="curMenu.id === '2'"
+      ></apply-audit-history>
+    </div>
+
+    <div slot="footer"></div>
+  </el-dialog>
+</template>
+
+<script>
+import ApplyContent from "./ApplyContent";
+import ApplyAuditHistory from "./ApplyAuditHistory";
+
+const initModalForm = {
+  id: null,
+  courseCode: "",
+  courseName: "",
+  specialty: "",
+  paperNumber: "",
+  startTime: "",
+  endTime: "",
+  cardRuleId: "",
+  cardRuleName: "",
+  userId: "",
+  userName: "",
+  auditStatus: "",
+  reviewStatus: ""
+};
+
+export default {
+  name: "modify-task-apply",
+  components: { ApplyContent, ApplyAuditHistory },
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      }
+    },
+    viewType: {
+      type: String,
+      default: "APPLY",
+      validator: val => ["APPLY", "VIEW", "AUDIT"].includes(val)
+    }
+  },
+  computed: {
+    isEdit() {
+      return !!this.instance.id;
+    },
+    title() {
+      return (this.isEdit ? "编辑" : "新增") + "命题任务";
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      modalForm: {},
+      menus: [
+        { id: "1", name: "命题处理", component: "apply-content" },
+        { id: "2", name: "审核意见", component: "apply-audit-history" }
+      ],
+      curMenu: {}
+    };
+  },
+  methods: {
+    initData(val) {
+      this.modalForm = this.$objAssign(initModalForm, val);
+    },
+    visibleChange() {
+      this.initData(this.instance);
+      this.curMenu = this.menus[0];
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    selectMenu(item) {
+      this.curMenu = item;
+    },
+    modified() {
+      this.cancel();
+      this.$emit("modified");
+    }
+  }
+};
+</script>

+ 112 - 0
src/modules/exam/components/UploadPaperDialog.vue

@@ -0,0 +1,112 @@
+<template>
+  <el-dialog
+    class="upload-paper-dialog"
+    :visible.sync="modalIsShow"
+    :title="`上传${curConfig.title}`"
+    top="10vh"
+    width="500px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @opened="visibleChange"
+  >
+    <div class="file-upload-body">
+      <p class="mb-2">
+        上传的{{ curConfig.title }}仅限{{ curConfig.format.join(",") }}文件
+      </p>
+      <upload-file-view
+        input-width="360px"
+        :format="curConfig.format"
+        :upload-url="uploadUrl"
+        @upload-error="uplaodError"
+        @upload-success="uploadSuccess"
+        ref="UploadFileView"
+      ></upload-file-view>
+    </div>
+    <div slot="footer">
+      <el-button type="primary" @click="confirm">确认</el-button>
+      <el-button type="danger" @click="cancel" plain>关闭</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import UploadFileView from "@/components/UploadFileView";
+
+export default {
+  name: "upload-paper-dialog",
+  components: { UploadFileView },
+  props: {
+    paperAttachment: {
+      type: Object,
+      default() {
+        return {};
+      }
+    },
+    uploadType: {
+      type: String,
+      default: "paper",
+      validator(val) {
+        return ["paper", "paperConfirm"].includes(val);
+      }
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      attachment: {},
+      config: {
+        paper: { title: "试卷文件", format: ["pdf"] },
+        paperConfirm: {
+          title: "入库审核表",
+          format: ["jpg", "png"]
+        }
+      },
+      curConfig: { format: [] },
+      // import
+      uploadUrl: "/api/common/file/upload"
+    };
+  },
+  methods: {
+    visibleChange() {
+      this.$refs.UploadFileView.setAttachmentName(
+        `${this.paperAttachment.filename || ""}`
+      );
+      this.attachment = { ...this.paperAttachment };
+      this.curConfig = this.config[this.uploadType];
+    },
+    // upload-handler
+    uplaodError(errorData) {
+      this.$notify.error({
+        title: "错误提示",
+        message: errorData.message
+      });
+    },
+    uploadSuccess(res) {
+      this.$message.success("上传成功!");
+      let infos = {
+        attachmentId: res.data.id,
+        filename: `${res.data.fileName}${res.data.fileType}`,
+        path: res.data.path
+      };
+      this.attachment = Object.assign(this.attachment, infos);
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    confirm() {
+      this.$emit("confirm", this.attachment, this.uploadType);
+      this.cancel();
+    }
+  }
+};
+</script>
+
+<style lang="css" scoped>
+.file-upload-body {
+  min-height: 150px;
+}
+</style>

+ 40 - 0
src/modules/exam/router.js

@@ -0,0 +1,40 @@
+// exam
+import WaitTask from "./views/WaitTask.vue";
+import DoneTask from "./views/DoneTask.vue";
+import ExamTaskManage from "./views/ExamTaskManage.vue";
+import TaskApplyManage from "./views/TaskApplyManage.vue";
+import TaskReviewManage from "./views/TaskReviewManage.vue";
+import TaskPaperManage from "./views/TaskPaperManage.vue";
+
+export default [
+  {
+    path: "/exam/wait-task",
+    name: "WaitTask",
+    component: WaitTask
+  },
+  {
+    path: "/exam/done-task",
+    name: "DoneTask",
+    component: DoneTask
+  },
+  {
+    path: "/exam/exam-task-manage",
+    name: "ExamTaskManage",
+    component: ExamTaskManage
+  },
+  {
+    path: "/exam/task-apply-manage",
+    name: "TaskApplyManage",
+    component: TaskApplyManage
+  },
+  {
+    path: "/exam/task-review-manage",
+    name: "TaskReviewManage",
+    component: TaskReviewManage
+  },
+  {
+    path: "/exam/task-paper-manage",
+    name: "TaskPaperManage",
+    component: TaskPaperManage
+  }
+];

+ 15 - 0
src/modules/exam/views/DoneTask.vue

@@ -0,0 +1,15 @@
+<template>
+  <div class="DoneTask">
+    DoneTask
+  </div>
+</template>
+
+<script>
+export default {
+  name: "DoneTask",
+  data() {
+    return {};
+  },
+  methods: {}
+};
+</script>

+ 350 - 0
src/modules/exam/views/ExamTaskManage.vue

@@ -0,0 +1,350 @@
+<template>
+  <div class="exam-task-manage">
+    <div class="part-box part-box-filter part-box-flex">
+      <el-form ref="FilterForm" label-position="left" label-width="85px" inline>
+        <el-form-item label="命题状态:">
+          <el-select
+            v-model="filter.status"
+            style="width: 142px;"
+            placeholder="请选择"
+            clearable
+          >
+            <el-option
+              v-for="(val, key) in EXAM_TASK_STATUS"
+              :key="key"
+              :value="key"
+              :label="val"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="题卡规则:">
+          <card-rule-select
+            ref="CardRuleSelect"
+            v-model.trim="filter.cardRuleId"
+            placeholder="请输入内容"
+            clearable
+          ></card-rule-select>
+        </el-form-item>
+        <el-form-item label="课程(代码):" label-width="110px">
+          <course-select
+            ref="CourseSelect"
+            v-model.trim="filter.courseCode"
+            placeholder="请输入内容"
+            clearable
+          ></course-select>
+        </el-form-item>
+        <el-form-item label="试卷编号:">
+          <paper-number-select
+            ref="PaperNumberSelect"
+            v-model="filter.paperNumber"
+            placeholder="请选择"
+            clearable
+          ></paper-number-select>
+        </el-form-item>
+        <el-form-item label="命题时间:">
+          <el-date-picker
+            v-model="createTime"
+            type="datetimerange"
+            :picker-options="pickerOptions"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            value-format="timestamp"
+            align="right"
+            unlink-panels
+          >
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="状态:" label-width="55px">
+          <el-select
+            v-model="filter.enable"
+            style="width: 142px;"
+            placeholder="请选择"
+            clearable
+          >
+            <el-option
+              v-for="(val, key) in ABLE_TYPE"
+              :key="key"
+              :value="key"
+              :label="val"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label-width="0px">
+          <el-button type="primary" icon="icon icon-search" @click="toPage(1)"
+            >查询</el-button
+          >
+        </el-form-item>
+      </el-form>
+      <div class="part-box-action">
+        <el-button icon="icon icon-plus" type="warning" @click="toBatchAdd">
+          批量新建命题任务
+        </el-button>
+        <el-button icon="icon icon-plus" type="primary" @click="toAdd">
+          新建命题任务
+        </el-button>
+      </div>
+    </div>
+
+    <div class="part-box">
+      <el-table ref="TableList" :data="examTasks" border stripe>
+        <el-table-column
+          type="index"
+          label="序号"
+          width="70"
+          align="center"
+          :index="indexMethod"
+        ></el-table-column>
+        <el-table-column prop="paperNumber" label="试卷编号"></el-table-column>
+        <el-table-column prop="courseName" label="课程(代码)">
+          <template slot-scope="scope">
+            {{ scope.row.courseName }}({{ scope.row.courseCode }})
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="specialty"
+          label="适用专业(方向)"
+        ></el-table-column>
+        <el-table-column prop="cardRuleName" label="题卡规则"></el-table-column>
+        <el-table-column prop="userName" label="命题老师"></el-table-column>
+        <el-table-column prop="startTime" label="命题开始时间">
+          <span slot-scope="scope">{{
+            scope.row.startTime | timestampFilter
+          }}</span>
+        </el-table-column>
+        <el-table-column prop="endTime" label="命题结束时间">
+          <span slot-scope="scope">{{
+            scope.row.endTime | timestampFilter
+          }}</span>
+        </el-table-column>
+        <el-table-column prop="status" label="命题状态">
+          <template slot-scope="scope">
+            {{ scope.row.status | examTaskStatusFilter }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="enable" label="启用/取消">
+          <template slot-scope="scope">
+            {{ scope.row.enable ? "启用" : "取消" }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          class-name="action-column"
+          label="操作"
+          align="center"
+          width="120px"
+        >
+          <template slot-scope="scope">
+            <el-button
+              class="btn-table-icon"
+              type="text"
+              :icon="
+                scope.row.enable
+                  ? 'icon icon-circle-stop'
+                  : 'icon icon-circle-caret-right'
+              "
+              @click="toEnable(scope.row)"
+              :title="scope.row.enable ? '取消' : '启用'"
+            ></el-button>
+            <el-button
+              v-if="scope.row.status === 'STAGE' || scope.row.status === 'NEW'"
+              class="btn-table-icon"
+              type="text"
+              icon="icon icon-edit"
+              @click="toEdit(scope.row)"
+              :title="scope.row.status === 'NEW' ? '更改' : '指派'"
+            ></el-button>
+            <el-button
+              v-if="scope.row.status === 'SUBMIT'"
+              class="btn-table-icon"
+              type="text"
+              icon="icon icon-circle-right"
+              @click="toPreview(scope.row)"
+              title="查看"
+            ></el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="part-page">
+        <el-pagination
+          background
+          layout="total,prev, pager, next"
+          :current-page="current"
+          :total="total"
+          :page-size="size"
+          @current-change="toPage"
+        >
+        </el-pagination>
+      </div>
+    </div>
+
+    <!-- ModifyExamTask -->
+    <modify-exam-task
+      ref="ModifyExamTask"
+      :instance="curExamTask"
+      :edit-type="editType"
+      @modified="getList"
+    ></modify-exam-task>
+    <!-- BatchAddExamTask -->
+    <batch-add-exam-task
+      ref="BatchAddExamTask"
+      @modified="getList"
+    ></batch-add-exam-task>
+  </div>
+</template>
+
+<script>
+import ModifyExamTask from "../components/ModifyExamTask";
+import BatchAddExamTask from "../components/BatchAddExamTask";
+import CardRuleSelect from "../../../components/base/CardRuleSelect";
+import CourseSelect from "../../../components/base/CourseSelect";
+import PaperNumberSelect from "../../../components/base/PaperNumberSelect";
+import { EXAM_TASK_STATUS } from "@/constants/enumerate";
+import { examTaskListPage, ableExamTask } from "../api";
+
+export default {
+  name: "exam-task-manage",
+  components: {
+    ModifyExamTask,
+    BatchAddExamTask,
+    CardRuleSelect,
+    CourseSelect,
+    PaperNumberSelect
+  },
+  data() {
+    return {
+      filter: {
+        status: "",
+        cardRuleId: "",
+        courseCode: "",
+        paperNumber: "",
+        startTime: null,
+        endTime: null,
+        enable: null
+      },
+      createTime: [],
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      editType: "ADD",
+      ABLE_TYPE: {
+        0: "取消",
+        1: "启用"
+      },
+      EXAM_TASK_STATUS,
+      examTasks: [],
+      curExamTask: {},
+      // date-picker
+      pickerOptions: {
+        shortcuts: [
+          {
+            text: "最近一周",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
+              picker.$emit("pick", [start, end]);
+            }
+          },
+          {
+            text: "最近一个月",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
+              picker.$emit("pick", [start, end]);
+            }
+          },
+          {
+            text: "最近三个月",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
+              picker.$emit("pick", [start, end]);
+            }
+          }
+        ]
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    async getList() {
+      const datas = {
+        ...this.filter,
+        pageNumber: this.current,
+        pageSize: this.size
+      };
+      datas.enable = !!datas.enable;
+      if (this.createTime) {
+        datas.startTime = this.createTime[0];
+        datas.endTime = this.createTime[1];
+      }
+      const data = await examTaskListPage(datas);
+      this.examTasks = data.records;
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    async toEnable(row) {
+      const msgs = [
+        [
+          "命题任务取消后,有如下后果:",
+          "1.命题老师将无法看到该命题任务;",
+          "2.该任务无法审核和与考试计划关联;",
+          "3.该任务已绑定的题卡将取消绑定。",
+          "您确定要取消命题任务吗?"
+        ],
+        [
+          "命题任务启用后:",
+          "1.命题老师将重新看到该命题任务;",
+          "2.该任务将允许继续审核及与考试计划关联;",
+          "3.该任务需重新绑定题卡。",
+          "您确定要启用命题任务吗?"
+        ]
+      ];
+      const msg = row.enable ? msgs[0] : msgs[1];
+      const msgHtml = msg
+        .map(item => `<p class="text-left">${item}</p>`)
+        .join("");
+      this.$confirm(msgHtml, "提示", {
+        cancelButtonClass: "el-button--danger is-plain",
+        confirmButtonClass: "el-button--primary",
+        dangerouslyUseHTMLString: true,
+        type: "warning"
+      }).then(async () => {
+        const enable = Number(!row.enable);
+        await ableExamTask({
+          id: row.id,
+          enable
+        });
+        row.enable = enable;
+        this.$message.success("操作成功!");
+      });
+    },
+    toBatchAdd() {
+      this.$refs.BatchAddExamTask.open();
+    },
+    toAdd() {
+      this.curExamTask = {};
+      this.editType = "ADD";
+      this.$refs.ModifyExamTask.open();
+    },
+    toEdit(row) {
+      this.curExamTask = row;
+      this.editType = "EDIT";
+      this.$refs.ModifyExamTask.open();
+    },
+    toPreview(row) {
+      this.curExamTask = row;
+      this.editType = "PREVIEW";
+      this.$refs.ModifyExamTask.open();
+    }
+  }
+};
+</script>

+ 318 - 0
src/modules/exam/views/TaskApplyManage.vue

@@ -0,0 +1,318 @@
+<template>
+  <div class="task-apply-manage">
+    <div class="part-box part-box-filter part-box-flex">
+      <el-form ref="FilterForm" label-position="left" label-width="85px" inline>
+        <el-form-item label="审核状态:">
+          <el-select
+            v-model="filter.auditStatus"
+            style="width: 142px;"
+            placeholder="请选择"
+            clearable
+          >
+            <el-option
+              v-for="(val, key) in AUDITING_STATUS"
+              :key="key"
+              :value="key"
+              :label="val"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="审核结果:">
+          <el-select
+            v-model="filter.reviewStatus"
+            style="width: 142px;"
+            placeholder="请选择"
+            clearable
+          >
+            <el-option
+              v-for="(val, key) in AUDITING_RESULT"
+              :key="key"
+              :value="key"
+              :label="val"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="题卡规则:">
+          <card-rule-select
+            ref="CardRuleSelect"
+            v-model.trim="filter.cardRuleId"
+            placeholder="请输入内容"
+            clearable
+          ></card-rule-select>
+        </el-form-item>
+        <el-form-item label="课程(代码):" label-width="110px">
+          <course-select
+            ref="CourseSelect"
+            v-model.trim="filter.courseCode"
+            placeholder="请输入内容"
+            clearable
+          ></course-select>
+        </el-form-item>
+        <el-form-item label="试卷编号:">
+          <paper-number-select
+            ref="PaperNumberSelect"
+            v-model="filter.paperNumber"
+            placeholder="请选择"
+            clearable
+          ></paper-number-select>
+        </el-form-item>
+        <el-form-item label="命题时间:">
+          <el-date-picker
+            v-model="createTime"
+            type="datetimerange"
+            :picker-options="pickerOptions"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            value-format="timestamp"
+            align="right"
+            unlink-panels
+          >
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label-width="0px">
+          <el-button type="primary" icon="icon icon-search" @click="toPage(1)"
+            >查询</el-button
+          >
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="part-box">
+      <el-table ref="TableList" :data="examTasks" border stripe>
+        <el-table-column
+          type="index"
+          label="序号"
+          width="70"
+          align="center"
+          :index="indexMethod"
+        ></el-table-column>
+        <el-table-column prop="paperNumber" label="试卷编号"></el-table-column>
+        <el-table-column prop="courseName" label="课程(代码)">
+          <template slot-scope="scope">
+            {{ scope.row.courseName }}({{ scope.row.courseCode }})
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="specialty"
+          label="适用专业(方向)"
+        ></el-table-column>
+        <el-table-column prop="cardRuleName" label="题卡规则"></el-table-column>
+        <el-table-column prop="userName" label="命题老师"></el-table-column>
+        <el-table-column prop="startTime" label="命题开始时间">
+          <span slot-scope="scope">{{
+            scope.row.startTime | timestampFilter
+          }}</span>
+        </el-table-column>
+        <el-table-column prop="endTime" label="命题结束时间">
+          <span slot-scope="scope">{{
+            scope.row.endTime | timestampFilter
+          }}</span>
+        </el-table-column>
+        <el-table-column prop="auditStatus" label="审核状态">
+          <template slot-scope="scope">
+            {{ scope.row.auditStatus | auditStatusFilter }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="reviewStatus" label="审核结果">
+          <template slot-scope="scope">
+            {{ scope.row.reviewStatus | reviewStatusFilter }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="enable" label="启用/取消">
+          <template slot-scope="scope">
+            {{ scope.row.enable ? "启用" : "取消" }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          class-name="action-column"
+          label="操作"
+          align="center"
+          width="120px"
+        >
+          <template slot-scope="scope">
+            <el-button
+              v-if="!scope.row.auditStatus && !scope.row.reviewStatus"
+              class="btn-table-icon"
+              type="text"
+              icon="icon icon-edit"
+              @click="toEdit(scope.row)"
+              title="立即申请"
+            ></el-button>
+            <el-button
+              v-if="
+                scope.row.auditStatus === 'AUDITED' &&
+                  scope.row.reviewStatus === 'NOT_PASS'
+              "
+              class="btn-table-icon"
+              type="text"
+              icon="icon icon-edit"
+              @click="toEdit(scope.row)"
+              title="重新申请"
+            ></el-button>
+            <el-button
+              v-if="scope.row.auditStatus === 'NOT_AUDITED'"
+              class="btn-table-icon"
+              type="text"
+              icon="icon icon-close-act"
+              @click="toCancel(scope.row)"
+              title="撤销申请"
+            ></el-button>
+            <el-button
+              v-if="scope.row.auditStatus || scope.row.reviewStatus"
+              class="btn-table-icon"
+              type="text"
+              icon="icon icon-circle-right"
+              @click="toPreview(scope.row)"
+              title="查看详情"
+            ></el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="part-page">
+        <el-pagination
+          background
+          layout="total,prev, pager, next"
+          :current-page="current"
+          :total="total"
+          :page-size="size"
+          @current-change="toPage"
+        >
+        </el-pagination>
+      </div>
+    </div>
+
+    <!-- ModifyTaskApply -->
+    <modify-task-apply
+      ref="ModifyTaskApply"
+      :view-type="viewType"
+      :instance="curExamTask"
+      @modified="getList"
+    ></modify-task-apply>
+  </div>
+</template>
+
+<script>
+import ModifyTaskApply from "../components/ModifyTaskApply";
+import CardRuleSelect from "../../../components/base/CardRuleSelect";
+import CourseSelect from "../../../components/base/CourseSelect";
+import PaperNumberSelect from "../../../components/base/PaperNumberSelect";
+import { AUDITING_STATUS, AUDITING_RESULT } from "@/constants/enumerate";
+import { taskApplyListPage, cancelOrRestartTaskApply } from "../api";
+
+export default {
+  name: "task-apply-manage",
+  components: {
+    ModifyTaskApply,
+    CardRuleSelect,
+    CourseSelect,
+    PaperNumberSelect
+  },
+  data() {
+    return {
+      filter: {
+        auditStatus: "",
+        reviewStatus: "",
+        cardRuleId: "",
+        courseCode: "",
+        paperNumber: "",
+        startTime: "",
+        endTime: ""
+      },
+      createTime: [],
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      viewType: "APPLY",
+      ABLE_TYPE: {
+        0: "取消",
+        1: "启用"
+      },
+      AUDITING_STATUS,
+      AUDITING_RESULT,
+      examTasks: [],
+      curExamTask: {},
+      // date-picker
+      pickerOptions: {
+        shortcuts: [
+          {
+            text: "最近一周",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
+              picker.$emit("pick", [start, end]);
+            }
+          },
+          {
+            text: "最近一个月",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
+              picker.$emit("pick", [start, end]);
+            }
+          },
+          {
+            text: "最近三个月",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
+              picker.$emit("pick", [start, end]);
+            }
+          }
+        ]
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    async getList() {
+      const datas = {
+        ...this.filter,
+        pageNumber: this.current,
+        pageSize: this.size
+      };
+      if (this.createTime) {
+        datas.startTime = this.createTime[0];
+        datas.endTime = this.createTime[1];
+      }
+      const data = await taskApplyListPage(datas);
+      this.examTasks = data.records;
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    toCancel(row) {
+      this.$confirm("确定要撤销当前申请吗?", "提示", {
+        cancelButtonClass: "el-button--danger is-plain",
+        confirmButtonClass: "el-button--primary",
+        type: "warning"
+      }).then(async () => {
+        const data = await cancelOrRestartTaskApply({
+          id: row.id,
+          status: "CANCEL"
+        }).catch(() => {});
+        if (!data) return;
+        this.$message.success("操作成功!");
+        this.getList();
+      });
+    },
+    toEdit(row) {
+      this.curExamTask = row;
+      this.viewType = "APPLY";
+      this.$refs.ModifyTaskApply.open();
+    },
+    toPreview(row) {
+      this.curExamTask = row;
+      this.viewType = "VIEW";
+      this.$refs.ModifyTaskApply.open();
+    }
+  }
+};
+</script>

+ 269 - 0
src/modules/exam/views/TaskPaperManage.vue

@@ -0,0 +1,269 @@
+<template>
+  <div class="task-paper-manage">
+    <div class="part-box part-box-filter part-box-flex">
+      <el-form ref="FilterForm" label-position="left" label-width="85px" inline>
+        <el-form-item label="课程(代码):" label-width="110px">
+          <course-select
+            ref="CourseSelect"
+            v-model.trim="filter.courseCode"
+            placeholder="请输入内容"
+            clearable
+          ></course-select>
+        </el-form-item>
+        <el-form-item label="试卷编号:">
+          <paper-number-select
+            ref="PaperNumberSelect"
+            v-model="filter.paperNumber"
+            placeholder="请选择"
+            clearable
+          ></paper-number-select>
+        </el-form-item>
+        <el-form-item label="入库时间:">
+          <el-date-picker
+            v-model="createTime"
+            type="datetimerange"
+            :picker-options="pickerOptions"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            value-format="timestamp"
+            align="right"
+            unlink-panels
+          >
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label-width="0px">
+          <el-button type="primary" icon="icon icon-search" @click="toPage(1)"
+            >查询</el-button
+          >
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="part-box">
+      <el-table ref="TableList" :data="papers" border stripe>
+        <el-table-column
+          type="index"
+          label="序号"
+          width="70"
+          align="center"
+          :index="indexMethod"
+        ></el-table-column>
+        <el-table-column prop="paperNumber" label="试卷编号"></el-table-column>
+        <el-table-column prop="courseNameCode" label="课程(代码)">
+        </el-table-column>
+        <el-table-column
+          prop="specialty"
+          label="适用专业(方向)"
+        ></el-table-column>
+        <el-table-column prop="relatePaperType" label="卷型"></el-table-column>
+        <el-table-column prop="userName" label="命题老师"></el-table-column>
+        <el-table-column prop="createTime" label="入库时间">
+          <span slot-scope="scope">{{
+            scope.row.createTime | timestampFilter
+          }}</span>
+        </el-table-column>
+        <el-table-column
+          prop="exposedPaperType"
+          label="已曝光"
+        ></el-table-column>
+        <el-table-column
+          prop="unexposedPaperType"
+          label="未曝光"
+        ></el-table-column>
+        <el-table-column prop="enable" label="启用/禁用">
+          <template slot-scope="scope">
+            {{ scope.row.enable | enableFilter }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          class-name="action-column"
+          label="操作"
+          align="center"
+          width="120px"
+        >
+          <template slot-scope="scope">
+            <el-button
+              class="btn-table-icon"
+              type="text"
+              :icon="
+                scope.row.enable
+                  ? 'icon icon-circle-stop'
+                  : 'icon icon-circle-caret-right'
+              "
+              @click="toEnable(scope.row)"
+              :title="scope.row.enable ? '禁用' : '启用'"
+            ></el-button>
+            <el-button
+              v-if="scope.row.status === 'STAGE' || scope.row.status === 'NEW'"
+              class="btn-table-icon"
+              type="text"
+              icon="icon icon-edit"
+              @click="toEdit(scope.row)"
+              :title="scope.row.status === 'NEW' ? '更改' : '指派'"
+            ></el-button>
+            <el-button
+              class="btn-table-icon"
+              type="text"
+              icon="icon icon-circle-right"
+              @click="toPreview(scope.row)"
+              title="查看"
+            ></el-button>
+            <el-button
+              class="btn-table-icon"
+              type="text"
+              icon="icon icon-download-act"
+              @click="toDownload(scope.row)"
+              title="下载"
+            ></el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="part-page">
+        <el-pagination
+          background
+          layout="total,prev, pager, next"
+          :current-page="current"
+          :total="total"
+          :page-size="size"
+          @current-change="toPage"
+        >
+        </el-pagination>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import CourseSelect from "../../../components/base/CourseSelect";
+import PaperNumberSelect from "../../../components/base/PaperNumberSelect";
+import { taskPaperListPage, ableTaskPaper } from "../api";
+
+export default {
+  name: "task-paper-manage",
+  components: {
+    CourseSelect,
+    PaperNumberSelect
+  },
+  data() {
+    return {
+      filter: {
+        courseCode: "",
+        paperNumber: "",
+        startTime: "",
+        endTime: ""
+      },
+      createTime: [],
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      papers: [],
+      curPaper: {},
+      // date-picker
+      pickerOptions: {
+        shortcuts: [
+          {
+            text: "最近一周",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
+              picker.$emit("pick", [start, end]);
+            }
+          },
+          {
+            text: "最近一个月",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
+              picker.$emit("pick", [start, end]);
+            }
+          },
+          {
+            text: "最近三个月",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
+              picker.$emit("pick", [start, end]);
+            }
+          }
+        ]
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    async getList() {
+      const datas = {
+        ...this.filter,
+        pageNumber: this.current,
+        pageSize: this.size
+      };
+      if (this.createTime) {
+        datas.startTime = this.createTime[0];
+        datas.endTime = this.createTime[1];
+      }
+      const data = await taskPaperListPage(datas);
+      this.papers = data.records;
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    async toEnable(row) {
+      const msgs = [
+        [
+          "命题任务取消后,有如下后果:",
+          "1.命题老师将无法看到该命题任务;",
+          "2.该任务无法审核和与考试计划关联;",
+          "3.该任务已绑定的题卡将取消绑定。",
+          "您确定要取消命题任务吗?"
+        ],
+        [
+          "命题任务启用后:",
+          "1.命题老师将重新看到该命题任务;",
+          "2.该任务将允许继续审核及与考试计划关联;",
+          "3.该任务需重新绑定题卡。",
+          "您确定要启用命题任务吗?"
+        ]
+      ];
+      const msg = row.enable ? msgs[0] : msgs[1];
+      const msgHtml = msg
+        .map(item => `<p class="text-left">${item}</p>`)
+        .join("");
+      this.$confirm(msgHtml, "提示", {
+        cancelButtonClass: "el-button--danger is-plain",
+        confirmButtonClass: "el-button--primary",
+        dangerouslyUseHTMLString: true,
+        type: "warning"
+      }).then(async () => {
+        const enable = !row.enable;
+        await ableTaskPaper({
+          id: row.id,
+          enable
+        });
+        row.enable = enable;
+        this.$message.success("操作成功!");
+      });
+    },
+    toEdit(row) {
+      this.curPaper = row;
+      this.editType = "EDIT";
+      this.$refs.ModifyExamTask.open();
+    },
+    toPreview(row) {
+      this.curPaper = row;
+      this.editType = "PREVIEW";
+      this.$refs.ModifyExamTask.open();
+    },
+    toDownload(row) {
+      console.log(row);
+    }
+  }
+};
+</script>

+ 435 - 0
src/modules/exam/views/TaskReviewManage.vue

@@ -0,0 +1,435 @@
+<template>
+  <div class="task-review-manage">
+    <div class="mb-4">
+      <el-button
+        v-for="(val, key) in AUDITING_STATUS"
+        :key="key"
+        :type="auditStatus == key ? 'primary' : 'default'"
+        @click="selectMenu(key)"
+        >{{ val }}</el-button
+      >
+    </div>
+    <div class="part-box part-box-filter part-box-flex">
+      <el-form ref="FilterForm" label-position="left" label-width="85px" inline>
+        <el-form-item label="题卡规则:">
+          <card-rule-select
+            ref="CardRuleSelect"
+            v-model.trim="filter.cardRuleId"
+            placeholder="请输入内容"
+            clearable
+          ></card-rule-select>
+        </el-form-item>
+        <el-form-item label="课程(代码):" label-width="110px">
+          <course-select
+            ref="CourseSelect"
+            v-model.trim="filter.courseCode"
+            placeholder="请输入内容"
+            clearable
+          ></course-select>
+        </el-form-item>
+        <el-form-item label="试卷编号:">
+          <paper-number-select
+            ref="PaperNumberSelect"
+            v-model="filter.paperNumber"
+            placeholder="请选择"
+            clearable
+          ></paper-number-select>
+        </el-form-item>
+        <el-form-item label="命题老师:">
+          <question-teacher-select
+            ref="QuestionTeacherSelect"
+            v-model="filter.userId"
+            :course-code="filter.courseCode"
+            placeholder="请选择"
+          ></question-teacher-select>
+        </el-form-item>
+        <el-form-item label="命题时间:">
+          <el-date-picker
+            v-model="createTime"
+            type="datetimerange"
+            :picker-options="pickerOptions"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            value-format="timestamp"
+            align="right"
+            unlink-panels
+          >
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="审核结果:" v-if="AUDITED">
+          <el-select
+            v-model="filter.reviewStatus"
+            style="width: 142px;"
+            placeholder="请选择"
+            clearable
+          >
+            <el-option
+              v-for="(val, key) in AUDITING_RESULT"
+              :key="key"
+              :value="key"
+              :label="val"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label-width="0px">
+          <el-button type="primary" icon="icon icon-search" @click="toPage(1)"
+            >查询</el-button
+          >
+        </el-form-item>
+      </el-form>
+      <div class="part-box-action" v-if="!AUDITED">
+        <el-button
+          icon="el-icon-download"
+          type="primary"
+          :disabled="loading"
+          @click="toExport"
+        >
+          导出审核样本
+        </el-button>
+        <el-button
+          icon="el-icon-coordinate"
+          type="danger"
+          @click="toBatchAudit(0)"
+        >
+          批量不通过
+        </el-button>
+        <el-button
+          icon="el-icon-coordinate"
+          type="primary"
+          @click="toBatchAudit(1)"
+        >
+          批量通过
+        </el-button>
+      </div>
+    </div>
+
+    <div class="part-box">
+      <el-table
+        ref="TableList"
+        :data="examTasks"
+        border
+        stripe
+        @selection-change="handleSelectionChange"
+      >
+        <el-table-column
+          v-if="!AUDITED"
+          type="selection"
+          width="55"
+        ></el-table-column>
+        <el-table-column
+          type="index"
+          label="序号"
+          width="70"
+          align="center"
+          :index="indexMethod"
+        ></el-table-column>
+        <el-table-column prop="paperNumber" label="试卷编号"></el-table-column>
+        <el-table-column prop="courseName" label="课程(代码)">
+          <template slot-scope="scope">
+            {{ scope.row.courseName }}({{ scope.row.courseCode }})
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="specialty"
+          label="适用专业(方向)"
+        ></el-table-column>
+        <el-table-column prop="cardRuleName" label="题卡规则"></el-table-column>
+        <el-table-column prop="userName" label="命题老师"></el-table-column>
+        <el-table-column prop="startTime" label="命题开始时间">
+          <span slot-scope="scope">{{
+            scope.row.startTime | timestampFilter
+          }}</span>
+        </el-table-column>
+        <el-table-column prop="endTime" label="命题结束时间">
+          <span slot-scope="scope">{{
+            scope.row.endTime | timestampFilter
+          }}</span>
+        </el-table-column>
+        <el-table-column prop="createTime" label="申请时间">
+          <template slot-scope="scope">
+            {{ scope.row.createTime | timestampFilter }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          class-name="action-column"
+          label="操作"
+          align="center"
+          width="120px"
+        >
+          <template slot-scope="scope">
+            <el-button
+              v-if="auditStatus === 'NOT_AUDITED'"
+              class="btn-table-icon"
+              type="text"
+              icon="icon icon-edit"
+              @click="toEdit(scope.row)"
+              title="审核"
+            ></el-button>
+            <el-button
+              v-else
+              class="btn-table-icon"
+              type="text"
+              icon="icon icon-circle-right"
+              @click="toPreview(scope.row)"
+              title="查看详情"
+            ></el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="part-page">
+        <el-pagination
+          background
+          layout="total,prev, pager, next"
+          :current-page="current"
+          :total="total"
+          :page-size="size"
+          @current-change="toPage"
+        >
+        </el-pagination>
+      </div>
+    </div>
+
+    <!-- ModifyTaskApply -->
+    <modify-task-apply
+      ref="ModifyTaskApply"
+      :view-type="viewType"
+      :instance="curExamTask"
+      @modified="getList"
+    ></modify-task-apply>
+    <!-- audit-dialog-->
+    <el-dialog
+      class="modify-task-apply"
+      :visible.sync="auditDialogShow"
+      title="审核意见"
+      width="500px"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      append-to-body
+    >
+      <el-form ref="AuditForm" :rules="auditRules" :model="auditModal">
+        <el-form-item prop="reason">
+          <el-input
+            class="mb-2"
+            v-model.trim="auditModal.reason"
+            type="textarea"
+            resize="none"
+            :rows="5"
+            :maxlength="1000"
+            clearable
+            show-word-limit
+            placeholder="建议不超过1000个字"
+          ></el-input>
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button type="primary" @click="auditNotPass">确认</el-button>
+        <el-button type="danger" @click="auditDialogShow = false" plain
+          >取消</el-button
+        >
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import ModifyTaskApply from "../components/ModifyTaskApply";
+import CardRuleSelect from "../../../components/base/CardRuleSelect";
+import CourseSelect from "../../../components/base/CourseSelect";
+import PaperNumberSelect from "../../../components/base/PaperNumberSelect";
+import QuestionTeacherSelect from "../../../components/base/QuestionTeacherSelect";
+import { AUDITING_RESULT, AUDITING_STATUS } from "@/constants/enumerate";
+import { taskReviewListPage, batchUpdateTaskReview } from "../api";
+import { download, randomCode } from "@/plugins/utils";
+
+export default {
+  name: "task-review-manage",
+  components: {
+    ModifyTaskApply,
+    CardRuleSelect,
+    CourseSelect,
+    PaperNumberSelect,
+    QuestionTeacherSelect
+  },
+  data() {
+    return {
+      auditStatus: "NOT_AUDITED",
+      filter: {
+        userId: "",
+        cardRuleId: "",
+        courseCode: "",
+        paperNumber: "",
+        startTime: null,
+        endTime: null
+      },
+      createTime: [],
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      viewType: "APPLY",
+      AUDITING_RESULT,
+      AUDITING_STATUS,
+      caches: {},
+      examTasks: [],
+      curExamTask: {},
+      multipleSelection: [],
+      loading: false,
+      // audit-dialog
+      auditDialogShow: false,
+      auditModal: { reason: "" },
+      auditRules: {
+        reason: [
+          { required: true, message: "请输入审核意见", trigger: "change" }
+        ]
+      },
+      // date-picker
+      pickerOptions: {
+        shortcuts: [
+          {
+            text: "最近一周",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
+              picker.$emit("pick", [start, end]);
+            }
+          },
+          {
+            text: "最近一个月",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
+              picker.$emit("pick", [start, end]);
+            }
+          },
+          {
+            text: "最近三个月",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
+              picker.$emit("pick", [start, end]);
+            }
+          }
+        ]
+      }
+    };
+  },
+  computed: {
+    AUDITED() {
+      return this.auditStatus === "AUDITED";
+    }
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    async getList() {
+      const datas = {
+        ...this.filter,
+        auditStatus: this.auditStatus,
+        pageNumber: this.current,
+        pageSize: this.size
+      };
+      if (this.createTime) {
+        datas.startTime = this.createTime[0];
+        datas.endTime = this.createTime[1];
+      }
+      const data = await taskReviewListPage(datas);
+      this.examTasks = data.records;
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    handleSelectionChange(val) {
+      this.multipleSelection = val.map(item => item.id);
+      console.log(this.multipleSelection);
+    },
+    selectMenu(val) {
+      this.caches[this.auditStatus] = {
+        current: this.current,
+        examTasks: this.examTasks,
+        total: this.total
+      };
+      this.auditStatus = val;
+      if (this.caches[val] && this.caches[val].examTasks) {
+        this.current = this.caches[val].current;
+        this.total = this.caches[val].total;
+        this.examTasks = this.caches[val].examTasks;
+      } else {
+        this.toPage(1);
+      }
+    },
+    toBatchAudit(isPass) {
+      if (!this.multipleSelection.length) {
+        this.$message.error("请选择要审核的记录!");
+        return;
+      }
+      if (isPass) {
+        this.auditApply("PASS", this.multipleSelection);
+      } else {
+        this.auditModal.reason = "";
+        this.auditDialogShow = true;
+      }
+    },
+    async auditNotPass() {
+      const valid = await this.$refs.AuditForm.validate().catch(() => {});
+      if (!valid) return;
+
+      await this.auditApply("NOT_PASS", this.multipleSelection);
+      this.auditDialogShow = false;
+    },
+    async auditApply(type, ids) {
+      const data = await batchUpdateTaskReview({
+        reviewStatus: type,
+        examTaskId: ids,
+        reason: this.auditModal.reason
+      }).catch(() => {});
+      if (!data) return;
+      this.$message.success("审核成功!");
+      this.getList();
+    },
+    async toExport() {
+      if (this.loading) return;
+
+      if (!this.multipleSelection.length) {
+        this.$message.error("请选择要导出的记录!");
+        return;
+      }
+
+      this.loading = true;
+      const res = await download({
+        type: "post",
+        url: this.GLOBAL.domain + "/api/exam/task_review/export",
+        data: {
+          examTaskIds: this.multipleSelection
+        },
+        fileName: `${Date.now()}${randomCode()}.zip`,
+        header: {
+          schoolId: this.$ls.get("schoolId"),
+          userId: this.$ls.get("user", { id: "" }).id
+        }
+      }).catch(error => {
+        this.$message.error(error + "文件下载失败,请重新尝试!");
+      });
+      this.loading = false;
+      if (!res) return;
+      this.$message.success("文件下载成功!");
+    },
+    toEdit(row) {
+      this.curExamTask = row;
+      this.viewType = "AUDIT";
+      this.$refs.ModifyTaskApply.open();
+    },
+    toPreview(row) {
+      this.curExamTask = row;
+      this.viewType = "PREVIEW";
+      this.$refs.ModifyTaskApply.open();
+    }
+  }
+};
+</script>

+ 15 - 0
src/modules/exam/views/WaitTask.vue

@@ -0,0 +1,15 @@
+<template>
+  <div class="WaitTask">
+    WaitTask
+  </div>
+</template>
+
+<script>
+export default {
+  name: "WaitTask",
+  data() {
+    return {};
+  },
+  methods: {}
+};
+</script>

+ 2 - 2
src/modules/login/api.js

@@ -1,7 +1,7 @@
 import { $post, $get } from "@/plugins/axios";
 
 export const login = datas => {
-  return $post("/api/print/basic/user/login", datas);
+  return $post("/api/common/login", datas);
 };
 export const getSmsCode = datas => {
   return $post("/api/print/basic/user/getVerifyCode", datas);
@@ -13,5 +13,5 @@ export const sysMenu = () => {
   return $get("/api/print/basic/sys/getMenu", {});
 };
 export const attachmentPreview = id => {
-  return $get("/api/common/file/getOne", { id });
+  return $get("/api/common/file/get_one", { id });
 };

+ 0 - 0
src/modules/print/api.js


+ 40 - 0
src/modules/print/router.js

@@ -0,0 +1,40 @@
+// print
+import PrintPlanManage from "./views/PrintPlanManage.vue";
+import BusinessDataExport from "./views/BusinessDataExport.vue";
+import BusinessDataDetail from "./views/BusinessDataDetail.vue";
+import PlanLinkPaper from "./views/PlanLinkPaper.vue";
+import PrintTaskManage from "./views/PrintTaskManage.vue";
+import PrintProgressManage from "./views/PrintProgressManage.vue";
+
+export default [
+  {
+    path: "/print/print-plan-manage",
+    name: "PrintPlanManage",
+    component: PrintPlanManage
+  },
+  {
+    path: "/print/business-data-export",
+    name: "BusinessDataExport",
+    component: BusinessDataExport
+  },
+  {
+    path: "/print/business-data-detail",
+    name: "BusinessDataDetail",
+    component: BusinessDataDetail
+  },
+  {
+    path: "/print/plan-link-paper",
+    name: "PlanLinkPaper",
+    component: PlanLinkPaper
+  },
+  {
+    path: "/print/print-task-manage",
+    name: "PrintTaskManage",
+    component: PrintTaskManage
+  },
+  {
+    path: "/print/print-progress-manage",
+    name: "PrintProgressManage",
+    component: PrintProgressManage
+  }
+];

+ 15 - 0
src/modules/print/views/BusinessDataDetail.vue

@@ -0,0 +1,15 @@
+<template>
+  <div class="BusinessDataDetail">
+    BusinessDataDetail
+  </div>
+</template>
+
+<script>
+export default {
+  name: "BusinessDataDetail",
+  data() {
+    return {};
+  },
+  methods: {}
+};
+</script>

+ 15 - 0
src/modules/print/views/BusinessDataExport.vue

@@ -0,0 +1,15 @@
+<template>
+  <div class="BusinessDataExport">
+    BusinessDataExport
+  </div>
+</template>
+
+<script>
+export default {
+  name: "BusinessDataExport",
+  data() {
+    return {};
+  },
+  methods: {}
+};
+</script>

+ 15 - 0
src/modules/print/views/PlanLinkPaper.vue

@@ -0,0 +1,15 @@
+<template>
+  <div class="PlanLinkPaper">
+    PlanLinkPaper
+  </div>
+</template>
+
+<script>
+export default {
+  name: "PlanLinkPaper",
+  data() {
+    return {};
+  },
+  methods: {}
+};
+</script>

+ 15 - 0
src/modules/print/views/PrintPlanManage.vue

@@ -0,0 +1,15 @@
+<template>
+  <div class="print-plan-manage">
+    print-plan-manage
+  </div>
+</template>
+
+<script>
+export default {
+  name: "print-plan-manage",
+  data() {
+    return {};
+  },
+  methods: {}
+};
+</script>

+ 15 - 0
src/modules/print/views/PrintProgressManage.vue

@@ -0,0 +1,15 @@
+<template>
+  <div class="PrintProgressManage">
+    PrintProgressManage
+  </div>
+</template>
+
+<script>
+export default {
+  name: "PrintProgressManage",
+  data() {
+    return {};
+  },
+  methods: {}
+};
+</script>

+ 15 - 0
src/modules/print/views/PrintTaskManage.vue

@@ -0,0 +1,15 @@
+<template>
+  <div class="PrintTaskManage">
+    PrintTaskManage
+  </div>
+</template>
+
+<script>
+export default {
+  name: "PrintTaskManage",
+  data() {
+    return {};
+  },
+  methods: {}
+};
+</script>

+ 26 - 6
src/plugins/filters.js

@@ -1,18 +1,38 @@
 import Vue from "vue";
-import { ABLE_TYPE, TEMPLATE_CLASSIFY } from "../constants/enumerate";
+import {
+  TEMPLATE_CLASSIFY,
+  EXAM_TASK_STATUS,
+  AUDITING_STATUS,
+  AUDITING_RESULT
+} from "../constants/enumerate";
+import { formatDate } from "../plugins/utils";
+
+const DEFAULT_FIELD = "--";
 
 Vue.filter("enableFilter", function(val) {
-  return ABLE_TYPE[val];
+  return val ? "启用" : "禁用";
 });
 Vue.filter("templateClassifyFilter", function(val) {
-  return TEMPLATE_CLASSIFY[val];
+  return TEMPLATE_CLASSIFY[val] || DEFAULT_FIELD;
 });
 Vue.filter("orgsFilter", function(val) {
-  return val.map(item => item.orgName).join(",");
+  return val.map(item => item.name).join(",");
 });
 Vue.filter("rolesFilter", function(val) {
-  return val.map(item => item.roleName).join(",");
+  return val.map(item => item.name).join(",");
 });
 Vue.filter("coursesFilter", function(val) {
-  return val.map(item => `${item.courseName}(${item.courseCode})`).join(",");
+  return val.map(item => `${item.name}(${item.code})`).join(",");
+});
+Vue.filter("examTaskStatusFilter", function(val) {
+  return EXAM_TASK_STATUS[val] || DEFAULT_FIELD;
+});
+Vue.filter("auditStatusFilter", function(val) {
+  return AUDITING_STATUS[val] || DEFAULT_FIELD;
+});
+Vue.filter("reviewStatusFilter", function(val) {
+  return AUDITING_RESULT[val] || DEFAULT_FIELD;
+});
+Vue.filter("timestampFilter", function(val) {
+  return val ? formatDate("YYYY-MM-DD HH:mm:ss", new Date(val)) : DEFAULT_FIELD;
 });

+ 3 - 4
src/router.js

@@ -7,9 +7,8 @@ import NotFound from "./views/404.vue";
 import login from "./modules/login/router";
 // module-example
 import base from "./modules/base/router";
-// import examCenter from "./modules/exam-center/router";
-// import scorePaper from "./modules/score-paper/router";
-// import analyze from "./modules/analyze/router";
+import exam from "./modules/exam/router";
+import print from "./modules/print/router";
 // card part
 import card from "./modules/card/router";
 
@@ -39,7 +38,7 @@ export default new Router({
       path: "/home",
       name: "Home",
       component: Home,
-      children: [...base]
+      children: [...base, ...exam, ...print]
     },
     { ...login },
     ...card,

+ 1 - 1
src/views/Home.vue

@@ -163,7 +163,7 @@ export default {
       this.menus = localNavs;
       if (this.$route.name === "Home") {
         this.$router.replace({
-          name: "UserManage"
+          name: "ExamTaskManage"
         });
       } else {
         this.actCurNav();