فهرست منبع

Merge branch 'dev'

zhangjie 4 سال پیش
والد
کامیت
ad24ffdea2
36فایلهای تغییر یافته به همراه1376 افزوده شده و 163 حذف شده
  1. BIN
      public/temps/扩展字段.xlsx
  2. BIN
      public/temps/考务导入模板.xlsx
  3. BIN
      public/temps/考务编排导入模板.xlsx
  4. BIN
      public/temps/考试计划导入模板.xlsx
  5. 1 1
      src/assets/styles/adaptive.scss
  6. 5 0
      src/assets/styles/base.scss
  7. 20 7
      src/assets/styles/element-ui-costom.scss
  8. 0 1
      src/assets/styles/home.scss
  9. 7 0
      src/assets/styles/login.scss
  10. 21 0
      src/assets/styles/pages.scss
  11. 3 1
      src/constants/enumerate.js
  12. 18 0
      src/constants/navs.js
  13. 0 4
      src/modules/card/api.js
  14. 1 1
      src/modules/card/router.js
  15. 9 4
      src/modules/card/views/CardDesign.vue
  16. 25 9
      src/modules/card/views/CardPreview.vue
  17. 35 0
      src/modules/exam-center/api.js
  18. 1 1
      src/modules/exam-center/components/BusinessData.vue
  19. 2 0
      src/modules/exam-center/components/CardOptionDialog.vue
  20. 47 0
      src/modules/exam-center/components/ExamPlanData.vue
  21. 6 1
      src/modules/exam-center/components/UploadFileView.vue
  22. 26 7
      src/modules/exam-center/components/UploadPaperDialog.vue
  23. 29 0
      src/modules/exam-center/router.js
  24. 1 1
      src/modules/exam-center/views/CardAudit.vue
  25. 91 26
      src/modules/exam-center/views/CardManage.vue
  26. 23 1
      src/modules/exam-center/views/DoneTaskDetail.vue
  27. 120 33
      src/modules/exam-center/views/ExamModify.vue
  28. 1 1
      src/modules/exam-center/views/ExamRomeStudentDetail.vue
  29. 26 11
      src/modules/exam-center/views/PrintManage.vue
  30. 147 0
      src/modules/exam-center/views/TodoExam.vue
  31. 338 0
      src/modules/exam-center/views/TopicTaskManage.vue
  32. 222 0
      src/modules/exam-center/views/TopicTaskModify.vue
  33. 4 1
      src/modules/exam-center/views/WaitTask.vue
  34. 117 51
      src/modules/exam-center/views/WaitTaskDetail.vue
  35. 13 0
      src/router.js
  36. 17 1
      src/views/Home.vue

BIN
public/temps/扩展字段.xlsx


BIN
public/temps/考务导入模板.xlsx


BIN
public/temps/考务编排导入模板.xlsx


BIN
public/temps/考试计划导入模板.xlsx


+ 1 - 1
src/assets/styles/adaptive.scss

@@ -98,7 +98,7 @@
     font-size: 14px;
   }
   .el-button {
-    padding: 10px 15px;
+    padding: 9px 15px;
   }
   .el-input__inner {
     height: 36px;

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

@@ -167,3 +167,8 @@ body {
     color: $--color-success;
   }
 }
+.tb-action-column {
+  .cell {
+    overflow: visible;
+  }
+}

+ 20 - 7
src/assets/styles/element-ui-costom.scss

@@ -112,13 +112,6 @@
     border-color: #ddd;
     background-color: #f5f5f5;
   }
-  .el-input__prefix {
-    left: 9px;
-    > i {
-      height: 12px;
-      width: 12px;
-    }
-  }
 }
 // upload
 .el-upload,
@@ -129,6 +122,7 @@
 // button
 .el-button {
   border-radius: $--border-radius;
+
   > .icon {
     margin-right: 5px;
   }
@@ -261,6 +255,11 @@
   .el-table__row.row-danger {
     color: $--color-danger;
   }
+  &.el-table--noback {
+    tr.el-table__row {
+      background-color: $--color-white;
+    }
+  }
 }
 // el-checkbox
 .el-checkbox {
@@ -442,3 +441,17 @@
     }
   }
 }
+
+// el-date-editor
+.el-date-editor {
+  .el-range-separator {
+    width: auto;
+    line-height: 28px;
+  }
+  .el-range-input {
+    background-color: transparent;
+  }
+  .el-input__icon {
+    line-height: 28px;
+  }
+}

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

@@ -410,7 +410,6 @@
 // other
 .tips-info {
   font-size: 14px;
-  height: 25px;
   line-height: 25px;
   color: $--color-text-secondary;
 }

+ 7 - 0
src/assets/styles/login.scss

@@ -87,4 +87,11 @@
   .el-form-item:last-child {
     margin-bottom: 0;
   }
+  .el-input__prefix {
+    left: 9px;
+    > i {
+      height: 12px;
+      width: 12px;
+    }
+  }
 }

+ 21 - 0
src/assets/styles/pages.scss

@@ -21,6 +21,17 @@
     &-infos {
       float: right;
     }
+    &-add {
+      margin-left: 15px;
+      cursor: pointer;
+      i {
+        margin-right: 5px;
+      }
+
+      &:hover {
+        color: #333;
+      }
+    }
     .el-checkbox__label {
       font-size: 16px;
       color: $--color-text-primary !important;
@@ -142,3 +153,13 @@
     margin-bottom: 5px;
   }
 }
+
+// page-preview
+.preview-frame {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 99;
+}

+ 3 - 1
src/constants/enumerate.js

@@ -132,5 +132,7 @@ export const MENU_ROUTER_DICT = {
   printManager: "PrintManage",
   cardAuditingManager: "CardAudit",
   courseManager: "CourseManage",
-  examTaskAudit: "ExamTaskAudit"
+  examTaskAudit: "ExamTaskAudit",
+  topicTaskManage: "TopicTaskManage",
+  todoExam: "TodoExam"
 };

+ 18 - 0
src/constants/navs.js

@@ -74,6 +74,24 @@ const navs = [
       {
         title: "考试任务审核",
         router: "ExamTaskAudit"
+      },
+      {
+        title: "命题任务管理",
+        router: "TopicTaskManage",
+        children: [
+          {
+            title: "编辑任务",
+            router: "TopicTaskEdit"
+          },
+          {
+            title: "新增任务",
+            router: "TopicTaskAdd"
+          }
+        ]
+      },
+      {
+        title: "待办任务",
+        router: "TodoExam"
       }
     ]
   },

+ 0 - 4
src/modules/card/api.js

