浏览代码

考试管理完成

zhangjie 3 年之前
父节点
当前提交
2977a5a350

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "teachcloud-platform-web",
   "name": "teachcloud-platform-web",
-  "version": "2.2.0",
+  "version": "3.0.0",
   "scripts": {
   "scripts": {
     "start": "npm run serve",
     "start": "npm run serve",
     "serve": "vue-cli-service serve",
     "serve": "vue-cli-service serve",

+ 9 - 0
src/assets/styles/element-ui-costom.scss

@@ -71,6 +71,15 @@
     margin-left: 10px;
     margin-left: 10px;
   }
   }
 }
 }
+//
+.page-dialog {
+  .el-dialog.is-fullscreen {
+    background: $--color-background;
+    .el-dialog__body {
+      padding: 70px 20px 20px;
+    }
+  }
+}
 
 
 // .opacity-dialog
 // .opacity-dialog
 .opacity-dialog {
 .opacity-dialog {

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

@@ -409,3 +409,18 @@
     font-size: 40px;
     font-size: 40px;
   }
   }
 }
 }
+
+// modify-exam
+.modify-exam {
+  .tips-info {
+    font-size: 14px;
+  }
+  .el-radio-v {
+    display: block;
+    margin-bottom: 8px;
+  }
+}
+// modify-exam-config
+// .modify-exam-config {
+
+// }

+ 12 - 0
src/constants/enumerate.js

@@ -86,6 +86,18 @@ export const SYNC_PRINT_STATUS = {
   FINISH: "已结束"
   FINISH: "已结束"
 };
 };
 
 
+// 考试
+export const EXAM_TYPE = {
+  OFFLINE: "线下考试",
+  ONLINE: "线上考试"
+};
+
+export const EXAM_TYPE_MODE = {
+  ONE: "模式1:电子交卷环节需要提交考务数据",
+  TWO: "模式2:电子交卷环节不需要提交考务数据,只输入印刷份数即可",
+  THREE: "模式3:电子交卷环节不需要提交考务数据"
+};
+
 // 命题 -------------->
 // 命题 -------------->
 // 待办任务警告时间
 // 待办任务警告时间
 export const TASK_WARNING_TIME = 3 * 24 * 60 * 60 * 1000;
 export const TASK_WARNING_TIME = 3 * 24 * 60 * 60 * 1000;

+ 100 - 0
src/constants/menus-data.js

@@ -1355,6 +1355,106 @@ export default [
         sequence: 1,
         sequence: 1,
         enable: true,
         enable: true,
         children: [
         children: [
+          {
+            id: "1001",
+            name: "考试管理",
+            url: "ExamManage",
+            type: "MENU",
+            parentId: "356",
+            schoolId: "2",
+            sequence: 3,
+            enable: true,
+            urls: [
+              {
+                id: "96",
+                name: "考试管理-查询",
+                url: "/api/admin/basic/course/list",
+                type: "URL",
+                parentId: "13",
+                schoolId: "2",
+                sequence: 1,
+                enable: true
+              },
+              {
+                id: "97",
+                name: "考试管理-新增/修改",
+                url: "/api/admin/basic/course/save",
+                type: "URL",
+                parentId: "13",
+                schoolId: "2",
+                sequence: 2,
+                enable: true
+              }
+            ],
+            buttons: [
+              {
+                id: "263",
+                name: "考试管理-查询",
+                url: "Select",
+                type: "BUTTON",
+                parentId: "13",
+                schoolId: "2",
+                sequence: 1,
+                enable: true
+              },
+              {
+                id: "385",
+                name: "考试管理-新增",
+                url: "Add",
+                type: "BUTTON",
+                parentId: "13",
+                schoolId: "2",
+                sequence: 1,
+                enable: true
+              }
+            ],
+            links: [
+              {
+                id: "268",
+                name: "考试管理-编辑",
+                url: "Edit",
+                type: "LINK",
+                parentId: "13",
+                schoolId: "2",
+                sequence: 1,
+                enable: true
+              },
+              {
+                id: "269",
+                name: "考试管理-启用&禁用",
+                url: "Enable",
+                type: "LINK",
+                parentId: "13",
+                schoolId: "2",
+                sequence: 2,
+                enable: true
+              }
+            ],
+            lists: [
+              {
+                id: "271",
+                name: "考试管理-列表",
+                url: "List",
+                type: "LIST",
+                parentId: "13",
+                schoolId: "2",
+                sequence: 1,
+                enable: true
+              }
+            ],
+            conditions: [
+              {
+                id: "270",
+                name: "考试管理-查询条件",
+                url: "Condition",
+                type: "CONDITION",
+                parentId: "13",
+                schoolId: "2",
+                sequence: 2,
+                enable: true
+              }
+            ]
+          },
           {
           {
             id: "13",
             id: "13",
             name: "课程管理",
             name: "课程管理",

+ 28 - 0
src/modules/base/api.js

@@ -262,6 +262,34 @@ export const resendSms = id => {
   return $postParam("/api/admin/basic/message/resend", { id });
   return $postParam("/api/admin/basic/message/resend", { id });
 };
 };
 
 
+// exam-manage
+export const examListQuery = datas => {
+  return $postParam("/api/admin/basic/exam/list", datas);
+};
+export const deleteExam = idList => {
+  return $postParam("/api/admin/basic/exam/delete", { idList });
+};
+export const updateExam = datas => {
+  return $post("/api/admin/basic/exam/save", datas);
+};
+export const ableExam = ({ idList, enable }) => {
+  return $postParam("/api/admin/basic/exam/enable", { idList, enable });
+};
+
+// exam-config
+export const examConfigQuery = datas => {
+  return $postParam("/api/admin/basic/exam-config/list", datas);
+};
+export const deleteExamConfig = idList => {
+  return $postParam("/api/admin/basic/exam-config/delete", { idList });
+};
+export const updateExamConfig = datas => {
+  return $post("/api/admin/basic/exam-config/save", datas);
+};
+export const ableExamConfig = ({ idList, enable }) => {
+  return $postParam("/api/admin/basic/exam-config/enable", { idList, enable });
+};
+
 // common
 // common
 export const uploadFile = datas => {
 export const uploadFile = datas => {
   return $post("/api/admin/common/file/upload", datas);
   return $post("/api/admin/common/file/upload", datas);

+ 183 - 0
src/modules/base/components/ModifyExam.vue

@@ -0,0 +1,183 @@
+<template>
+  <el-dialog
+    class="modify-exam"
+    :visible.sync="modalIsShow"
+    :title="title"
+    top="10vh"
+    width="650px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @open="visibleChange"
+  >
+    <el-form
+      ref="modalFormComp"
+      :model="modalForm"
+      :rules="rules"
+      :key="modalForm.id"
+      label-width="100px"
+    >
+      <el-form-item prop="name" label="考试名称:">
+        <el-input
+          v-model.trim="modalForm.name"
+          placeholder="请输入考试名称"
+          clearable
+        ></el-input>
+        <p class="tips-info">示例:期末考试/期中考试/模拟考试</p>
+      </el-form-item>
+      <el-form-item prop="semesterId" label="学年学期:">
+        <semester-select
+          v-model="modalForm.semesterId"
+          placeholder="学年学期"
+          clearable
+          style="width:100%"
+        ></semester-select>
+      </el-form-item>
+      <el-form-item prop="examType" label="考试类型:">
+        <el-radio-group v-model="modalForm.examType">
+          <el-radio v-for="(val, key) in EXAM_TYPE" :key="key" :label="key">{{
+            val
+          }}</el-radio>
+        </el-radio-group>
+        <div class="tips-info">
+          <p>说明:</p>
+          <p>线下考试指纸笔传统考试,需要印刷试卷/题卡</p>
+          <p>线上考试指无纸化考试,如网考/机考</p>
+        </div>
+      </el-form-item>
+      <el-form-item
+        v-if="modalForm.examType === 'OFFLINE'"
+        prop="examTypeMode"
+        label=""
+      >
+        <p class="tips-info mb-2">请选择符合此考试的业务模式</p>
+        <el-radio-group v-model="modalForm.examTypeMode">
+          <el-radio
+            v-for="(val, key) in EXAM_TYPE_MODE"
+            :key="key"
+            :label="key"
+            class="el-radio-v"
+            >{{ val }}</el-radio
+          >
+        </el-radio-group>
+      </el-form-item>
+    </el-form>
+    <div slot="footer">
+      <el-button type="primary" :disabled="isSubmit" @click="submit"
+        >确认</el-button
+      >
+      <el-button @click="cancel">取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { updateExam } from "../api";
+import { EXAM_TYPE, EXAM_TYPE_MODE } from "@/constants/enumerate";
+
+const initModalForm = {
+  id: null,
+  name: "",
+  semesterId: "",
+  examType: "OFFLINE",
+  examTypeMode: "ONE"
+};
+
+export default {
+  name: "modify-exam",
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  computed: {
+    isEdit() {
+      return !!this.instance.id;
+    },
+    title() {
+      return (this.isEdit ? "编辑" : "新增") + "学期";
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      modalForm: { ...initModalForm },
+      EXAM_TYPE,
+      EXAM_TYPE_MODE,
+      rules: {
+        name: [
+          {
+            required: true,
+            // pattern: /^[0-9a-zA-Z\u4E00-\u9FA5]{1,20}$/,
+            // message: "课程名称只能输入汉字、数字和字母,长度不能超过20",
+            message: "考试名称不能超过100个字",
+            max: 100,
+            trigger: "change"
+          }
+        ],
+        semesterId: [
+          {
+            required: true,
+            message: "请选择学年学期",
+            trigger: "change"
+          }
+        ],
+        examType: [
+          {
+            required: true,
+            message: "请选择考试类型",
+            trigger: "change"
+          }
+        ],
+        examTypeMode: [
+          {
+            required: true,
+            message: "请选择考试模式",
+            trigger: "change"
+          }
+        ]
+      }
+    };
+  },
+  methods: {
+    initData(val) {
+      if (val.id) {
+        this.modalForm = this.$objAssign(initModalForm, val);
+      } else {
+        this.modalForm = { ...initModalForm };
+      }
+    },
+    visibleChange() {
+      this.initData(this.instance);
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      let datas = { ...this.modalForm };
+      const data = await updateExam(datas).catch(() => {
+        this.isSubmit = false;
+      });
+
+      if (!data) return;
+
+      this.isSubmit = false;
+      this.$message.success(this.title + "成功!");
+      this.$emit("modified");
+      this.cancel();
+    }
+  }
+};
+</script>

+ 251 - 0
src/modules/base/components/ModifyExamConfig.vue

@@ -0,0 +1,251 @@
+<template>
+  <div>
+    <el-dialog
+      class="modify-exam-config page-dialog"
+      :visible.sync="modalIsShow"
+      title="配置管理"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      append-to-body
+      fullscreen
+      destroy-on-close
+      @open="visibleChange"
+    >
+      <div class="part-box part-box-filter part-box-flex">
+        <el-form
+          ref="FilterForm"
+          label-position="left"
+          label-width="85px"
+          inline
+        >
+          <template v-if="checkPrivilege('condition', 'condition')">
+            <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="90px">
+              <el-select
+                v-model="filter.enable"
+                style="width: 120px;"
+                placeholder="启用/禁用"
+                clearable
+              >
+                <el-option
+                  v-for="(val, key) in ABLE_TYPE"
+                  :key="key"
+                  :value="key * 1"
+                  :label="val"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+          </template>
+          <el-form-item>
+            <el-button
+              v-if="checkPrivilege('button', 'select')"
+              type="primary"
+              @click="toPage(1)"
+              >查询</el-button
+            >
+          </el-form-item>
+        </el-form>
+        <div class="part-box-action">
+          <el-button
+            v-if="checkPrivilege('button', 'add')"
+            type="primary"
+            icon="el-icon-circle-plus-outline"
+            @click="toAdd"
+            >新增</el-button
+          >
+        </div>
+      </div>
+
+      <div class="part-box part-box-pad">
+        <el-table ref="TableList" :data="examConfigList">
+          <el-table-column
+            type="index"
+            label="序号"
+            width="70"
+            :index="indexMethod"
+          ></el-table-column>
+          <el-table-column prop="orgs" label="适用学院">
+            <template slot-scope="scope">
+              <more-text :data="scope.row.orgNames"></more-text>
+            </template>
+          </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="enable" label="启用/禁用">
+            <template slot-scope="scope">
+              {{ scope.row.enable | enableFilter }}
+            </template>
+          </el-table-column>
+          <el-table-column class-name="action-column" label="操作" width="160">
+            <template slot-scope="scope">
+              <el-button
+                v-if="checkPrivilege('link', 'edit')"
+                class="btn-primary"
+                type="text"
+                @click="toEdit(scope.row)"
+                >编辑</el-button
+              >
+              <el-button
+                v-if="checkPrivilege('link', 'Enable')"
+                :class="scope.row.enable ? 'btn-danger' : 'btn-primary'"
+                type="text"
+                @click="toEnable(scope.row)"
+                >{{ scope.row.enable ? "禁用" : "启用" }}</el-button
+              >
+              <!-- <el-button
+              class="btn-danger"
+              type="text"
+              @click="toDelete(scope.row)"
+              >删除</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>
+    </el-dialog>
+
+    <!-- ModifyExamConfigDetail -->
+    <modify-exam-config-detail
+      ref="ModifyExamConfigDetail"
+      :instance="curExamConfig"
+      @modified="getList"
+    ></modify-exam-config-detail>
+  </div>
+</template>
+
+<script>
+import { ABLE_TYPE } from "@/constants/enumerate";
+import pickerOptions from "@/constants/datePickerOptions";
+import { examConfigQuery, deleteExamConfig, ableExamConfig } from "../api";
+import ModifyExamConfigDetail from "./ModifyExamConfigDetail";
+
+export default {
+  name: "modify-exam-config",
+  components: { ModifyExamConfigDetail },
+  props: {
+    exam: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      filter: {
+        examId: "",
+        startCreateTime: "",
+        endCreateTime: "",
+        enable: ""
+      },
+      ABLE_TYPE,
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      examConfigList: [],
+      curExamConfig: {},
+      // date-picker
+      createTime: [],
+      pickerOptions
+    };
+  },
+  methods: {
+    visibleChange() {
+      this.filter.examId = this.exam.id;
+      this.toPage(1);
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async getList() {
+      if (!this.checkPrivilege("list", "list")) return;
+
+      const datas = {
+        ...this.filter,
+        pageNumber: this.current,
+        pageSize: this.size
+      };
+      if (this.createTime) {
+        datas.startCreateTime = this.createTime[0];
+        datas.endCreateTime = this.createTime[1];
+      }
+      if (datas.enable !== null && datas.enable !== "")
+        datas.enable = !!datas.enable;
+
+      const data = await examConfigQuery(datas);
+      this.examConfigList = data.records;
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    toAdd() {
+      this.curExamConfig = {};
+      this.$refs.ModifyExamConfigDetail.open();
+    },
+    toEdit(row) {
+      this.curExamConfig = row;
+      this.$refs.ModifyExamConfigDetail.open();
+    },
+    toDelete(row) {
+      this.$confirm(`确定要删除该配置吗?`, "提示", {
+        type: "warning"
+      })
+        .then(async () => {
+          await deleteExamConfig([row.id]);
+          this.$message.success("删除成功!");
+          this.deletePageLastItem();
+        })
+        .catch(() => {});
+    },
+    toEnable(row) {
+      const action = row.enable ? "禁用" : "启用";
+      this.$confirm(`确定要${action}该配置吗?`, "提示", {
+        type: "warning"
+      })
+        .then(async () => {
+          const enable = !row.enable;
+          await ableExamConfig({
+            idList: [row.id],
+            enable
+          });
+          row.enable = enable;
+          this.$message.success("操作成功!");
+        })
+        .catch(() => {});
+    }
+  }
+};
+</script>

+ 604 - 0
src/modules/base/components/ModifyExamConfigDetail.vue

@@ -0,0 +1,604 @@
+<template>
+  <el-dialog
+    class="modify-exam-config-detail"
+    :visible.sync="modalIsShow"
+    :title="title"
+    top="0"
+    width="800px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @open="visibleChange"
+  >
+    <el-form
+      ref="modalFormComp"
+      label-width="130px"
+      :rules="rules"
+      :model="modalForm"
+    >
+      <el-form-item prop="cardRuleId" label="题卡规则:">
+        <card-rule-select
+          ref="CardRuleSelect"
+          v-model.trim="modalForm.cardRuleId"
+          placeholder="请选择"
+          clearable
+        ></card-rule-select>
+        <div class="tips-info">
+          <p>说明:</p>
+          <p>
+            1、若选择全部通卡,则命题老师只能选择通卡,若选择题卡规则,则专卡和通卡均可选择
+          </p>
+          <p>
+            2、若选择题卡规则,命题老师在该考试下自主创建题卡时,版头样式及版头内容为该规则对应样式及版头。
+          </p>
+        </div>
+      </el-form-item>
+
+      <div class="part-box">
+        <h4 class="part-box-tips">
+          试卷&题卡印品:
+          <el-checkbox
+            v-show="infoShow"
+            v-model="allSelected"
+            label="全选"
+            @change="selectAll"
+          ></el-checkbox>
+        </h4>
+        <el-form-item prop="printContent" label="试卷、题卡:">
+          <el-checkbox-group
+            v-model="modalForm.printContent"
+            @change="() => checkSelectAll()"
+          >
+            <el-checkbox
+              v-for="(val, key) in PRINT_CONTENT_TYPE"
+              :key="key"
+              :label="key"
+              >{{ val }}</el-checkbox
+            >
+          </el-checkbox-group>
+        </el-form-item>
+        <el-form-item
+          v-if="modalForm.printContent.length"
+          prop="backupMethod"
+          label="备用数量:"
+        >
+          <el-select
+            v-model="modalForm.backupMethod"
+            class="mr-2"
+            size="small"
+            placeholder="请选择"
+          >
+            <el-option
+              v-for="(val, key) in PAPER_BACKUP_TYPE"
+              :key="key"
+              :value="key"
+              :label="val"
+            ></el-option>
+          </el-select>
+          <el-input-number
+            class="mr-1"
+            v-model="modalForm.backupCount"
+            size="small"
+            :min="0"
+            :max="200"
+            :step="1"
+            step-strictly
+            :controls="false"
+            style="width: 60px"
+          ></el-input-number>
+          <span>份</span>
+        </el-form-item>
+        <el-form-item
+          v-show="infoShow"
+          v-if="contentIncludesPaper"
+          prop="drawRule"
+          label="抽卷规则:"
+        >
+          <el-radio-group v-model="modalForm.drawRule">
+            <el-radio
+              v-for="(val, key) in DRAW_RULE_TYPE"
+              :label="key"
+              :key="key"
+              >{{ val }}</el-radio
+            >
+          </el-radio-group>
+          <div class="tips-info">
+            <p>说明:</p>
+            <p>
+              1.只抽取一次:不同印刷计划下,同一试卷编号下的卷型只能被抽取一次;
+            </p>
+            <p>
+              2.可反复抽取:不同印刷计划下,同一试卷编号下的卷型可重复抽取,系统默认优先抽取未曝光卷型。
+            </p>
+          </div>
+        </el-form-item>
+      </div>
+
+      <div class="part-box" v-show="infoShow">
+        <h4 class="part-box-tips">变量印品:</h4>
+        <el-form-item
+          v-for="(item, index) in modalForm.variableContent"
+          :key="item.type"
+          :label="`${TEMPLATE_CLASSIFY[item.type]}:`"
+          :prop="`variableContent.${index}.templateId`"
+          :rules="{
+            required: false,
+            validator: templateValidator,
+            trigger: 'change'
+          }"
+          :required="!!item.templateId.length"
+        >
+          <el-checkbox-group
+            v-model="item.templateId"
+            @change="vals => tempChange(vals, `variableContent.${index}`)"
+          >
+            <el-checkbox
+              v-for="temp in templateSources[item.type]"
+              :label="temp.id"
+              :key="temp.id"
+              >{{ temp.name }}</el-checkbox
+            >
+          </el-checkbox-group>
+          <div v-if="item.templateId.length">
+            <el-select
+              v-model="item.backupMethod"
+              class="mr-2"
+              size="small"
+              placeholder="请选择"
+            >
+              <el-option
+                v-for="(val, key) in PRINT_BACKUP_TYPE"
+                :key="key"
+                :value="key"
+                :label="val"
+              ></el-option>
+            </el-select>
+            <el-input-number
+              v-model="item.backupCount"
+              class="mr-1"
+              size="small"
+              :min="1"
+              :max="200"
+              :step="1"
+              step-strictly
+              :controls="false"
+              style="width: 60px"
+            ></el-input-number>
+            <span>份</span>
+          </div>
+        </el-form-item>
+      </div>
+
+      <div class="part-box" v-show="infoShow">
+        <h4 class="part-box-tips">普通印品:</h4>
+        <el-form-item
+          v-for="(item, index) in modalForm.ordinaryContent"
+          :key="item.type"
+          :label="`${TEMPLATE_CLASSIFY[item.type]}:`"
+          :prop="`ordinaryContent.${index}.templateId`"
+          :rules="{
+            required: false,
+            validator: templateValidator,
+            trigger: 'change'
+          }"
+          :required="!!item.templateId.length"
+        >
+          <el-checkbox-group
+            v-model="item.templateId"
+            @change="vals => tempChange(vals, `ordinaryContent.${index}`)"
+          >
+            <el-checkbox
+              v-for="temp in templateSources[item.type]"
+              :label="temp.id"
+              :key="temp.id"
+              >{{ temp.name }}</el-checkbox
+            >
+          </el-checkbox-group>
+          <div v-if="item.templateId.length">
+            <el-select
+              v-model="item.backupMethod"
+              class="mr-2"
+              size="small"
+              placeholder="请选择"
+            >
+              <el-option
+                v-for="(val, key) in PRINT_BACKUP_TYPE"
+                :key="key"
+                :value="key"
+                :label="val"
+              ></el-option>
+            </el-select>
+            <el-input-number
+              v-model="item.backupCount"
+              class="mr-1"
+              size="small"
+              :min="1"
+              :max="200"
+              :step="1"
+              step-strictly
+              :controls="false"
+              style="width: 60px"
+            ></el-input-number>
+            <span>份</span>
+          </div>
+        </el-form-item>
+      </div>
+
+      <el-form-item prop="selectedPrint"></el-form-item>
+
+      <el-form-item prop="orgIds" label="适用范围:">
+        <select-orgs v-model="modalForm.orgIds" ref="SelectOrgs"></select-orgs>
+      </el-form-item>
+    </el-form>
+
+    <div slot="footer">
+      <el-button type="primary" :disabled="isSubmit" @click="submit"
+        >确认</el-button
+      >
+      <el-button @click="cancel">取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import {
+  PRINT_CONTENT_TYPE,
+  DRAW_RULE_TYPE,
+  PRINT_BACKUP_TYPE,
+  PAPER_BACKUP_TYPE,
+  TEMPLATE_CLASSIFY
+} from "@/constants/enumerate";
+import { deepCopy } from "@/plugins/utils";
+import { updateExamConfig } from "../api";
+import { printPlanTemplateList } from "../../print/api";
+import SelectOrgs from "./SelectOrgs";
+
+const initModalForm = {
+  id: null,
+  cardRuleId: "",
+  orgIds: [],
+  printContent: [],
+  backupMethod: "ROOM",
+  backupCount: 1,
+  drawRule: "ONE",
+  variableContent: [
+    {
+      type: "SIGN",
+      templateId: [],
+      oldTemplateId: [],
+      backupMethod: "ROOM",
+      backupCount: 1
+    },
+    {
+      type: "PACKAGE",
+      templateId: [],
+      oldTemplateId: [],
+      backupMethod: "ROOM",
+      backupCount: 1
+    }
+  ],
+  ordinaryContent: [
+    {
+      type: "CHECK_IN",
+      templateId: [],
+      oldTemplateId: [],
+      backupMethod: "ROOM",
+      backupCount: 1
+    }
+  ]
+};
+
+export default {
+  name: "modify-exam-config-detail",
+  components: { SelectOrgs },
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  computed: {
+    isEdit() {
+      return !!this.instance.id;
+    },
+    title() {
+      return (this.isEdit ? "编辑" : "新增") + "考试配置";
+    },
+    contentIncludesPaper() {
+      return this.modalForm.printContent.includes("PAPER");
+    }
+  },
+  data() {
+    const printContentValidator = (rule, value, callback) => {
+      if (!this.modalForm.printContent.length) {
+        return callback(new Error("请选择试卷"));
+      }
+
+      callback();
+    };
+    const backupMethodValidator = (rule, value, callback) => {
+      if (!this.modalForm.printContent.length) {
+        return callback();
+      }
+      if (!value) {
+        return callback(new Error("请选择备份方式"));
+      }
+      if (!this.modalForm.backupCount && this.modalForm.backupCount !== 0) {
+        return callback(new Error("请输入备份数量"));
+      }
+
+      callback();
+    };
+    const selectedPrintValidator = (rule, value, callback) => {
+      const printInfo = [
+        ...this.modalForm.variableContent,
+        ...this.modalForm.ordinaryContent
+      ];
+      const hasPrintInfo = printInfo.some(item => item.templateId.length);
+
+      if (hasPrintInfo || this.modalForm.printContent.length) {
+        callback();
+      } else {
+        callback(new Error("必须选择一项印品"));
+      }
+    };
+
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      modalForm: deepCopy(initModalForm),
+      PRINT_CONTENT_TYPE,
+      DRAW_RULE_TYPE,
+      PRINT_BACKUP_TYPE,
+      PAPER_BACKUP_TYPE,
+      TEMPLATE_CLASSIFY,
+      variableContent: [],
+      ordinaryContent: [],
+      templateSources: {},
+      oldPrintContent: [],
+      allSelected: false,
+      infoShow: true,
+      rules: {
+        cardRuleId: [
+          {
+            required: true,
+            message: "请选择题卡规则",
+            trigger: "change"
+          }
+        ],
+        printContent: [
+          {
+            required: true,
+            validator: printContentValidator,
+            trigger: "change"
+          }
+        ],
+        backupMethod: [
+          {
+            required: true,
+            validator: backupMethodValidator,
+            trigger: "change"
+          }
+        ],
+        drawRule: [
+          {
+            required: true,
+            message: "请选择抽卷规则",
+            trigger: "change"
+          }
+        ],
+        selectedPrint: [
+          {
+            required: false,
+            validator: selectedPrintValidator,
+            trigger: "change"
+          }
+        ],
+        orgIds: [
+          {
+            required: true,
+            validator: (rule, value, callback) => {
+              if (value.length) {
+                callback();
+              } else {
+                callback(new Error("请选择适用学院"));
+              }
+            },
+            trigger: "change"
+          }
+        ]
+      }
+    };
+  },
+  mounted() {
+    this.getTemplates();
+  },
+  methods: {
+    visibleChange() {
+      this.initData(this.instance);
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async getTemplates() {
+      const data = await printPlanTemplateList();
+      const templateSources = {};
+      const templates = [...data.variable, ...data.ordinary];
+      templates.forEach(item => {
+        templateSources[item.type] = item.template;
+      });
+      this.templateSources = templateSources;
+    },
+    async initData(val) {
+      if (val.id) {
+        this.modalForm = this.$objAssign(
+          deepCopy(initModalForm),
+          val.printPlan
+        );
+        const transformInfo = item => {
+          const templateIds = item.templateId ? [item.templateId] : [];
+          return {
+            type: item.type,
+            templateId: templateIds,
+            oldTemplateId: templateIds,
+            backupMethod: item.backupMethod,
+            backupCount: item.backupCount
+          };
+        };
+        this.modalForm.variableContent = val.variableContent.map(transformInfo);
+        this.modalForm.ordinaryContent = val.ordinaryContent.map(transformInfo);
+      } else {
+        let modalForm = deepCopy(initModalForm);
+        modalForm.variableContent = modalForm.variableContent.filter(
+          item => this.templateSources[item.type]
+        );
+        modalForm.ordinaryContent = modalForm.ordinaryContent.filter(
+          item => this.templateSources[item.type]
+        );
+        this.modalForm = modalForm;
+      }
+
+      if (!this.checkHasSelect()) {
+        this.allSelected = true;
+        this.selectAll(this.allSelected);
+      } else {
+        this.checkSelectAll();
+      }
+    },
+    getData() {
+      const printPlan = { ...this.modalForm };
+      const transformInfo = item => {
+        const templateId = item.templateId.join();
+        const template = this.templateSources[item.type].find(
+          temp => temp.id === templateId
+        );
+        return {
+          type: item.type,
+          templateId,
+          attachmentId: template && template.attachmentId,
+          backupMethod: item.backupMethod,
+          backupCount: item.backupCount
+        };
+      };
+      printPlan.variableContent = this.modalForm.variableContent.map(
+        transformInfo
+      );
+      printPlan.ordinaryContent = this.modalForm.ordinaryContent.map(
+        transformInfo
+      );
+
+      return {
+        id: this.modalForm.id,
+        cardRuleId: this.modalForm.cardRuleId,
+        orgIds: this.modalForm.orgIds,
+        printPlan
+      };
+    },
+    selectAll(selected) {
+      if (selected) {
+        this.modalForm.printContent = Object.keys(this.PRINT_CONTENT_TYPE);
+        this.modalForm.variableContent.forEach(item => {
+          if (item.templateId && item.templateId.length) return;
+
+          const source = this.templateSources[item.type][0];
+          item.templateId = source && [source.id];
+          item.oldTemplateId = source && [source.id];
+        });
+        this.modalForm.ordinaryContent.forEach(item => {
+          if (item.templateId && item.templateId.length) return;
+
+          const source = this.templateSources[item.type][0];
+          item.templateId = source && [source.id];
+          item.oldTemplateId = source && [source.id];
+        });
+      } else {
+        this.modalForm.printContent = [];
+        this.modalForm.variableContent.forEach(item => {
+          item.templateId = [];
+          item.oldTemplateId = [];
+        });
+        this.modalForm.ordinaryContent.forEach(item => {
+          item.templateId = [];
+          item.oldTemplateId = [];
+        });
+      }
+    },
+    checkSelectAll() {
+      const vNotSelected = this.modalForm.variableContent.some(
+        item => !item.templateId.length
+      );
+      const oNotSelected = this.modalForm.ordinaryContent.some(
+        item => !item.templateId.length
+      );
+      const pNotSelected =
+        this.modalForm.printContent.length <
+        Object.keys(this.PRINT_CONTENT_TYPE).length;
+
+      const selecteds = [vNotSelected, oNotSelected, pNotSelected];
+
+      this.allSelected = !selecteds.some(item => item);
+    },
+    checkHasSelect() {
+      const vSelected = this.modalForm.variableContent.some(
+        item => item.templateId.length
+      );
+      const oSelected = this.modalForm.ordinaryContent.some(
+        item => item.templateId.length
+      );
+      const pSelected = !!this.modalForm.printContent.length;
+
+      const selecteds = [vSelected, oSelected, pSelected];
+
+      return selecteds.some(item => item);
+    },
+    templateValidator(rule, value, callback) {
+      const [field, index] = rule.field.split(".");
+      const val = this.modalForm[field][index];
+      if (val.templateId.length) {
+        if (!val.backupMethod) {
+          return callback(new Error("请选择备份方式"));
+        }
+        if (!val.backupCount) {
+          return callback(new Error("请输入备份数量"));
+        }
+        callback();
+      } else {
+        callback();
+      }
+    },
+    tempChange(vals, name) {
+      const [field, index] = name.split(".");
+      const info = this.modalForm[field][index];
+      const newVals = vals.filter(item => !info.oldTemplateId.includes(item));
+      info.templateId = newVals;
+      info.oldTemplateId = newVals;
+
+      this.checkSelectAll();
+      this.$refs.modalFormComp.validateField("selectedPrint");
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      let datas = this.getData();
+      const data = await updateExamConfig(datas).catch(() => {
+        this.isSubmit = false;
+      });
+
+      if (!data) return;
+
+      this.isSubmit = false;
+      this.$message.success(this.title + "成功!");
+      this.$emit("modified");
+      this.cancel();
+    }
+  }
+};
+</script>

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

@@ -19,6 +19,7 @@ import CourseManage from "./views/CourseManage.vue";
 import ClazzManage from "./views/ClazzManage.vue";
 import ClazzManage from "./views/ClazzManage.vue";
 import SemesterManage from "./views/SemesterManage.vue";
 import SemesterManage from "./views/SemesterManage.vue";
 import MajorManage from "./views/MajorManage.vue";
 import MajorManage from "./views/MajorManage.vue";
+import ExamManage from "./views/ExamManage.vue";
 
 
 export default [
 export default [
   {
   {
@@ -110,5 +111,10 @@ export default [
     path: "/base/sms-manage",
     path: "/base/sms-manage",
     name: "SmsManage",
     name: "SmsManage",
     component: SmsManage
     component: SmsManage
+  },
+  {
+    path: "/base/exam-manage",
+    name: "ExamManage",
+    component: ExamManage
   }
   }
 ];
 ];

+ 230 - 0
src/modules/base/views/ExamManage.vue

@@ -0,0 +1,230 @@
+<template>
+  <div class="exam-manage">
+    <div class="part-box part-box-filter part-box-flex">
+      <el-form ref="FilterForm" label-position="left" label-width="85px" inline>
+        <template v-if="checkPrivilege('condition', 'condition')">
+          <el-form-item label="学期:">
+            <semester-select
+              v-model="filter.semesterId"
+              placeholder="学期"
+              clearable
+            ></semester-select>
+          </el-form-item>
+          <el-form-item label="启用/禁用:" label-width="90px">
+            <el-select
+              v-model="filter.enable"
+              style="width: 120px;"
+              placeholder="启用/禁用"
+              clearable
+            >
+              <el-option
+                v-for="(val, key) in ABLE_TYPE"
+                :key="key"
+                :value="key * 1"
+                :label="val"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+        </template>
+        <el-form-item>
+          <el-button
+            v-if="checkPrivilege('button', 'select')"
+            type="primary"
+            @click="toPage(1)"
+            >查询</el-button
+          >
+        </el-form-item>
+      </el-form>
+      <div class="part-box-action">
+        <el-button
+          v-if="checkPrivilege('button', 'add')"
+          type="primary"
+          icon="el-icon-circle-plus-outline"
+          @click="toAdd"
+          >新增</el-button
+        >
+      </div>
+    </div>
+    <div class="part-box part-box-pad">
+      <el-table ref="TableList" :data="exams">
+        <el-table-column
+          type="index"
+          label="序号"
+          width="70"
+          :index="indexMethod"
+        ></el-table-column>
+        <el-table-column prop="name" label="考试名称"></el-table-column>
+        <el-table-column prop="semesterName" label="学年学期"></el-table-column>
+        <el-table-column prop="examType" label="考试类型">
+          <span slot-scope="scope">
+            {{ scope.row.examType | examTypeFilter }}
+          </span>
+        </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="enable" label="启用/禁用" width="100">
+          <template slot-scope="scope">
+            {{ scope.row.enable | enableFilter }}
+          </template>
+        </el-table-column>
+        <el-table-column class-name="action-column" label="操作" width="180px">
+          <template slot-scope="scope">
+            <el-button
+              v-if="checkPrivilege('link', 'edit')"
+              class="btn-primary"
+              type="text"
+              @click="toEdit(scope.row)"
+              >编辑</el-button
+            >
+            <el-button
+              v-if="checkPrivilege('link', 'edit')"
+              class="btn-primary"
+              type="text"
+              @click="toEditConfig(scope.row)"
+              >设置</el-button
+            >
+            <el-button
+              v-if="checkPrivilege('link', 'Enable')"
+              :class="scope.row.enable ? 'btn-danger' : 'btn-primary'"
+              type="text"
+              @click="toEnable(scope.row)"
+              >{{ scope.row.enable ? "禁用" : "启用" }}</el-button
+            >
+            <el-button
+              class="btn-danger"
+              type="text"
+              @click="toDelete(scope.row)"
+              >删除</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>
+
+    <!-- modify-exam -->
+    <modify-exam
+      ref="ModifyExam"
+      :instance="curExam"
+      @modified="getList"
+    ></modify-exam>
+    <!-- modify-exam-config -->
+    <modify-exam-config
+      ref="ModifyExamConfig"
+      :exam="curExam"
+    ></modify-exam-config>
+  </div>
+</template>
+
+<script>
+import { examListQuery, deleteExam, ableExam } from "../api";
+import { ABLE_TYPE } from "@/constants/enumerate";
+import ModifyExam from "../components/ModifyExam";
+import ModifyExamConfig from "../components/ModifyExamConfig";
+
+export default {
+  name: "exam-manage",
+  components: { ModifyExam, ModifyExamConfig },
+  data() {
+    return {
+      filter: {
+        semesterId: "",
+        enable: ""
+      },
+      ABLE_TYPE,
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      exams: [
+        {
+          id: "11",
+          name: "齐12考试",
+          semesterName: "上学期",
+          createTime: 19245121323,
+          semesterId: "12",
+          examType: "OFFLINE",
+          examTypeMode: "ONE",
+          enable: true
+        }
+      ],
+      curExam: {}
+    };
+  },
+  mounted() {
+    // this.getList();
+  },
+  methods: {
+    async getList() {
+      if (!this.checkPrivilege("list", "list")) return;
+
+      const datas = {
+        ...this.filter,
+        pageNumber: this.current,
+        pageSize: this.size
+      };
+      if (datas.enable !== null && datas.enable !== "")
+        datas.enable = !!datas.enable;
+
+      const data = await examListQuery(datas);
+      this.exams = data.records;
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    toAdd() {
+      this.curExam = {};
+      this.$refs.ModifyExam.open();
+    },
+    toEdit(row) {
+      this.curExam = row;
+      this.$refs.ModifyExam.open();
+    },
+    toEditConfig(row) {
+      this.curExam = row;
+      this.$refs.ModifyExamConfig.open();
+    },
+    toDelete(row) {
+      this.$confirm(`确定要删除学期【${row.semesterName}】吗?`, "提示", {
+        type: "warning"
+      })
+        .then(async () => {
+          await deleteExam([row.id]);
+          this.$message.success("删除成功!");
+          this.deletePageLastItem();
+        })
+        .catch(() => {});
+    },
+    toEnable(row) {
+      const action = row.enable ? "禁用" : "启用";
+      this.$confirm(`确定要${action}学期【${row.semesterName}】吗?`, "提示", {
+        type: "warning"
+      })
+        .then(async () => {
+          const enable = !row.enable;
+          await ableExam({
+            idList: [row.id],
+            enable
+          });
+          row.enable = enable;
+          this.$message.success("操作成功!");
+        })
+        .catch(() => {});
+    }
+  }
+};
+</script>

+ 5 - 1
src/plugins/filters.js

@@ -14,7 +14,8 @@ import {
   MARK_TASK_SYNC_STATUS,
   MARK_TASK_SYNC_STATUS,
   STMMS_SYNC_TYPE,
   STMMS_SYNC_TYPE,
   SYNC_PRINT_STATUS,
   SYNC_PRINT_STATUS,
-  PRINT_PDF_TYPE
+  PRINT_PDF_TYPE,
+  EXAM_TYPE
 } from "../constants/enumerate";
 } from "../constants/enumerate";
 import { formatDate } from "../plugins/utils";
 import { formatDate } from "../plugins/utils";
 
 
@@ -88,3 +89,6 @@ Vue.filter("syncPrintStatusFilter", function(val) {
 Vue.filter("printPdfTypeFilter", function(val) {
 Vue.filter("printPdfTypeFilter", function(val) {
   return PRINT_PDF_TYPE[val] || DEFAULT_FIELD;
   return PRINT_PDF_TYPE[val] || DEFAULT_FIELD;
 });
 });
+Vue.filter("examTypeFilter", function(val) {
+  return EXAM_TYPE[val] || DEFAULT_FIELD;
+});

+ 2 - 1
src/views/Home.vue

@@ -123,6 +123,7 @@
 
 
 <script>
 <script>
 import { mapState, mapActions } from "vuex";
 import { mapState, mapActions } from "vuex";
+// import localMenus from "@/constants/privileges";
 import localMenus from "@/constants/menus";
 import localMenus from "@/constants/menus";
 import { sysMenu, logout } from "../modules/login/api";
 import { sysMenu, logout } from "../modules/login/api";
 import ResetPwd from "../modules/base/components/ResetPwd";
 import ResetPwd from "../modules/base/components/ResetPwd";
@@ -173,7 +174,7 @@ export default {
     ...mapState("exam", ["waitTaskCount"])
     ...mapState("exam", ["waitTaskCount"])
   },
   },
   created() {
   created() {
-    this.initData();
+    this.initData1();
   },
   },
   methods: {
   methods: {
     ...mapActions("exam", ["updateWaitTaskCount"]),
     ...mapActions("exam", ["updateWaitTaskCount"]),