@@ -50,7 +50,3 @@ export const saveCard = datas => {
 export const submitCard = datas => {
   return $post("/api/print/card/submit", datas);
 };
-
-export const cardStudentInfo = ({ cardId, studentNo }) => {
-  return $get("/backend/sysuser/resetPwd", { cardId, studentNo });
-};

+ 1 - 1
src/modules/card/router.js

@@ -7,7 +7,7 @@ export default [
   },
   {
     // viewType::: view:预览,print:打印,frame:iframe嵌套
-    path: "/card/preview/:cardId/:viewType/:studentNo?",
+    path: "/card/preview/:cardId/:viewType",
     name: "CardPreview",
     component: () =>
       import(/* webpackChunkName: "CardPreview" */ "./views/CardPreview.vue")

+ 9 - 4
src/modules/card/views/CardDesign.vue

@@ -396,7 +396,7 @@ export default {
         // 没有题卡内容时,直接创建新的内容
         this.setCardConfig({
           cardName: detData.title,
-          aOrB: detData.enablePaperType === "A,B"
+          aOrB: detData.enablePaperType.split(",").length > 1
         });
         this.initPageData();
       }
@@ -483,11 +483,15 @@ export default {
     },
     // save
     getCardData(contentTemp = "") {
+      const multiEnablePaperType =
+        this.prepareTcPCard["enablePaperType"] &&
+        this.prepareTcPCard["enablePaperType"].split(",").length > 1
+          ? this.prepareTcPCard["enablePaperType"]
+          : "";
       const tcPCard = this.$objAssign(
         {
           examId: "",
           enablePaperType: "A",
-          // paperAttachmentId: "",
           courseName: "",
           courseCode: "",
           cardSource: "",
@@ -497,8 +501,9 @@ export default {
         {
           ...this.prepareTcPCard,
           title: this.cardConfig.cardName,
-          // todo:A,B最好前端配置一下,以便支持扩展
-          enablePaperType: this.cardConfig.aOrB ? "A,B" : "A"
+          enablePaperType: this.cardConfig.aOrB
+            ? multiEnablePaperType || "A,B"
+            : "A"
         }
       );
 

+ 25 - 9
src/modules/card/views/CardPreview.vue

@@ -70,13 +70,14 @@
         </div>
       </template>
     </div>
+    <div class="preview-frame" id="preview-frame" v-if="showHtmlTemp"></div>
   </div>
 </template>
 
 <script>
 import TopicElementPreview from "../components/TopicElementPreview";
 import PageNumber from "../components/pageInfo/PageNumber";
-import { cardTempDetail, cardStudentInfo } from "../api";
+import { cardTempDetail } from "../api";
 import previewTemp from "../previewTemp";
 const JsBarcode = require("jsbarcode");
 
@@ -88,10 +89,10 @@ export default {
       isPrint: this.$route.params.viewType !== "view",
       isFrame: this.$route.params.viewType === "frame",
       isFrameView: this.$route.params.viewType === "frame-view",
-      studentNo: this.$route.params.studentNo,
       cardId: this.$route.params.cardId,
       cardConfig: {},
-      pages: []
+      pages: [],
+      showHtmlTemp: false
     };
   },
   computed: {
@@ -173,19 +174,34 @@ export default {
         this.$message.error("很抱歉,当前题卡还没开始制作!");
         return;
       }
-      let stdInfo = {};
-      if (this.studentNo) {
-        stdInfo = await cardStudentInfo({
-          cardId: this.cardId,
-          studentNo: this.studentNo
+      if (!tempData.content) {
+        if (!tempData.contentTemp) {
+          this.$message.error("当前题卡详情数据错误!");
+          return;
+        }
+        this.showHtmlTemp = true;
+        this.$nextTick(() => {
+          this.initHtmlTemp(tempData.contentTemp);
         });
+        return;
       }
       const { cardConfig, pages } = JSON.parse(tempData.content);
-      const fieldInfos = this.fetchFieldInfos(cardConfig, stdInfo);
+      const fieldInfos = this.fetchFieldInfos(cardConfig, {});
 
       this.cardConfig = cardConfig;
       this.pages = this.appendFieldInfo(pages, fieldInfos);
     },
+    initHtmlTemp(htmlTemp) {
+      const iframeDom = document.createElement("iframe");
+      document.getElementById("preview-frame").appendChild(iframeDom);
+      const wwidth = window.innerWidth - 10;
+      const wheight = window.innerHeight - 10;
+      iframeDom.style.cssText = `width: ${wwidth}px;height: ${wheight}px;`;
+      const iframeDoc = iframeDom.contentDocument;
+      iframeDoc.open();
+      iframeDoc.write(htmlTemp);
+      iframeDoc.close();
+    },
     fetchFieldInfos(cardConfig, stdInfo) {
       let fieldInfos = {};
       const defContent = "相关信息";

+ 35 - 0
src/modules/exam-center/api.js

@@ -65,10 +65,18 @@ export const deleteExam = examId => {
   return $post("/api/print/exam/exam/delete", { examId });
 };
 
+// todo-exam
+export const todoExamList = datas => {
+  return $get("/api/print/exam/exam/listExams", datas);
+};
+
 // course
 export const courseList = () => {
   return $get("/api/print/basic/sys/courseList", {});
 };
+export const examCourseList = examId => {
+  return $get("/api/print/exam/examTask/listCourseByExamId", { examId });
+};
 
 // card-manage
 export const cardList = datas => {
@@ -126,6 +134,33 @@ export const examTaskAudtiHistory = taskId => {
   return $get("/api/print/exam/taskAudit/listHis", { taskId });
 };
 
+// topic-task-manage
+export const topicTaskListPage = datas => {
+  return $get("/api/print/exam/examTask/listTaskPage", datas);
+};
+export const topicTaskDetail = taskId => {
+  return $get("/api/print/exam/examTask/preUpdate", { taskId });
+};
+export const topicTaskExamList = () => {
+  return $get("/api/print/exam/examTask/exams", {});
+};
+export const topicTaskExamCourseList = examId => {
+  return $get("/api/print/exam/examTask/courses", { examId });
+};
+export const topicTaskExamTeacherList = courseCode => {
+  return $get("/api/print/exam/examTask/users", { courseCode });
+};
+export const eableTopicTask = datas => {
+  return $post("/api/print/exam/examTask/enable", datas);
+};
+export const updateTopicTask = datas => {
+  if (datas.taskId) {
+    return $post("/api/print/exam/examTask/update", datas);
+  } else {
+    return $post("/api/print/exam/examTask/add", datas);
+  }
+};
+
 // custom upload-file
 export const customUpload = options => {
   let formData = new FormData();

+ 1 - 1
src/modules/exam-center/components/BusinessData.vue

@@ -29,7 +29,7 @@
       ></el-table-column>
     </el-table>
     <div class="table-tips">
-      将导入 <span>{{ total }}</span> 数据
+      将导入 <span>{{ total }}</span> 数据
     </div>
     <div class="part-page">
       <el-pagination

+ 2 - 0
src/modules/exam-center/components/CardOptionDialog.vue

@@ -102,6 +102,7 @@ export default {
         courseNameCode: "",
         enablePaperType: "",
         paperAttachmentId: "",
+        paperConfirmAttachmentId: "",
         courseName: "",
         courseCode: "",
         cardSource: "",
@@ -187,6 +188,7 @@ export default {
       }
 
       if (this.modalForm.cardSource === "2") {
+        this.$emit("draft-task");
         this.$refs.UploadSamplePaperDialog.open();
         return;
       }

+ 47 - 0
src/modules/exam-center/components/ExamPlanData.vue

@@ -0,0 +1,47 @@
+<template>
+  <el-dialog
+    class="exam-plan-data"
+    :visible.sync="modalIsShow"
+    title="导入考试计划数据预览"
+    top="10vh"
+    width="540px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+  >
+    <el-table ref="TableList" :data="datas" border stripe>
+      <el-table-column prop="courseName" label="科目名称"></el-table-column>
+      <el-table-column prop="courseCode" label="科目代码"></el-table-column>
+    </el-table>
+    <div slot="footer" style="text-align: right">
+      <el-button type="primary" @click="cancel">确定</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+export default {
+  name: "exam-plan-data",
+  props: {
+    datas: {
+      type: Array,
+      default() {
+        return [];
+      }
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false
+    };
+  },
+  methods: {
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    }
+  }
+};
+</script>

+ 6 - 1
src/modules/exam-center/components/UploadFileView.vue

@@ -19,10 +19,11 @@
       :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">选择</el-button>
+      <el-button type="primary" :disabled="disabled">选择</el-button>
     </el-upload>
     <el-button
       type="primary"
@@ -69,6 +70,10 @@ export default {
     addFilenameParam: {
       type: String,
       default: "filename"
+    },
+    disabled: {
+      type: Boolean,
+      default: false
     }
   },
   data() {

+ 26 - 7
src/modules/exam-center/components/UploadPaperDialog.vue

@@ -2,20 +2,20 @@
   <el-dialog
     class="file-upload-view"
     :visible.sync="modalIsShow"
-    title="上传试卷文件"
+    :title="`上传${curConfig.title}`"
     top="10vh"
-    width="685px"
+    width="710px"
     :close-on-click-modal="false"
     :close-on-press-escape="false"
     append-to-body
     @opened="visibleChange"
   >
     <div class="file-upload-body">
-      <el-form label-width="75px" label-position="left">
-        <el-form-item label="试卷文件:">
+      <el-form label-position="left">
+        <el-form-item :label="`${curConfig.title}:`">
           <upload-file-view
             input-width="270px"
-            :format="['doc', 'docx', 'pdf']"
+            :format="curConfig.format"
             :upload-url="uploadUrl"
             @upload-error="uplaodError"
             @upload-success="uploadSuccess"
@@ -43,13 +43,31 @@ export default {
   components: { UploadFileView },
   props: {
     paperAttachment: {
-      type: Object
+      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: {},
       // import
       uploadUrl: "/api/print/basic/sys/saveAttachment"
     };
@@ -60,6 +78,7 @@ export default {
         `${this.paperAttachment.filename || ""}`
       );
       this.attachment = { ...this.paperAttachment };
+      this.curConfig = this.config[this.uploadType];
     },
     // upload-handler
     uplaodError(errorData) {
@@ -82,7 +101,7 @@ export default {
       this.modalIsShow = true;
     },
     confirm() {
-      this.$emit("confirm", this.attachment);
+      this.$emit("confirm", this.attachment, this.uploadType);
       this.cancel();
     },
     async toPreview() {

+ 29 - 0
src/modules/exam-center/router.js

@@ -12,6 +12,9 @@ import ExamRomeStudentDetail from "./views/ExamRomeStudentDetail.vue";
 import CardManage from "./views/CardManage.vue";
 import ExamTaskAudit from "./views/ExamTaskAudit.vue";
 import ExamTaskAuditEdit from "./views/ExamTaskAuditEdit.vue";
+import TopicTaskManage from "./views/TopicTaskManage.vue";
+import TopicTaskModify from "./views/TopicTaskModify.vue";
+import TodoExam from "./views/TodoExam.vue";
 
 export default [
   {
@@ -112,5 +115,31 @@ export default [
     meta: {
       relate: "ExamTaskAudit/ExamTaskAuditEdit"
     }
+  },
+  {
+    path: "/exam-center/topic-task-manage",
+    name: "TopicTaskManage",
+    component: TopicTaskManage
+  },
+  {
+    path: "/exam-center/topic-task-add",
+    name: "TopicTaskAdd",
+    component: TopicTaskModify,
+    meta: {
+      relate: "TopicTaskManage/TopicTaskAdd"
+    }
+  },
+  {
+    path: "/exam-center/topic-task-edit/:taskId",
+    name: "TopicTaskEdit",
+    component: TopicTaskModify,
+    meta: {
+      relate: "TopicTaskManage/TopicTaskEdit"
+    }
+  },
+  {
+    path: "/exam-center/todo-exam",
+    name: "TodoExam",
+    component: TodoExam
   }
 ];

+ 1 - 1
src/modules/exam-center/views/CardAudit.vue

@@ -127,7 +127,7 @@ import { download } from "@/plugins/utils";
 import { auditListPage, schoolList } from "../api";
 
 export default {
-  name: "card-check",
+  name: "card-audit",
   data() {
     return {
       filter: {

+ 91 - 26
src/modules/exam-center/views/CardManage.vue

@@ -43,6 +43,17 @@
           <el-button type="warning" icon="icon icon-plus" @click="toAdd"
             >创建题卡</el-button
           >
+          <upload-button
+            btn-icon="icon icon-upload"
+            btn-type="default"
+            btn-content="导入题卡"
+            :upload-url="uploadUrl"
+            :format="['html']"
+            :upload-data="uploadData"
+            @upload-error="uplaodError"
+            @upload-success="uploadSuccess"
+            v-if="IS_ADMIN"
+          ></upload-button>
         </el-form-item>
       </el-form>
     </div>
@@ -83,30 +94,48 @@
         ></el-table-column>
         <el-table-column label="操作" align="center" width="120">
           <template slot-scope="scope">
-            <el-button
-              class="btn-table-icon"
-              type="text"
-              icon="icon icon-edit"
-              @click="toEdit(scope.row)"
-              title="编辑"
-              v-if="scope.row.cardStatus === 0"
-            ></el-button>
-            <el-button
-              class="btn-table-icon"
-              type="text"
-              icon="icon icon-delete"
-              @click="toDelete(scope.row)"
-              title="删除"
-              v-if="scope.row.cardStatus === 0"
-            ></el-button>
-            <el-button
-              class="btn-table-icon"
-              type="text"
-              icon="icon icon-copy"
-              @click="toCopy(scope.row)"
-              title="复制"
-              v-if="scope.row.auditingStatus !== 0"
-            ></el-button>
+            <div v-if="!scope.row.isImport">
+              <el-button
+                class="btn-table-icon"
+                type="text"
+                icon="icon icon-edit"
+                @click="toEdit(scope.row)"
+                title="编辑"
+                v-if="scope.row.cardStatus === 0"
+              ></el-button>
+              <el-button
+                class="btn-table-icon"
+                type="text"
+                icon="icon icon-delete"
+                @click="toDelete(scope.row)"
+                title="删除"
+                v-if="scope.row.cardStatus === 0"
+              ></el-button>
+              <el-button
+                class="btn-table-icon"
+                type="text"
+                icon="icon icon-copy"
+                @click="toCopy(scope.row)"
+                title="复制"
+                v-if="scope.row.auditingStatus !== 0"
+              ></el-button>
+            </div>
+            <div v-else>
+              <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-copy"
+                @click="toCopyCommon(scope.row)"
+                title="复制"
+              ></el-button>
+            </div>
           </template>
         </el-table-column>
       </el-table>
@@ -145,11 +174,13 @@ import {
 import { cardConfigInfos, cardTempDetail } from "@/modules/card/api";
 import { transformField } from "@/modules/card/enumerate";
 import CardOptionDialog from "../components/CardOptionDialog";
+import UploadButton from "@/components/UploadButton";
 
 export default {
   name: "card-manage",
   components: {
-    CardOptionDialog
+    CardOptionDialog,
+    UploadButton
   },
   data() {
     return {
@@ -163,10 +194,20 @@ export default {
       total: 0,
       visible: false,
       AUDITING_STATUS,
-      cards: []
+      cards: [],
+      // import card
+      IS_ADMIN: false,
+      uploadData: {
+        schoolId: this.$ls.get("schoolId"),
+        userId: this.$ls.get("user", { id: "" }).id
+      },
+      uploadUrl: "/api/print/card/card/impHtml"
     };
   },
   created() {
+    const roleCode = this.$ls.get("user", { roleCode: "" }).roleCode;
+    this.IS_ADMIN =
+      roleCode.includes("ADMIN") || roleCode.includes("SUPER_ADMIN");
     this.getList();
   },
   methods: {
@@ -257,6 +298,22 @@ export default {
 
       this.getList();
     },
+    toPreview(row) {
+      window.open(
+        this.getRouterPath({
+          name: "CardPreview",
+          params: {
+            cardId: row.id,
+            viewType: "view"
+          }
+        })
+      );
+    },
+    async toCopyCommon(row) {
+      await copyCard(row.id);
+      this.$message.success("复制成功!");
+      this.getList();
+    },
     compareCardConfig(sysConfig, curConfig) {
       let isChange = false;
       Object.keys(sysConfig).forEach(key => {
@@ -307,6 +364,14 @@ export default {
           this.deletePageLastItem();
         })
         .catch(() => {});
+    },
+    // import card
+    uplaodError(errorData) {
+      this.$notify.error({ title: "错误提示", message: errorData.message });
+    },
+    uploadSuccess(res) {
+      this.$message.success("上传成功!");
+      this.getList();
     }
   },
   beforeRouteLeave(to, from, next) {

+ 23 - 1
src/modules/exam-center/views/DoneTaskDetail.vue

@@ -3,7 +3,7 @@
     <div class="task-title">
       <h2>
         <span>考试名称: {{ task.examName }} </span>
-        <span>科目名称:{{ task.courseName }}</span>
+        <span>科目名称:{{ task.courseNameCode }}</span>
       </h2>
     </div>
     <div class="task-body">
@@ -11,6 +11,7 @@
         <tr>
           <th>试卷类型</th>
           <th v-if="task.examPaper">试卷文件</th>
+          <th v-if="task.examPaper">试卷审核确认书</th>
           <th v-if="task.answerSheet">答题卡</th>
           <!-- <th>标答</th>
           <th>试卷结构</th> -->
@@ -25,6 +26,16 @@
               <i class="icon icon-download-act"></i>{{ attachment.filename }}
             </span>
           </td>
+          <td
+            class="td-link"
+            :rowspan="pTypeEnable ? paperAttachments.length : 1"
+            v-if="index === 0 && task.examPaper"
+          >
+            <span @click="downloadPaperConfirm" title="点击下载确认书文件">
+              <i class="icon icon-download-act"></i
+              >{{ paperConfirmAttachmentId.filename }}
+            </span>
+          </td>
           <td
             class="td-link"
             :rowspan="pTypeEnable ? paperAttachments.length : 1"
@@ -73,6 +84,7 @@ export default {
       taskId: this.$route.params.taskId,
       task: {},
       pTypeEnable: false,
+      paperConfirmAttachmentId: {},
       paperAttachments: [],
       curAttachment: {}
     };
@@ -90,6 +102,9 @@ export default {
       });
       this.pTypeEnable = this.task.enablePaperType.split(",").length > 1;
       this.parsePaperAttachment();
+      this.paperConfirmAttachmentId = this.task.paperConfirmAttachmentId
+        ? JSON.parse(this.task.paperConfirmAttachmentId)
+        : { attachmentId: "", filename: "" };
     },
     parsePaperAttachment() {
       const paperAttachment =
@@ -112,6 +127,13 @@ export default {
       const data = await attachmentPreview(attachment.attachmentId);
       window.open(data.path);
     },
+    async downloadPaperConfirm() {
+      if (!this.paperConfirmAttachmentId.attachmentId) return;
+      const data = await attachmentPreview(
+        this.paperConfirmAttachmentId.attachmentId
+      );
+      window.open(data.path);
+    },
     toPreviewCard() {
       window.open(
         this.getRouterPath({

+ 120 - 33
src/modules/exam-center/views/ExamModify.vue

@@ -15,35 +15,65 @@
           clearable
         ></el-input>
       </el-form-item>
-      <el-form-item prop="examCodeTemp" label="考务文件:">
+      <el-form-item prop="examPlanCodeTemp" label="考试计划表:">
         <upload-file-view
-          :upload-url="uploadUrl"
+          :upload-url="uploadExamPlanUrl"
+          :upload-data="uploadExamPlanData"
+          :disabled="!canEdit"
+          @upload-error="uplaodError"
+          @upload-success="uploadExamPlanSuccess"
+          ref="ExamPlanUploadFileView"
+        ></upload-file-view>
+        <el-button @click="toPreviewExamPlan" style="margin-left:16px;"
+          >预览</el-button
+        >
+        <el-button
+          type="warning"
+          icon="icon icon-download"
+          style="margin-left:10px;"
+        >
+          <a :href="planDownloadUrl" download="考试计划导入模板.xlsx"
+            >考试计划导入模板下载</a
+          >
+        </el-button>
+      </el-form-item>
+      <el-form-item label="考务编排文件:">
+        <upload-file-view
+          :upload-url="uploadExamBusinessUrl"
           :upload-data="uploadData"
+          :disabled="!modalForm.examPlanAttachmentId"
           @upload-error="uplaodError"
-          @upload-success="uploadSuccess"
-          ref="UploadFileView"
+          @upload-success="uploadExamBusinessSuccess"
+          ref="ExamBusinessUploadFileView"
         ></upload-file-view>
-        <el-button @click="toPreview" style="margin-left:16px;">预览</el-button>
+        <el-button @click="toPreviewExamBusiness" style="margin-left:16px;"
+          >预览</el-button
+        >
         <el-button
           type="warning"
-          icon="icon icon-plus"
+          icon="icon icon-download"
           style="margin-left:10px;"
         >
-          <a :href="downloadUrl" download="考务导入模板.xlsx">考务模板下载</a>
+          <a :href="businessDownloadUrl" download="考务编排导入模板.xlsx"
+            >考务编排导入模板下载</a
+          >
         </el-button>
       </el-form-item>
-      <el-form-item label="考试开始时间:">
-        <p class="form-item-content" v-if="modalForm.beginTime">
-          {{ modalForm.beginTime }}
-        </p>
-        <p class="color-info" v-else>请上传考务文件</p>
+      <el-form-item prop="beginTime" label="考试开始时间:">
+        <el-date-picker
+          v-model="modalForm.beginTime"
+          type="datetime"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          placeholder="请选择时间"
+        >
+        </el-date-picker>
       </el-form-item>
       <el-form-item prop="printTime" label="打印时间:">
         <el-date-picker
           v-model="modalForm.printTime"
           type="datetime"
           value-format="yyyy-MM-dd HH:mm:ss"
-          placeholder="请选择日期"
+          placeholder="请选择时间"
         >
         </el-date-picker>
       </el-form-item>
@@ -123,7 +153,12 @@
         </el-popover>
       </el-form-item>
       <el-form-item prop="teacher" label="指派命题老师:">
-        <el-table :data="courses" style="width: 420px;" border stripe>
+        <el-table
+          class="el-table--noback"
+          :data="courses"
+          style="width: 420px;"
+          border
+        >
           <el-table-column prop="courseName" label="科目"></el-table-column>
           <el-table-column label="命题老师" width="180px">
             <template slot-scope="scope">
@@ -132,6 +167,7 @@
                 size="small"
                 style="width: 156px;"
                 placeholder="请选择"
+                :disabled="!scope.row.canEdit"
               >
                 <el-option
                   v-for="user in scope.row.users"
@@ -159,6 +195,8 @@
       :exam-code="modalForm.examCodeTemp"
       ref="BusinessData"
     ></business-data>
+    <!-- exam-plan -->
+    <exam-plan-data :datas="courses" ref="ExamPlanData"></exam-plan-data>
   </div>
 </template>
 
@@ -170,11 +208,12 @@ import {
 } from "@/constants/enumerate";
 import { uploadExam, examDetail } from "../api";
 import BusinessData from "../components/BusinessData";
+import ExamPlanData from "../components/ExamPlanData";
 import UploadFileView from "../components/UploadFileView";
 
 export default {
   name: "exam-modify",
-  components: { BusinessData, UploadFileView },
+  components: { BusinessData, ExamPlanData, UploadFileView },
   data() {
     const printTimeValidator = (rule, value, callback) => {
       if (!this.checkDateRangeValid(value, this.modalForm.beginTime)) {
@@ -206,6 +245,7 @@ export default {
     return {
       examId: this.$route.params.examId,
       modalForm: {
+        id: null,
         examName: "",
         beginTime: "",
         printTime: "",
@@ -213,6 +253,8 @@ export default {
         backup: 0,
         review: 0,
         backupCard: "",
+        examPlanCodeTemp: "",
+        examPlanAttachmentId: "",
         examCodeTemp: "",
         attachmentId: ""
       },
@@ -224,10 +266,17 @@ export default {
             trigger: "change"
           }
         ],
-        examCodeTemp: [
+        examPlanCodeTemp: [
           {
             required: true,
-            message: "请上传考务文件",
+            message: "请上传考试计划表",
+            trigger: "change"
+          }
+        ],
+        beginTime: [
+          {
+            required: true,
+            message: "请选择考试开始时间",
             trigger: "change"
           }
         ],
@@ -279,11 +328,18 @@ export default {
       printContentCheckAll: false,
       courses: [],
       isSubmit: false,
-      downloadUrl: this.GLOBAL.domain + "/temps/考务导入模板.xlsx",
+      canEdit: true,
+      planDownloadUrl: this.GLOBAL.domain + "/temps/考试计划导入模板.xlsx",
+      businessDownloadUrl: this.GLOBAL.domain + "/temps/考务编排导入模板.xlsx",
       // import
-      uploadUrl: "/api/print/exam/exam/impExamData",
-      uploadData: {
+      uploadExamPlanUrl: "/api/print/exam/exam/impExamPlanData",
+      uploadExamBusinessUrl: "/api/print/exam/exam/impExamData",
+      uploadExamPlanData: {
         examId: ""
+      },
+      uploadData: {
+        examId: "",
+        examPlanCodeTemp: ""
       }
     };
   },
@@ -299,20 +355,34 @@ export default {
     if (this.isEdit) {
       this.getExamDetail();
       this.uploadData.examId = this.examId;
+      this.uploadExamPlanData.examId = this.examId;
     }
   },
   methods: {
     async getExamDetail() {
       const data = await examDetail(this.examId);
-      this.modalForm = Object.assign({}, this.modalForm, data.tcPExam);
+      this.modalForm = this.$objAssign(this.modalForm, data.tcPExam);
       this.modalForm.printContent = this.printContentAll.filter(
         key => data.tcPExam[key]
       );
-      this.courses = data.userCourses;
+      this.courses = data.userCourses.map(item => {
+        item.canEdit = item.status === "未开始";
+        return item;
+      });
+      this.canEdit = !this.courses.some(item => !item.canEdit);
       this.handleCheckedChange();
-      this.$refs.UploadFileView.setAttachmentName(
-        `${data.tcPAttachment.name}${data.tcPAttachment.type}`
+      this.$refs.ExamPlanUploadFileView.setAttachmentName(
+        `${data.tcPPlanAttachment.name}${data.tcPPlanAttachment.type}`
       );
+      if (
+        data.tcPAttachment &&
+        data.tcPAttachment["name"] &&
+        data.tcPAttachment["type"]
+      )
+        this.$refs.ExamBusinessUploadFileView.setAttachmentName(
+          `${data.tcPAttachment.name}${data.tcPAttachment.type}`
+        );
+      this.uploadData.examPlanCodeTemp = this.modalForm.examPlanCodeTemp;
     },
     checkDateRangeValid(startTime, endTime) {
       if (startTime && endTime) {
@@ -334,8 +404,8 @@ export default {
         this.modalForm.printContent.length === this.printContentAll.length;
     },
     async save() {
-      // const valid = await this.$refs["ModalForm"].validate().catch(() => {});
-      // if (!valid) return;
+      const valid = await this.$refs["ModalForm"].validate().catch(() => {});
+      if (!valid) return;
 
       if (this.isSubmit) return;
       this.isSubmit = true;
@@ -363,9 +433,16 @@ export default {
       this.$message.success("保存成功!");
       this.goback();
     },
-    toPreview() {
+    toPreviewExamPlan() {
+      if (!this.modalForm.examPlanCodeTemp) {
+        this.$message.error("请先上传考试计划表!");
+        return;
+      }
+      this.$refs.ExamPlanData.open();
+    },
+    toPreviewExamBusiness() {
       if (!this.modalForm.examCodeTemp) {
-        this.$message.error("请先上传考务文件!");
+        this.$message.error("请先上传考试计划表!");
         return;
       }
       this.$refs.BusinessData.open();
@@ -374,19 +451,29 @@ export default {
     uplaodError(errorData) {
       this.$notify.error({ title: "错误提示", message: errorData.message });
     },
-    uploadSuccess(res) {
+    uploadExamPlanSuccess(res) {
       this.$message.success("上传成功!");
       const data = res.data;
 
-      this.modalForm.beginTime = data.startTime;
-      this.modalForm.examCodeTemp = data.examCode;
-      this.modalForm.attachmentId = data.attachmentId;
-      this.$refs["ModalForm"].validateField("examCodeTemp");
+      this.modalForm.examPlanCodeTemp = data.examPlanCodeTemp;
+      this.uploadData.examPlanCodeTemp = data.examPlanCodeTemp;
+      this.modalForm.examPlanAttachmentId = data.attachmentId;
+      this.$refs["ModalForm"].validateField("examPlanCodeTemp");
 
       this.courses = data.userCourses.map(item => {
+        item.canEdit = item.status === "未开始";
         item.teacherId = item.users.length === 1 ? item.users[0].id + "" : "";
         return item;
       });
+      this.canEdit = !this.courses.some(item => !item.canEdit);
+    },
+    uploadExamBusinessSuccess(res) {
+      this.$message.success("上传成功!");
+      const data = res.data;
+
+      this.modalForm.examCodeTemp = data.examCode;
+      this.modalForm.attachmentId = data.attachmentId;
+      this.$refs["ModalForm"].validateField("examCodeTemp");
     }
   }
 };

+ 1 - 1
src/modules/exam-center/views/ExamRomeStudentDetail.vue

@@ -144,7 +144,7 @@
 import { studentDetail, examSiteRoomList } from "../api";
 
 export default {
-  name: "exam-room-detail",
+  name: "exam-room-student-detail",
   data() {
     return {
       filter: {

+ 26 - 11
src/modules/exam-center/views/PrintManage.vue

@@ -96,7 +96,7 @@ import { REVOKE_STATUS } from "@/constants/enumerate";
 import { printTaskListPage, examList, printRevokeAudit } from "../api";
 
 export default {
-  name: "exam-manage",
+  name: "print-manage",
   data() {
     return {
       filter: {
@@ -111,14 +111,13 @@ export default {
       REVOKE_STATUS,
       exams: [],
       dataList: [],
-      setT: "",
-      IS_PRINTER: false
+      IS_PRINTER: false,
+      loopRunning: false,
+      loopSetTs: []
     };
   },
   created() {
-    if (this.setT) clearInterval(this.setT);
     this.init();
-    this.setInter();
   },
   methods: {
     init() {
@@ -126,12 +125,27 @@ export default {
         .get("user", { roleCode: "" })
         .roleCode.includes("PRINT");
       this.getExamList();
-      this.getList();
+      this.loopRunning = true;
+      this.timerUpdatePage();
+    },
+    clearLoopSetTs() {
+      if (!this.loopSetTs.length) return;
+      this.loopSetTs.forEach(sett => {
+        clearTimeout(sett);
+      });
+      this.loopSetTs = [];
     },
-    setInter() {
-      this.setT = setInterval(() => {
-        this.getList();
-      }, 30 * 1000);
+    async timerUpdatePage() {
+      this.clearLoopSetTs();
+      if (!this.loopRunning) return;
+
+      await this.getList().catch(() => {});
+
+      this.loopSetTs.push(
+        setTimeout(() => {
+          this.timerUpdatePage();
+        }, 30 * 1000)
+      );
     },
     async getList() {
       const datas = {
@@ -163,7 +177,8 @@ export default {
     }
   },
   beforeDestroy() {
-    if (this.setT) clearInterval(this.setT);
+    this.loopRunning = false;
+    this.clearLoopSetTs();
   }
 };
 </script>

+ 147 - 0
src/modules/exam-center/views/TodoExam.vue

@@ -0,0 +1,147 @@
+<template>
+  <div class="todo-exam">
+    <div class="part-box part-box-filter">
+      <el-form ref="FilterForm" label-position="left" label-width="85px" inline>
+        <el-form-item label="考试名称:">
+          <el-select
+            v-model="filter.examId"
+            style="width: 193px;"
+            placeholder="请选择"
+            clearable
+          >
+            <el-option
+              v-for="item in exams"
+              :key="item.id"
+              :value="item.id"
+              :label="item.name"
+            ></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>
+
+    <div class="part-box">
+      <el-table
+        ref="TableList"
+        :data="dataList"
+        :row-class-name="tableRowClassName"
+        border
+        stripe
+      >
+        <el-table-column prop="examCode" label="考试ID"></el-table-column>
+        <el-table-column prop="examName" label="考试名称"></el-table-column>
+        <el-table-column
+          prop="printTime"
+          label="任务截止日期"
+          min-width="100"
+        ></el-table-column>
+        <el-table-column
+          prop="overTime"
+          label="剩余时间"
+          min-width="100"
+        ></el-table-column>
+        <el-table-column
+          prop="createTime"
+          label="接收时间"
+          min-width="100"
+        ></el-table-column>
+        <el-table-column
+          label="操作"
+          align="center"
+          width="120"
+          class-name="tb-action-column"
+        >
+          <template slot-scope="scope">
+            <el-button
+              class="btn-table-icon"
+              type="text"
+              icon="icon icon-edit"
+              @click="toEdit(scope.row)"
+              title="编辑"
+            ></el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+  </div>
+</template>
+
+<script>
+import { examList, todoExamList } from "../api";
+
+export default {
+  name: "todo-exam",
+  data() {
+    return {
+      filter: {
+        examId: ""
+      },
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      exams: [],
+      dataList: []
+    };
+  },
+  mounted() {
+    this.getExamList();
+    this.getList();
+  },
+  methods: {
+    async getList() {
+      const datas = {
+        ...this.filter,
+        pageNumber: this.current,
+        pageSize: this.size
+      };
+      const data = await todoExamList(datas);
+      this.dataList = data.records;
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    async getExamList() {
+      const data = await examList();
+      this.exams = data.map(item => {
+        return {
+          id: item.id,
+          name: item.examName
+        };
+      });
+    },
+    tableRowClassName({ row }) {
+      return row.warning ? "row-danger" : "";
+    },
+    toEdit(row) {
+      this.$router.push({
+        name: "ExamEdit",
+        params: {
+          examId: row.id
+        }
+      });
+    }
+  },
+  beforeRouteLeave(to, from, next) {
+    const nextRouters = ["TopicTaskEdit"];
+    if (!nextRouters.includes(to.name)) {
+      this.$destroy();
+    }
+    next();
+  },
+  beforeRouteEnter(to, from, next) {
+    const nextRouters = ["TopicTaskEdit"];
+    if (nextRouters.includes(from.name)) {
+      next(vm => vm.$nextTick(() => vm.getList()));
+    } else {
+      next();
+    }
+  }
+};
+</script>

+ 338 - 0
src/modules/exam-center/views/TopicTaskManage.vue

@@ -0,0 +1,338 @@
+<template>
+  <div class="exam-task-manage">
+    <div class="part-box part-box-filter">
+      <el-form ref="FilterForm" label-position="left" label-width="85px" inline>
+        <el-form-item label="考试名称:">
+          <el-select
+            v-model="filter.examId"
+            style="width: 193px;"
+            placeholder="请选择"
+            @change="examChange"
+            clearable
+          >
+            <el-option
+              v-for="item in exams"
+              :key="item.id"
+              :value="item.id"
+              :label="item.name"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="科目(代码):" label-width="110px">
+          <el-select
+            v-model="filter.courseCode"
+            style="width: 193px;"
+            placeholder="请选择"
+            clearable
+          >
+            <el-option
+              v-for="item in courses"
+              :key="item.courseCode"
+              :value="item.courseCode"
+              :label="item.courseName"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="状态:" label-width="60px">
+          <el-select
+            v-model="filter.status"
+            style="width: 142px;"
+            placeholder="请选择"
+            clearable
+          >
+            <el-option
+              v-for="(val, key) in TASK_TYPES"
+              :key="key"
+              :value="key"
+              :label="val"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="提交印刷:">
+          <el-select
+            v-model="filter.printStatus"
+            style="width: 142px;"
+            placeholder="请选择"
+            clearable
+          >
+            <el-option
+              v-for="(val, key) in BOOLEAN_TYPE"
+              :key="key"
+              :value="key"
+              :label="val"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="启用:" label-width="60px">
+          <el-select
+            v-model="filter.enable"
+            style="width: 142px;"
+            placeholder="请选择"
+            clearable
+          >
+            <el-option
+              v-for="(val, key) in BOOLEAN_TYPE"
+              :key="key"
+              :value="key"
+              :label="val"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="打印起始时间:" label-width="110px">
+          <el-date-picker
+            v-model="printTime"
+            type="daterange"
+            @change="printTimeChange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            value-format="timestamp"
+            format="yyyy-MM-dd"
+          >
+          </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-button type="warning" icon="icon icon-plus" @click="toAdd">
+            新增命题任务
+          </el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+    <div class="part-box">
+      <el-table ref="TableList" :data="dataList" border stripe>
+        <el-table-column prop="examCode" label="考试ID"></el-table-column>
+        <el-table-column prop="examName" label="考试名称"></el-table-column>
+        <el-table-column prop="courseNameCode" label="科目名称(编码)">
+        </el-table-column>
+        <el-table-column
+          prop="questionTeacherName"
+          label="命题老师"
+        ></el-table-column>
+        <el-table-column
+          prop="printTime"
+          label="打印时间"
+          min-width="100"
+        ></el-table-column>
+        <el-table-column
+          prop="beginTime"
+          label="考试时间"
+          min-width="160"
+        ></el-table-column>
+        <el-table-column prop="status" label="状态">
+          <span slot-scope="scope">
+            {{ TASK_TYPES[scope.row.status] }}
+          </span>
+        </el-table-column>
+        <el-table-column prop="printStatus" label="提交印刷">
+          <span slot-scope="scope">
+            {{ BOOLEAN_TYPE[scope.row.printStatus] }}
+          </span>
+        </el-table-column>
+        <el-table-column prop="enable" label="是否启用">
+          <span
+            :class="{ 'color-danger': !scope.row.enable }"
+            slot-scope="scope"
+            >{{ BOOLEAN_TYPE[scope.row.enable] }}</span
+          >
+        </el-table-column>
+        <el-table-column
+          label="操作"
+          align="center"
+          width="120"
+          class-name="tb-action-column"
+        >
+          <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 ? '取消' : '启用'"
+              v-if="!scope.row.printStatus"
+            ></el-button>
+            <el-button
+              class="btn-table-icon"
+              type="text"
+              icon="icon icon-edit"
+              @click="toEdit(scope.row)"
+              v-if="scope.row.status === 0 || scope.row.status === 1"
+              title="编辑"
+            ></el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="part-page">
+        <el-pagination
+          background
+          layout="prev, pager, next"
+          :current-page="current"
+          :total="total"
+          :page-size="size"
+          @current-change="toPage"
+        >
+        </el-pagination>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { BOOLEAN_TYPE } from "@/constants/enumerate";
+import {
+  topicTaskListPage,
+  examList,
+  examCourseList,
+  eableTopicTask
+} from "../api";
+
+export default {
+  name: "topic-task-manage",
+  data() {
+    return {
+      filter: {
+        examId: "",
+        courseCode: "",
+        status: "",
+        printStatus: null,
+        enable: null,
+        printStartTime: "",
+        printEntTime: ""
+      },
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      BOOLEAN_TYPE,
+      TASK_TYPES: {
+        0: "未开始",
+        1: "进行中",
+        2: "待审核",
+        3: "已完成"
+      },
+      printTime: "",
+      exams: [],
+      courses: [],
+      dataList: []
+    };
+  },
+  mounted() {
+    this.getExamList();
+    this.getList();
+  },
+  methods: {
+    async getList() {
+      const datas = {
+        ...this.filter,
+        pageNumber: this.current,
+        pageSize: this.size
+      };
+      const data = await topicTaskListPage(datas);
+      this.dataList = data.records;
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    async getExamList() {
+      const data = await examList();
+      this.exams = data.map(item => {
+        return {
+          id: item.id,
+          name: item.examName
+        };
+      });
+    },
+    async getCourses() {
+      if (!this.filter.examId) return;
+      this.courses = await examCourseList(this.filter.examId);
+    },
+    examChange() {
+      this.filter.courseCode = "";
+      this.courses = [];
+      this.getCourses();
+    },
+    printTimeChange(vals) {
+      const dvals = vals || [];
+      this.filter.printStartTime = dvals[0] || null;
+      this.filter.printEntTime = dvals[1] || null;
+    },
+    toAdd() {
+      this.$router.push({ name: "TopicTaskAdd" });
+    },
+    toEnable(row) {
+      if (row.enable) {
+        const h = this.$createElement;
+        const tips = [
+          "命题老师无法查看到该任务的待办。",
+          "若试卷正在审核,则无法继续审核流程,审核界面也无法查看到该命题任务。",
+          "若试卷已经绑定了题卡,则会取消题卡绑定,但题卡仍可以在题卡管理里查看到,可与其它命题任务重新进行绑定。",
+          "被取消的命题任务不会发送印刷。",
+          "任务的数据系统和流程进度系统仍然保留。"
+        ];
+        let msgs = tips.map((item, index) =>
+          h("p", null, `${index + 1}、${item}`)
+        );
+        msgs.unshift(h("p", null, "任务取消后,会有如下影响:"));
+        msgs.push(
+          h(
+            "p",
+            { style: "margin-top: 20px;color: rgba(254, 108, 105, 1)" },
+            "确定要取消当前任务吗?"
+          )
+        );
+        this.$msgbox({
+          title: "操作警告",
+          message: h("div", { style: "text-align: left" }, msgs),
+          cancelButtonClass: "el-button--danger is-plain",
+          confirmButtonClass: "el-button--primary",
+          showCancelButton: true
+        })
+          .then(async () => {
+            this.enableTask(row);
+          })
+          .catch(() => {});
+      } else {
+        this.enableTask(row);
+      }
+    },
+    async enableTask(row) {
+      const enable = Number(!row.enable);
+      await eableTopicTask({
+        taskId: row.taskId,
+        enable
+      });
+      row.enable = enable;
+    },
+    toEdit(row) {
+      this.$router.push({
+        name: "TopicTaskEdit",
+        params: {
+          taskId: row.taskId
+        }
+      });
+    }
+  },
+  beforeRouteLeave(to, from, next) {
+    const nextRouters = ["TopicTaskAdd", "TopicTaskEdit"];
+    if (!nextRouters.includes(to.name)) {
+      this.$destroy();
+    }
+    next();
+  },
+  beforeRouteEnter(to, from, next) {
+    const nextRouters = ["TopicTaskAdd", "TopicTaskEdit"];
+    if (nextRouters.includes(from.name)) {
+      next(vm => vm.$nextTick(() => vm.getList()));
+    } else {
+      next();
+    }
+  }
+};
+</script>

+ 222 - 0
src/modules/exam-center/views/TopicTaskModify.vue

@@ -0,0 +1,222 @@
+<template>
+  <div class="topic-task-modify part-box part-box-border part-box-pad">
+    <el-form
+      ref="ModalForm"
+      :model="modalForm"
+      :rules="rules"
+      label-width="180px"
+      style="min-width:800px;"
+    >
+      <el-form-item prop="examId" label="考试名称:">
+        <el-select
+          v-model="modalForm.examId"
+          style="width: 439px;"
+          placeholder="请选择"
+          @change="examChange"
+          clearable
+          v-if="!isEdit"
+        >
+          <el-option
+            v-for="item in exams"
+            :key="item.id"
+            :value="item.id"
+            :label="item.name"
+          ></el-option>
+        </el-select>
+        <el-input
+          v-model.trim="modalForm.examName"
+          style="width: 439px;"
+          readonly
+          v-else
+        ></el-input>
+      </el-form-item>
+      <el-form-item prop="courseCode" label="科目:">
+        <el-select
+          v-model="modalForm.courseCode"
+          style="width: 439px;"
+          placeholder="请选择"
+          @change="courseChange"
+          clearable
+          v-if="!isEdit"
+        >
+          <el-option
+            v-for="item in courses"
+            :key="item.courseCode"
+            :value="item.courseCode"
+            :label="item.courseName"
+          ></el-option>
+        </el-select>
+        <el-input
+          v-model.trim="modalForm.courseName"
+          style="width: 439px;"
+          readonly
+          v-else
+        ></el-input>
+      </el-form-item>
+      <el-form-item label="考试开始时间:">
+        <el-input
+          v-model.trim="modalForm.beginTime"
+          style="width: 439px;"
+          readonly
+        ></el-input>
+      </el-form-item>
+      <el-form-item prop="questionTeacherId" label="命题老师:">
+        <el-select
+          v-model="modalForm.questionTeacherId"
+          style="width: 439px;"
+          placeholder="请选择"
+          clearable
+        >
+          <el-option
+            v-for="item in teachers"
+            :key="item.id"
+            :value="item.id"
+            :label="item.name"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+
+      <el-form-item>
+        <el-button type="primary" :disabled="isSubmit" @click="save"
+          >保存</el-button
+        >
+        <el-button @click="goback">返回</el-button>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import {
+  updateTopicTask,
+  topicTaskDetail,
+  topicTaskExamList,
+  topicTaskExamCourseList,
+  topicTaskExamTeacherList
+} from "../api";
+
+export default {
+  name: "topic-task-modify",
+  data() {
+    return {
+      taskId: this.$route.params.taskId,
+      modalForm: {
+        examId: "",
+        examName: "",
+        courseCode: "",
+        courseName: "",
+        beginTime: "",
+        questionTeacherId: ""
+      },
+      curExam: {},
+      rules: {
+        examId: [
+          {
+            required: true,
+            message: "请选择考试",
+            trigger: "change"
+          }
+        ],
+        courseCode: [
+          {
+            required: true,
+            message: "请选择科目",
+            trigger: "change"
+          }
+        ],
+        questionTeacherId: [
+          {
+            required: true,
+            message: "请选择命题老师",
+            trigger: "change"
+          }
+        ]
+      },
+      exams: [],
+      courses: [],
+      teachers: [],
+      isSubmit: false
+    };
+  },
+  computed: {
+    isEdit() {
+      return !!this.taskId;
+    }
+  },
+  mounted() {
+    this.initData();
+  },
+  methods: {
+    async initData() {
+      if (this.isEdit) {
+        await this.getTaskDetail();
+        this.getTeachers();
+      } else {
+        this.getExams();
+      }
+    },
+    async getTaskDetail() {
+      const data = await topicTaskDetail(this.taskId);
+      this.modalForm = this.$objAssign(this.modalForm, data);
+    },
+    async getExams() {
+      const data = await topicTaskExamList();
+      this.exams = data.map(item => {
+        return {
+          id: item.id,
+          name: item.examName,
+          beginTime: item.beginTime
+        };
+      });
+    },
+    async getCourses() {
+      this.courses = await topicTaskExamCourseList(this.modalForm.examId);
+    },
+    async getTeachers() {
+      this.teachers = await topicTaskExamTeacherList(this.modalForm.courseCode);
+    },
+    examChange() {
+      this.modalForm.courseCode = "";
+      this.modalForm.questionTeacherId = "";
+      this.courses = [];
+      this.teachers = [];
+      if (!this.modalForm.examId) {
+        this.curExam = {};
+        return;
+      }
+      this.curExam = this.exams.find(item => item.id === this.modalForm.examId);
+      this.modalForm.beginTime = this.curExam.beginTime;
+      this.getCourses();
+    },
+    courseChange() {
+      this.modalForm.questionTeacherId = "";
+      this.teachers = [];
+      if (!this.modalForm.courseCode) return;
+      this.getTeachers();
+    },
+    async save() {
+      const valid = await this.$refs["ModalForm"].validate().catch(() => {});
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const datas = {
+        ...this.modalForm
+      };
+      if (this.isEdit) {
+        datas.taskId = this.taskId;
+      } else {
+        const curCourse = this.courses.find(
+          item => item.courseCode === datas.courseCode
+        );
+        datas.courseName = curCourse.courseName;
+      }
+      const data = await updateTopicTask(datas).catch(() => {});
+      this.isSubmit = false;
+      if (!data) return;
+      this.$message.success("保存成功!");
+      this.goback();
+    }
+  }
+};
+</script>

+ 4 - 1
src/modules/exam-center/views/WaitTask.vue

@@ -71,6 +71,7 @@
 
 <script>
 import { waitTaskListPage, examList } from "../api";
+import { mapActions } from "vuex";
 
 export default {
   name: "wait-task",
@@ -91,9 +92,10 @@ export default {
     this.init();
   },
   methods: {
+    ...mapActions("examCenter", ["updateWaitTaskCount"]),
     init() {
       this.getExamList();
-      this.getList();
+      this.toPage(1);
     },
     async getList() {
       const datas = {
@@ -109,6 +111,7 @@ export default {
     toPage(page) {
       this.current = page;
       this.getList();
+      this.updateWaitTaskCount();
     },
     async getExamList() {
       const data = await examList();

+ 117 - 51
src/modules/exam-center/views/WaitTaskDetail.vue

@@ -3,12 +3,15 @@
     <div class="task-title">
       <h2>
         <span>考试名称: {{ task.examName }} </span>
-        <span>科目名称:{{ task.courseName }}</span>
+        <span>科目名称:{{ task.courseNameCode }}</span>
       </h2>
       <div class="task-title-infos">
-        <el-checkbox v-model="pTypeEnable" @change="pTypeEnableChange"
+        <el-checkbox v-model="pTypeEnable" disabled
           >启用{{ paperTypeFieldContent }}卷</el-checkbox
         >
+        <span class="task-title-add" @click="addAtachment">
+          <i class="icon icon-plus-act"></i>增加卷型
+        </span>
       </div>
     </div>
     <div class="task-body">
@@ -16,7 +19,9 @@
         <tr>
           <th>试卷类型</th>
           <th v-if="task.examPaper">试卷文件</th>
+          <th v-if="task.examPaper">试卷审核确认书</th>
           <th v-if="task.answerSheet">答题卡</th>
+          <th>操作</th>
         </tr>
         <tr v-for="(attachment, index) in curPaperAttachments" :key="index">
           <td>{{ attachment.name }}卷</td>
@@ -35,6 +40,27 @@
               }}
             </span>
           </td>
+          <td
+            class="td-link"
+            :rowspan="pTypeEnable ? curPaperAttachments.length : 1"
+            v-if="index === 0 && task.examPaper"
+          >
+            <span @click="toUploadPaperConfirm" title="点击上传确认书文件">
+              <i
+                :class="[
+                  'icon',
+                  paperConfirmAttachmentId.attachmentId
+                    ? 'icon-files-act'
+                    : 'icon-files'
+                ]"
+              ></i
+              >{{
+                paperConfirmAttachmentId.attachmentId
+                  ? paperConfirmAttachmentId.filename
+                  : "点击上传确认书文件"
+              }}
+            </span>
+          </td>
           <td
             class="td-link"
             :rowspan="pTypeEnable ? curPaperAttachments.length : 1"
@@ -44,6 +70,15 @@
               ><i class="icon icon-plus-act"></i>{{ cardTodoName }}</span
             >
           </td>
+          <td>
+            <el-button
+              class="btn-table-icon"
+              type="text"
+              icon="icon icon-delete"
+              title="删除"
+              @click="deleteAttachment(index)"
+            ></el-button>
+          </td>
         </tr>
       </table>
 
@@ -58,10 +93,19 @@
       </div>
 
       <div class="task-action">
-        <el-button type="primary" style="width:105px;" @click="toSubmit"
+        <el-button
+          type="primary"
+          style="width:105px;"
+          @click="toSubmit"
+          :disabled="!curPaperAttachments.length"
           >确认提交</el-button
         >
-        <el-button style="width:88px;" @click="toSave">暂存</el-button>
+        <el-button
+          style="width:88px;"
+          @click="toSave"
+          :disabled="!curPaperAttachments.length"
+          >暂存</el-button
+        >
         <el-button @click="goback" style="width:88px;">取消</el-button>
       </div>
     </div>
@@ -86,6 +130,7 @@
     <!-- upload-paper-dialog -->
     <upload-paper-dialog
       :paper-attachment="curAttachment"
+      :upload-type="curUploadType"
       @confirm="uploadConfirm"
       ref="UploadPaperDialog"
     ></upload-paper-dialog>
@@ -136,27 +181,27 @@ export default {
         paperSticker: 0,
         review: 1,
         auditStatus: 0,
-        remark:
-          "这是按税法二,的啊ad框架,安大街开始的罚款案件发,发生的叫法卡是的罚款是安抚,继发技术附件发卡上开发发顺丰,发斯蒂芬咖啡机大法级法师打发斯蒂芬阿道夫。"
+        remark: ""
       },
-      pTypeEnable: false,
+      paperConfirmAttachmentId: { attachmentId: "", filename: "" },
       paperAttachments: [],
       curPaperAttachments: [],
       curAttachment: {},
+      curUploadType: "paper",
+      attachmentLimitCount: 4,
+      abc: "",
       // audit history
       modalIsShow: false,
-      auditHistory: [
-        {
-          createTime: "2020-01-15 15:15:15",
-          remark: "这是很航的阿克苏就按"
-        }
-      ]
+      auditHistory: []
     };
   },
   computed: {
     paperTypeFieldContent() {
       return this.PAPER_TYPE_FIELDS.join("/");
     },
+    pTypeEnable() {
+      return this.curPaperAttachments.length > 1;
+    },
     cardTodoName() {
       let name = "创建答题卡";
       if (this.task.cardId) {
@@ -173,6 +218,9 @@ export default {
     }
   },
   mounted() {
+    this.abc = "abcdefghijklmnopqrstuvwxyz"
+      .toUpperCase()
+      .slice(0, this.attachmentLimitCount);
     this.getData();
   },
   methods: {
@@ -184,11 +232,17 @@ export default {
         courseName: nameCode[0],
         courseCode: nameCode[1]
       });
-      this.pTypeEnable =
-        this.task.enablePaperType &&
-        this.task.enablePaperType.split(",").length > 1;
-      this.parsePaperAttachment();
-      this.pTypeEnableChange(this.pTypeEnable);
+
+      const paperAttachment =
+        this.task.paperAttachmentId && JSON.parse(this.task.paperAttachmentId);
+      this.paperAttachments = paperAttachment ? paperAttachment.paper : [];
+      this.curPaperAttachments = this.paperAttachments.map(item => {
+        return { ...item };
+      });
+      this.paperConfirmAttachmentId = this.task.paperConfirmAttachmentId
+        ? JSON.parse(this.task.paperConfirmAttachmentId)
+        : { attachmentId: "", filename: "" };
+      this.task.enablePaperType = this.getEnablePaperType();
 
       if (this.task.review && this.task.auditStatus === 2)
         this.getHistoryList();
@@ -196,47 +250,52 @@ export default {
     async getHistoryList() {
       this.auditHistory = await examTaskAudtiHistory(this.taskId);
     },
-    parsePaperAttachment() {
-      const paperAttachment =
-        this.task.paperAttachmentId && JSON.parse(this.task.paperAttachmentId);
-      const papers = paperAttachment && paperAttachment.paper;
-      let paperDict = {};
-      if (papers) {
-        papers.map(paper => {
-          paperDict[paper.name] = paper;
-        });
+    addAtachment() {
+      if (this.curPaperAttachments.length >= this.attachmentLimitCount) return;
+
+      const newAttachment = {
+        name: this.abc[this.curPaperAttachments.length],
+        attachmentId: "",
+        filename: ""
+      };
+      this.curPaperAttachments.push(newAttachment);
+    },
+    deleteAttachment(index) {
+      if (this.curPaperAttachments.length <= 1) {
+        this.$message.error("试卷类型数量不得少于1");
+        return;
       }
-      this.paperAttachments = this.PAPER_TYPE_FIELDS.map(paperType => {
-        const paperInfo = paperDict[paperType];
-        let paper = {
-          attachmentId: "",
-          name: paperType
-        };
-        return paperInfo ? Object.assign({}, paper, paperInfo) : paper;
+      this.curPaperAttachments.splice(index, 1);
+      this.curPaperAttachments.forEach((item, itemIndex) => {
+        item.name = this.abc[itemIndex];
       });
     },
-    pTypeEnableChange(val) {
-      this.curPaperAttachments = val
-        ? this.paperAttachments
-        : [this.paperAttachments[0]];
-      this.task.enablePaperType = this.getEnablePaperType();
-    },
     toUpload(attachment) {
-      this.curAttachment = { ...attachment };
+      this.curUploadType = "paper";
+      this.curAttachment = {
+        ...attachment
+      };
       this.$refs.UploadPaperDialog.open();
     },
-    uploadConfirm(attachment) {
-      this.curAttachment = Object.assign(this.curAttachment, attachment);
-      const index = this.paperAttachments.findIndex(
-        item => item.name === attachment.name
-      );
-      this.paperAttachments.splice(index, 1, { ...this.curAttachment });
-      this.pTypeEnableChange(this.pTypeEnable);
-      this.task.paperAttachmentId = JSON.stringify({
-        paper: this.curPaperAttachments
-      });
+    toUploadPaperConfirm() {
+      this.curUploadType = "paperConfirm";
+      this.curAttachment = {
+        ...this.paperConfirmAttachmentId
+      };
+      this.$refs.UploadPaperDialog.open();
+    },
+    uploadConfirm(attachment, uploadType) {
+      if (uploadType === "paper") {
+        const index = this.curPaperAttachments.findIndex(
+          item => item.name === attachment.name
+        );
+        this.curPaperAttachments.splice(index, 1, { ...attachment });
+      } else {
+        this.paperConfirmAttachmentId = { ...attachment };
+      }
     },
     toCreateCard() {
+      this.task = this.getTaskData().tcPExamTaskDetail;
       if (this.task.cardId) {
         if (this.task.cardSource === 0) {
           this.$refs.CardOptionDialog.open();
@@ -283,6 +342,9 @@ export default {
       datas.paperAttachmentId = JSON.stringify({
         paper: this.curPaperAttachments
       });
+      datas.paperConfirmAttachmentId = JSON.stringify(
+        this.paperConfirmAttachmentId
+      );
       datas.enablePaperType = this.getEnablePaperType();
       return { tcPExamTaskDetail: datas };
     },
@@ -294,6 +356,10 @@ export default {
         this.$message.error("请完成试卷文件上传!");
         return;
       }
+      if (this.task.examPaper && !this.paperConfirmAttachmentId.attachmentId) {
+        this.$message.error("请完成试卷审核确认书上传!");
+        return;
+      }
 
       if (this.task.answerSheet && !this.task.cardId && !this.task.refCardId) {
         this.$message.error("请选择题卡创建方式!");

+ 13 - 0
src/router.js

@@ -13,6 +13,19 @@ import scorePaper from "./modules/score-paper/router";
 import analyze from "./modules/analyze/router";
 import card from "./modules/card/router";
 
+// ignore NavigationDuplicated. https://github.com/vuejs/vue-router/issues/2881
+const originalPush = Router.prototype.push;
+Router.prototype.push = function push(location, onResolve, onReject) {
+  if (onResolve || onReject)
+    return originalPush.call(this, location, onResolve, onReject);
+  try {
+    return originalPush.call(this, location).catch(err => err);
+  } catch (error) {
+    console.log(error);
+  }
+};
+// end ignore
+
 Vue.use(Router);
 
 export default new Router({

+ 17 - 1
src/views/Home.vue

@@ -188,6 +188,9 @@ export default {
       curSubIndex: 0,
       breadcrumbs: [],
       username: this.$ls.get("user", { name: "" }).name,
+      IS_SUPER_ADMIN: this.$ls
+        .get("user", { roleCode: "" })
+        .roleCode.includes("SUPER_ADMIN"),
       menuDailogIsShow: false,
       cacheRouters: [
         "wait-task",
@@ -196,7 +199,9 @@ export default {
         "exam-room-detail",
         "exam-task-audit",
         "card-manage",
-        "user-manage"
+        "user-manage",
+        "topic-task-manage",
+        "todo-exam"
       ]
     };
   },
@@ -244,6 +249,16 @@ export default {
       }
     },
     menusToTree(menus) {
+      if (this.IS_SUPER_ADMIN) {
+        const names = {
+          todoExam: "待办任务(考务)",
+          todoTaskManager: "待办任务(命题)",
+          doneTaskManager: "已办任务(命题)"
+        };
+        menus.forEach(item => {
+          item.name = names[item.url] || item.name;
+        });
+      }
       let navTree = deepCopy(localNavs);
       let validRoutes = [];
       const anchorNav = (menu, navs) => {
@@ -251,6 +266,7 @@ export default {
         navs.forEach(item => {
           if (item.router === name) {
             item.fixed = true;
+            item.title = menu.name;
             validRoutes.push(item.router);
 
             if (menu.parentId !== null && item["children"]) {