Răsfoiți Sursa

下载管理

zhangjie 3 ani în urmă
părinte
comite
c0e945c3f3

+ 1 - 1
package.json

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

+ 11 - 1
src/assets/styles/base.scss

@@ -361,7 +361,7 @@ body {
 
 // other
 .btn-danger {
-  &.el-button--text {
+  &.el-button--text:not(.is-disabled) {
     color: $--color-danger !important;
 
     &:hover {
@@ -369,6 +369,9 @@ body {
       color: mix(#000, $--color-danger, 20%) !important;
     }
   }
+  &.is-disabled {
+    color: $--color-text-gray-4;
+  }
 }
 .btn-primary {
   &.el-button--text:not(.is-disabled) {
@@ -447,12 +450,19 @@ body {
 .mb-4 {
   margin-bottom: 20px;
 }
+.mlr-1 {
+  margin-left: 5px;
+  margin-right: 5px;
+}
 .width-full {
   width: 100%;
 }
 .width-400 {
   width: 400px;
 }
+.width-80 {
+  width: 80px;
+}
 
 // other
 .tips-info {

+ 1 - 1
src/assets/styles/element-ui-costom.scss

@@ -346,7 +346,7 @@
       margin: 0 5px;
       border: none !important;
       outline: none !important;
-      &:hover {
+      &:not(.is-disabled):hover {
         transform: scale(1.1);
       }
     }

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

@@ -822,3 +822,97 @@
     }
   }
 }
+// modify-mark-params
+.modify-mark-params {
+  .mark-body {
+    margin: 40px 0;
+  }
+  .structure-desc {
+    margin-bottom: 10px;
+  }
+  .expand-btn {
+    display: inline-block;
+    width: 20px;
+    height: 20px;
+    line-height: 19px;
+    font-size: 13px;
+    border: 1px solid #6f7482;
+    border-radius: 3px;
+    text-align: center;
+    cursor: pointer;
+
+    &:hover {
+      color: $--color-primary;
+      border-color: $--color-primary;
+    }
+
+    &-unexpand {
+      background-color: #ebeffc;
+    }
+  }
+  .row-unexpand-sub {
+    display: none;
+  }
+  .marker-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 10px;
+  }
+}
+// modify-marker-question
+.modify-marker-question {
+  .el-dialog {
+    width: 900px;
+    margin: 0 auto;
+
+    &__header,
+    &__footer {
+      display: none;
+    }
+    &__body {
+      padding: 20px;
+    }
+  }
+
+  .marker-box {
+    height: 600px;
+    background-color: $--color-background;
+    border-radius: $--border-radius;
+    padding: 15px;
+    display: flex;
+    flex-direction: column;
+
+    &-uq {
+      display: block;
+      height: 100%;
+      overflow: auto;
+      background-color: #fff;
+      border: 1px solid $--color-background;
+
+      .el-tag {
+        margin: 3px;
+      }
+    }
+  }
+  .user-title {
+    margin-bottom: 10px;
+    flex-grow: 0;
+  }
+  .user-search {
+    margin-bottom: 10px;
+    flex-grow: 0;
+  }
+  .user-tree {
+    padding: 10px;
+    border-radius: 4px;
+    background-color: #fff;
+    overflow: auto;
+    flex-grow: 2;
+  }
+
+  .marker-footer {
+    margin-top: 15px;
+    text-align: center;
+  }
+}

Fișier diff suprimat deoarece este prea mare
+ 3635 - 2837
src/constants/menus-data.js


+ 0 - 50
src/constants/menus.js

@@ -1,50 +0,0 @@
-import navs from "./menus-data";
-
-let dataList = [];
-const idFunc = () => {
-  let id = 1;
-  return function() {
-    return "" + id++;
-  };
-};
-const getId = idFunc();
-const extraFields = ["urls", "buttons", "links", "lists", "conditions"];
-const checkField = obj => {
-  return extraFields.some(field => obj[field] && obj[field].length);
-};
-
-const treeToList = (tree, parentId = "-1") => {
-  tree.forEach(menu => {
-    let id = getId();
-    let nmenu = {
-      id,
-      parentId,
-      name: menu.name,
-      url: menu.url
-    };
-
-    if (checkField(menu)) {
-      extraFields.forEach(field => {
-        nmenu[field] = [];
-        if (menu[field]) {
-          nmenu[field] = menu[field].map(item => {
-            return {
-              name: item.name,
-              type: item.type,
-              parentId: id,
-              url: item.url
-            };
-          });
-        }
-      });
-    }
-    dataList.push(nmenu);
-
-    if (menu.children && menu.children.length) treeToList(menu.children, id);
-  });
-};
-treeToList(navs);
-
-// console.log(JSON.stringify(dataList));
-
-export default dataList;

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

@@ -240,3 +240,19 @@ export const statisticsDelete = ids => {
 export const statisticsFreshen = () => {
   return $postParam("/api/admin/statistics/freshen", {});
 };
+// download-manage
+export const dataDownloadList = datas => {
+  return $postParam("/api/admin/data/download/page", datas);
+};
+export const dataDownloadDetail = id => {
+  return $postParam(
+    "/api/admin/data/download/download_one",
+    { id },
+    {
+      responseType: "blob"
+    }
+  );
+};
+export const dataBatchDownload = datas => {
+  return $post("/api/admin/data/download/download_batch", datas);
+};

+ 225 - 0
src/modules/exam/components/DownloadSetDialog.vue

@@ -0,0 +1,225 @@
+<template>
+  <el-dialog
+    class="download-set-dialog"
+    :visible.sync="modalIsShow"
+    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="120px"
+    >
+      <el-form-item prop="paperFileDownloadContentType" label="下载文件:">
+        <el-select
+          v-model="modalForm.paperFileDownloadContentType"
+          placeholder="下载文件"
+          clearable
+          @change="paperFileDownloadContentTypeChange"
+        >
+          <el-option
+            v-for="(val, key) in FILE_TYPE"
+            :key="key"
+            :value="key"
+            :label="val"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item
+        v-if="modalForm.paperFileDownloadContentType !== 'ONLY_CARD'"
+        prop="paperFileDownloadExposureStatus"
+        label="试卷状态:"
+      >
+        <el-select
+          v-model="modalForm.paperFileDownloadExposureStatus"
+          placeholder="试卷状态"
+          clearable
+        >
+          <el-option
+            v-for="(val, key) in PAPER_STATUS"
+            :key="key"
+            :value="key"
+            :label="val"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item prop="nameRule" label="文件命名规则:">
+        <el-checkbox-group v-model="nameRule" @change="nameRuleChange">
+          <el-checkbox
+            v-for="rule in NAME_RULE"
+            :key="rule.code"
+            :label="rule.code"
+            :disabled="rule.disabled"
+            >{{ rule.name }}</el-checkbox
+          >
+        </el-checkbox-group>
+      </el-form-item>
+      <el-form-item label="命名示例:">
+        <span>{{ nameRuleContent }}</span>
+      </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 { dataBatchDownload } from "../api";
+
+const initModalForm = {
+  paperFileDownloadContentType: "",
+  paperFileDownloadExposureStatus: null,
+  namedByCourseInfo: false,
+  namedByPaperNumber: false,
+  namedByOriginalFile: false
+};
+
+export default {
+  name: "download-set-dialog",
+  props: {
+    downloadSet: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      nameRule: [],
+      modalForm: { ...initModalForm },
+      rules: {
+        paperFileDownloadContentType: [
+          {
+            required: true,
+            message: "请选择下载文件",
+            trigger: "change"
+          }
+        ],
+        paperFileDownloadExposureStatus: [
+          {
+            required: true,
+            message: "请选择试卷状态",
+            trigger: "change"
+          }
+        ],
+        nameRule: [
+          {
+            required: true,
+            validator: (rule, value, callback) => {
+              if (!this.nameRule || !this.nameRule.length) {
+                return callback(new Error("请设置文件命名规则"));
+              }
+
+              callback();
+            },
+            trigger: "change"
+          }
+        ]
+      },
+      PAPER_STATUS: {
+        EXPOSED_PAPER: "已曝光的试卷",
+        UNEXPOSED_PAPER: "未曝光的试卷",
+        ALL_PAPER: "全部试卷"
+      },
+      FILE_TYPE: {
+        ONLY_PAPER: "试卷",
+        ONLY_CARD: "题卡",
+        PAPER_AND_CARD: "试卷和题卡"
+      },
+      NAME_RULE: [
+        {
+          code: "namedByCourseInfo",
+          name: "课程名称-课程代码",
+          disabled: false
+        },
+        {
+          code: "namedByPaperNumber",
+          name: "试卷编号",
+          disabled: false
+        },
+        {
+          code: "namedByOriginalFile",
+          name: "原文件名",
+          disabled: false
+        }
+      ]
+    };
+  },
+  computed: {
+    nameRuleContent() {
+      const names = this.NAME_RULE.filter(item =>
+        this.nameRule.includes(item.code)
+      )
+        .map(item => item.name)
+        .join("-");
+      return names ? `${names}.pdf` : "";
+    }
+  },
+  methods: {
+    visibleChange() {
+      this.modalForm = { ...initModalForm };
+      this.nameRule = [];
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    nameRuleChange() {
+      this.NAME_RULE.forEach(item => {
+        this.modalForm[item.code] = this.nameRule.includes(item.code);
+      });
+    },
+    paperFileDownloadContentTypeChange(val) {
+      if (val === "ONLY_CARD") {
+        const originalItem = this.NAME_RULE.find(
+          item => item.code === "namedByOriginalFile"
+        );
+        originalItem.disabled = true;
+
+        this.nameRule = this.nameRule.filter(
+          item => item !== "namedByOriginalFile"
+        );
+        this.modalForm.paperFileDownloadExposureStatus = null;
+      } else {
+        this.NAME_RULE.forEach(item => (item.disabled = false));
+      }
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      let datas = { ...this.modalForm, ...this.downloadSet };
+      const data = await dataBatchDownload(datas).catch(() => {
+        this.isSubmit = false;
+        this.$message.error("下载任务提交失败,请重新尝试!");
+      });
+
+      if (!data) return;
+
+      this.isSubmit = false;
+      this.$message.success(
+        "下载任务已提交,请在系统设置-任务管理中下载文件!"
+      );
+      this.cancel();
+    }
+  }
+};
+</script>

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

@@ -7,6 +7,7 @@ import TaskReviewManage from "./views/TaskReviewManage.vue";
 import TaskPaperManage from "./views/TaskPaperManage.vue";
 import DataTaskManage from "./views/DataTaskManage.vue";
 import StatisticsManage from "./views/StatisticsManage.vue";
+import DownloadManage from "./views/DownloadManage.vue";
 
 export default [
   {
@@ -48,5 +49,10 @@ export default [
     path: "/exam/statistics-manage",
     name: "StatisticsManage",
     component: StatisticsManage
+  },
+  {
+    path: "/exam/download-manage",
+    name: "DownloadManage",
+    component: DownloadManage
   }
 ];

+ 213 - 0
src/modules/exam/views/DownloadManage.vue

@@ -0,0 +1,213 @@
+<template>
+  <div class="download-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"></semester-select>
+          </el-form-item>
+          <el-form-item label="考试:">
+            <exam-select
+              v-model="filter.examId"
+              :semester-id="filter.semesterId"
+            ></exam-select>
+          </el-form-item>
+          <el-form-item label="学院:">
+            <college-select
+              v-model="filter.orgId"
+              :semester-id="filter.semesterId"
+              cascader
+              placeholder="学院"
+            ></college-select>
+          </el-form-item>
+          <el-form-item label="课程名称:">
+            <el-input
+              v-model="filter.courseName"
+              placeholder="课程名称"
+              clearable
+            ></el-input>
+          </el-form-item>
+        </template>
+
+        <el-form-item label-width="0px">
+          <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', 'download')"
+          icon="el-icon-circle-plus-outline"
+          type="info"
+          :disabled="!filterHasQuery"
+          @click="toBatchDownload"
+        >
+          批量下载
+        </el-button>
+      </div>
+    </div>
+
+    <div class="part-box part-box-pad">
+      <el-table
+        ref="TableList"
+        :data="dataList"
+        @selection-change="handleSelectionChange"
+      >
+        <el-table-column
+          type="selection"
+          width="55"
+          align="center"
+        ></el-table-column>
+        <el-table-column
+          type="index"
+          label="序号"
+          width="70"
+          :index="indexMethod"
+        ></el-table-column>
+        <el-table-column prop="courseName" label="课程(代码)">
+          <template slot-scope="scope">
+            {{ scope.row.courseName }}({{ scope.row.courseCode }})
+          </template>
+        </el-table-column>
+        <el-table-column prop="paperNumber" label="试卷编号"></el-table-column>
+        <el-table-column prop="paperType" label="全部卷型" width="100">
+        </el-table-column>
+        <el-table-column
+          prop="unexposedPaperType"
+          label="未曝光卷型"
+          width="100"
+        ></el-table-column>
+        <el-table-column
+          class-name="action-column"
+          label="操作"
+          width="120px"
+          align="center"
+        >
+          <template slot-scope="scope">
+            <el-button
+              v-if="checkPrivilege('link', 'download')"
+              class="btn-primary"
+              type="text"
+              @click="toDownload(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>
+
+    <!-- DownloadSetDialog -->
+    <download-set-dialog :download-set="downloadSet" ref="DownloadSetDialog">
+    </download-set-dialog>
+  </div>
+</template>
+
+<script>
+import DownloadSetDialog from "../components/DownloadSetDialog";
+import { downloadByApi } from "@/plugins/download";
+import { dataDownloadList, dataDownloadDetail } from "../api";
+
+export default {
+  name: "download-manage",
+  components: {
+    DownloadSetDialog
+  },
+  data() {
+    return {
+      filter: {
+        semesterId: "",
+        examId: "",
+        orgId: "",
+        courseName: ""
+      },
+      queriedFilter: {},
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      downloading: false,
+      multipleSelection: [],
+      dataList: [],
+      curRow: {},
+      downloadSet: {}
+    };
+  },
+  computed: {
+    filterHasQuery() {
+      return !Object.keys(this.filter).some(
+        k => this.filter[k] !== this.queriedFilter[k]
+      );
+    }
+  },
+  mounted() {
+    this.getList();
+  },
+  methods: {
+    async getList() {
+      if (!this.checkPrivilege("list", "list")) return;
+
+      const datas = {
+        ...this.filter,
+        pageNumber: this.current,
+        pageSize: this.size
+      };
+      const data = await dataDownloadList(datas);
+      this.dataList = data.records;
+      this.total = data.total;
+      this.queriedFilter = { ...this.filter };
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    handleSelectionChange(val) {
+      this.multipleSelection = val.map(item => item.id);
+    },
+    toBatchDownload() {
+      if (this.multipleSelection.length) {
+        this.downloadSet = {
+          idSet: this.multipleSelection
+        };
+      } else {
+        this.downloadSet = {
+          ...this.filter,
+          idSet: this.multipleSelection
+        };
+      }
+      this.$refs.DownloadSetDialog.open();
+    },
+    async toDownload(row) {
+      if (this.downloading) return;
+      this.downloading = true;
+
+      const res = await downloadByApi(() => {
+        return dataDownloadDetail(row.id);
+      }).catch(e => {
+        console.log(e);
+      });
+      this.downloading = false;
+
+      if (!res) {
+        this.$message.error("下载失败,请重新尝试!");
+        return;
+      }
+
+      this.$message.success("下载成功!");
+    }
+  }
+};
+</script>

+ 1 - 1
src/modules/exam/views/TaskPaperManage.vue

@@ -374,7 +374,7 @@ export default {
 
       if (res) {
         this.$message.success(
-          "导出任务已提交,请在数据管理-任务管理中下载文件!"
+          "导出任务已提交,请在系统设置-任务管理中下载文件!"
         );
       } else {
         this.$message.error("导出任务提交失败,请重新尝试!");

+ 1 - 1
src/modules/exam/views/TaskReviewManage.vue

@@ -444,7 +444,7 @@ export default {
 
       if (res) {
         this.$message.success(
-          "导出任务已提交,请在数据管理-任务管理中下载文件!"
+          "导出任务已提交,请在系统设置-任务管理中下载文件!"
         );
       } else {
         this.$message.error("导出任务提交失败,请重新尝试!");

+ 1 - 1
src/modules/print/views/BusinessDataExport.vue

@@ -307,7 +307,7 @@ export default {
 
       if (res) {
         this.$message.success(
-          "导出任务已提交,请在数据管理-任务管理中下载文件!"
+          "导出任务已提交,请在系统设置-任务管理中下载文件!"
         );
       } else {
         this.$message.error("导出任务提交失败,请重新尝试!");

+ 202 - 0
src/modules/stmms/components/MarkPaperMarker.vue

@@ -0,0 +1,202 @@
+<template>
+  <div class="mark-paper-marker">
+    <div class="marker-header">
+      <p
+        class="marker-desc color-danger"
+        v-if="questionCount > markerQuestionCount"
+      >
+        本试卷共<span class="mlr-1">{{ questionCount }}</span
+        >道小题,已经设置<span class="mlr-1">{{ markerQuestionCount }}</span
+        >道,还有<span class="mlr-1">{{
+          questionCount - markerQuestionCount
+        }}</span
+        >道未设置评卷员,请继续设置,确保全部题目均已分配评卷员!
+      </p>
+      <p class="marker-desc color-success" v-else>
+        本试卷共<span class="mlr-1">{{ questionCount }}</span
+        >道小题,已全部设置评卷员!
+      </p>
+      <el-button type="primary" @click="toAdd">新增</el-button>
+    </div>
+
+    <el-table :data="markers" border>
+      <el-table-column type="index" width="50"> </el-table-column>
+      <el-table-column label="评卷员"></el-table-column>
+      <el-table-column label="评卷方式">
+        <template slot-scope="scope">
+          <el-radio-group v-model="scope.row.markType">
+            <el-radio
+              v-for="(val, key) in MARK_TYPE"
+              :key="key"
+              :label="key * 1"
+              >{{ val }}</el-radio
+            >
+          </el-radio-group>
+          <span v-if="scope.row.markType === 2">
+            仲裁阀值:<el-input-number
+              v-model="scope.row.arbitration"
+              class="width-80"
+              size="small"
+              :min="0.1"
+              :max="100"
+              :step="0.1"
+              step-strictly
+              :controls="false"
+            >
+            </el-input-number>
+          </span>
+        </template>
+      </el-table-column>
+      <el-table-column label="评阅题目">
+        <template slot-scope="scope">
+          {{ scope.row.questions | questionsFilter }}
+        </template>
+      </el-table-column>
+      <el-table-column class-name="action-column" label="操作" width="160px">
+        <template slot-scope="scope">
+          <el-button class="btn-primary" type="text" @click="toEdit(scope.row)"
+            >编辑</el-button
+          >
+          <el-button class="btn-danger" type="text" @click="toDelete(scope.row)"
+            >删除</el-button
+          >
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- ModifyMarkerQuestion -->
+    <modify-marker-question
+      ref="ModifyMarkerQuestion"
+      :instance="curMarker"
+      :disabled-question-ids="disabledQuestionIds"
+      :paper-structure="datas.structure"
+      @modified="modified"
+    ></modify-marker-question>
+  </div>
+</template>
+
+<script>
+import ModifyMarkerQuestion from "./ModifyMarkerQuestion";
+
+export default {
+  name: "mark-paper-marker",
+  components: { ModifyMarkerQuestion },
+  props: {
+    datas: {
+      type: Object,
+      default() {
+        return {
+          task: {},
+          structure: [],
+          markers: []
+        };
+      }
+    }
+  },
+  data() {
+    return {
+      questionCount: 0,
+      markerQuestionCount: 0,
+      markers: [],
+      disabledQuestionIds: [],
+      curMarker: {},
+      MARK_TYPE: {
+        1: "单评",
+        2: "双评"
+      }
+    };
+  },
+  filters: {
+    questionsFilter(val) {
+      return val.map(item => `${item.mainNumber}-${item.subNumber}`).join(",");
+    }
+  },
+  mounted() {
+    this.initData();
+  },
+  methods: {
+    initData() {
+      this.markers = this.datas.markers.map(item => {
+        return { ...item };
+      });
+      this.questionCount = this.datas.structure.length;
+      this.updateDisableQuestionIds();
+
+      this.$emit("on-ready");
+    },
+    updateDisableQuestionIds(filterId) {
+      let markers = this.markers;
+      if (filterId) markers = this.markers.filter(item => item.id !== filterId);
+      let disabledQuestionIds = [];
+      markers.forEach(item => {
+        disabledQuestionIds = [
+          ...disabledQuestionIds,
+          ...item.questions.map(item => item.id)
+        ];
+      });
+      this.disabledQuestionIds = disabledQuestionIds;
+      if (!filterId) this.markerQuestionCount = disabledQuestionIds.length;
+    },
+    toAdd() {
+      this.updateDisableQuestionIds();
+      if (this.markerQuestionCount === this.questionCount) {
+        this.$message.error("当前已经没有题目可供设置!");
+        return;
+      }
+
+      this.curMarker = {
+        id: this.$randomCode(),
+        markers: [],
+        markType: 1,
+        arbitration: 1,
+        questions: []
+      };
+      this.$refs.ModifyMarkerQuestion.open();
+    },
+    toEdit(row) {
+      this.curMarker = row;
+      this.updateDisableQuestionIds(row.id);
+      this.$refs.ModifyMarkerQuestion.open();
+    },
+    toDelete(row) {
+      const pos = this.markers.findIndex(item => item.id === row.id);
+      this.markers.splice(pos, 1);
+    },
+    modified(row) {
+      const pos = this.markers.findIndex(item => item.id === row.id);
+      this.markers.splice(pos, 1, row);
+      this.updateDisableQuestionIds();
+    },
+    checkData() {
+      let errorMessages = [];
+
+      if (this.questionCount > this.markerQuestionCount) {
+        errorMessages.push("当前还有题目未设置评卷员");
+      }
+
+      this.markers.forEach((item, index) => {
+        if (item.markType === 2 && !item.arbitration) {
+          errorMessages.push(`序号${index + 1}设置中,仲裁阀值不能为空`);
+        }
+      });
+
+      if (errorMessages.length) {
+        this.$message.error(errorMessages.join("。"));
+        this.$emit("on-ready");
+        return;
+      }
+
+      this.updateData();
+      this.$emit("next-step");
+    },
+    getData() {
+      return this.markers.map(item => {
+        return { ...item };
+      });
+    },
+    updateData() {
+      this.$emit("data-change", { markers: this.getData() });
+    }
+  }
+};
+</script>

+ 285 - 0
src/modules/stmms/components/MarkPaperStructure.vue

@@ -0,0 +1,285 @@
+<template>
+  <div class="mark-paper-structure">
+    <p class="structure-desc">
+      <span>课程名称:</span>
+      <span class="mr-4">{{ datas.task.courseName }}</span>
+      <span>课程代码:</span>
+      <span>{{ datas.task.courseCode }}</span>
+    </p>
+    <el-table
+      ref="TableList"
+      :data="tableData"
+      border
+      :row-class-name="getRowClassName"
+    >
+      <el-table-column width="50" align="center">
+        <template slot-scope="scope" v-if="scope.row.isMainFirstSub">
+          <div
+            :class="[
+              'expand-btn',
+              { 'expand-btn-unexpand': !scope.row.expandSub }
+            ]"
+            @click="switchExpandSub(scope.row)"
+          >
+            <i
+              :class="scope.row.expandSub ? 'el-icon-minus' : 'el-icon-plus'"
+            ></i>
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column prop="mainTitle" label="大题名称">
+        <span slot-scope="scope" v-if="scope.row.isMainFirstSub">
+          <el-input
+            v-model.trim="scope.row.mainTitle"
+            size="small"
+            :maxlength="50"
+            @change="mainTitleChange(scope.row)"
+          ></el-input>
+        </span>
+      </el-table-column>
+      <el-table-column prop="mainNumber" label="大题号" width="80">
+        <template slot-scope="scope" v-if="scope.row.isMainFirstSub">
+          <span>{{ scope.row.mainNumber }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        prop="subNumber"
+        label="小题号"
+        width="80"
+      ></el-table-column>
+      <el-table-column prop="totalScore" label="小题满分" width="105">
+        <template slot-scope="scope">
+          <el-input-number
+            v-model="scope.row.totalScore"
+            class="width-80"
+            size="small"
+            :min="0.5"
+            :max="200"
+            :step="0.5"
+            step-strictly
+            :controls="false"
+            @change="questionScoreChange"
+          ></el-input-number>
+        </template>
+      </el-table-column>
+      <el-table-column class-name="action-column" label="操作" width="200px">
+        <template slot-scope="scope">
+          <el-button
+            class="btn-primary"
+            type="text"
+            @click="toAddMain(scope.row)"
+            >新增大题</el-button
+          >
+          <el-button
+            class="btn-primary"
+            type="text"
+            @click="toAddSub(scope.row)"
+            >新增小题</el-button
+          >
+          <el-button
+            :disabled="tableData.length <= 1"
+            class="btn-danger"
+            type="text"
+            @click="toDeleteSub(scope.row)"
+            >删除</el-button
+          >
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script>
+import { calcSum } from "../../../plugins/utils";
+
+export default {
+  name: "mark-paper-structure",
+  props: {
+    datas: {
+      type: Object,
+      default() {
+        return {
+          task: {},
+          structure: [],
+          markers: []
+        };
+      }
+    }
+  },
+  data() {
+    return {
+      tableData: []
+    };
+  },
+  mounted() {
+    this.initData();
+  },
+  methods: {
+    initData() {
+      this.tableData = this.datas.structure.map(item => {
+        return { ...item };
+      });
+      if (!this.tableData.length) this.createMain();
+
+      this.$emit("on-ready");
+    },
+    createMain() {
+      this.tableData.push({
+        id: this.$randomCode(),
+        mainId: this.$randomCode(),
+        mainTitle: "",
+        mainNumber: 1,
+        subNumber: 1,
+        totalScore: 1,
+        isMainFirstSub: true,
+        expandSub: true
+      });
+    },
+    getRowClassName({ row }) {
+      let classNames = [];
+      if (row.isMainFirstSub) {
+        classNames.push("row-main-first-sub");
+      }
+      if (!row.isMainFirstSub && !row.expandSub) {
+        classNames.push("row-unexpand-sub");
+      }
+      return classNames.join(" ");
+    },
+    toAddMain(row) {
+      const mainStartPos = this.tableData.findIndex(
+        item => item.mainId === row.mainId
+      );
+      let nextMainStartPos = this.tableData
+        .slice(mainStartPos)
+        .findIndex(item => item.mainId !== row.mainId);
+      if (nextMainStartPos === -1) nextMainStartPos = this.tableData.length;
+
+      this.tableData.splice(nextMainStartPos, 0, {
+        id: this.$randomCode(),
+        mainId: this.$randomCode(),
+        mainTitle: "",
+        mainNumber: row.mainNumber + 1,
+        subNumber: 1,
+        totalScore: 1,
+        isMainFirstSub: true,
+        expandSub: true
+      });
+      this.updateMainData();
+    },
+    updateMainData() {
+      let curMainNumber = 0,
+        curMainId = null;
+      this.tableData.forEach(item => {
+        if (item.mainId !== curMainId) {
+          curMainId = item.mainId;
+          curMainNumber++;
+        }
+        item.mainNumber = curMainNumber;
+      });
+    },
+    toAddSub(row) {
+      const subPos = this.tableData.findIndex(item => item.id === row.id);
+      this.tableData.splice(subPos + 1, 0, {
+        id: this.$randomCode(),
+        mainId: row.mainId,
+        mainTitle: row.mainTitle,
+        mainNumber: row.mainNumber,
+        subNumber: row.subNumber + 1,
+        totalScore: 1,
+        isMainFirstSub: false,
+        expandSub: row.expandSub
+      });
+      this.updateSubData(row.mainId);
+    },
+    updateSubData(mainId) {
+      this.tableData
+        .filter(item => item.mainId === mainId)
+        .forEach((item, index) => {
+          item.subNumber = index + 1;
+        });
+    },
+    toDeleteSub(row) {
+      const subPos = this.tableData.findIndex(item => item.id === row.id);
+      this.tableData.splice(subPos, 1);
+
+      this.tableData
+        .filter(item => item.mainId === row.mainId)
+        .forEach((item, index) => {
+          item.isMainFirstSub = !index;
+        });
+      this.updateSubData(row.mainId);
+      this.updateMainData();
+    },
+    switchExpandSub(row) {
+      row.expandSub = !row.expandSub;
+      this.tableData
+        .filter(item => item.mainId === row.mainId && !item.isMainFirstSub)
+        .forEach(item => (item.expandSub = row.expandSub));
+    },
+    mainTitleChange(row) {
+      this.tableData
+        .filter(item => item.mainId === row.mainId && !item.isMainFirstSub)
+        .forEach(item => (item.mainTitle = row.mainTitle));
+    },
+    questionScoreChange() {
+      this.paperTotalScore = calcSum(
+        this.tableData.map(item => item.totalScore || 0)
+      );
+    },
+    checkData() {
+      let errorMessages = [];
+
+      this.tableData.forEach(item => {
+        let errorMsg = ``;
+        if (item.isMainFirstSub) {
+          let errorFields = [];
+          if (!item.mainTitle) {
+            errorFields.push("大题名称");
+          }
+          if (!item.mainNumber) {
+            errorFields.push("大题号");
+          }
+          if (errorFields.length) {
+            errorMsg += `${errorFields.join("、")}不能为空,`;
+          }
+        }
+
+        let errorFields = [];
+        if (!item.subNumber) {
+          errorFields.push("小题号");
+        }
+        if (!item.totalScore) {
+          errorFields.push("小题满分");
+        }
+        if (errorFields.length) {
+          errorMsg += `第${item.subNumber}小题,${errorFields.join(
+            "、"
+          )}不能为空,`;
+        }
+
+        if (errorMsg) {
+          errorMsg = `第${item.mainNumber}大题,${errorMsg}`;
+          errorMessages.push(errorMsg);
+        }
+      });
+
+      if (errorMessages.length) {
+        this.$message.error(errorMessages.join("。"));
+        this.$emit("on-ready");
+        return;
+      }
+
+      this.updateData();
+      this.$emit("next-step");
+    },
+    getData() {
+      return this.tableData.map(item => {
+        return { ...item };
+      });
+    },
+    updateData() {
+      this.$emit("data-change", { structure: this.getData() });
+    }
+  }
+};
+</script>

+ 186 - 0
src/modules/stmms/components/ModifyMarkParams.vue

@@ -0,0 +1,186 @@
+<template>
+  <el-dialog
+    class="modify-mark-params"
+    :visible.sync="modalIsShow"
+    title="评卷参数设置"
+    top="0"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    fullscreen
+    :before-close="beforeClose"
+    @open="visibleChange"
+  >
+    <el-steps
+      class="mark-step"
+      :active="current"
+      align-center
+      finish-status="success"
+    >
+      <el-step
+        v-for="step in steps"
+        :key="step.name"
+        :title="step.title"
+        :description="step.desc"
+      >
+      </el-step>
+    </el-steps>
+
+    <div class="mark-body" v-if="dataReady">
+      <component
+        :is="currentComponent"
+        :ref="currentComponent"
+        :datas="infos"
+        @next-step="toNext"
+        @on-ready="compReady"
+        @data-change="dataChange"
+      ></component>
+    </div>
+
+    <div class="text-center">
+      <el-button
+        v-if="!isFirstStep"
+        type="primary"
+        :disabled="loading"
+        @click="prevStep"
+        >上一步</el-button
+      >
+      <el-button
+        v-if="isLastStep"
+        type="success"
+        :disabled="loading"
+        @click="nextStep"
+        >提交</el-button
+      >
+      <el-button v-else type="primary" @click="nextStep" :disabled="loading"
+        >下一步</el-button
+      >
+      <el-button @click="cancel">取消</el-button>
+    </div>
+
+    <div slot="footer"></div>
+  </el-dialog>
+</template>
+
+<script>
+import MarkPaperMarker from "./MarkPaperMarker.vue";
+import MarkPaperStructure from "./MarkPaperStructure.vue";
+
+const STEPS_LIST = [
+  {
+    name: "structure",
+    title: "试卷结构",
+    desc: "请按试卷填写相应试卷结构信息"
+  },
+  {
+    name: "marker",
+    title: "评卷员",
+    desc: "请选择评卷老师及设置对应评卷题目"
+  }
+];
+
+export default {
+  name: "modify-mark-params",
+  components: { MarkPaperMarker, MarkPaperStructure },
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      infos: {
+        task: {},
+        structure: [],
+        markers: []
+      },
+      // step
+      steps: STEPS_LIST,
+      current: 0,
+      loading: false,
+      dataReady: false
+    };
+  },
+  computed: {
+    currentComponent() {
+      return `mark-paper-${this.steps[this.current].name}`;
+    },
+    isFirstStep() {
+      return this.current === 0;
+    },
+    isLastStep() {
+      return this.current === this.lastStep;
+    },
+    lastStep() {
+      return this.steps.length - 1;
+    }
+  },
+  methods: {
+    visibleChange() {
+      this.dataReady = false;
+      this.current = 0;
+      this.loading = false;
+
+      this.infos.task = { ...this.instance };
+
+      this.dataReady = true;
+    },
+    async cancel() {
+      const res = await this.$confirm("确定要退出阅卷参数编辑吗?", "提示", {
+        type: "warning"
+      }).catch(() => {});
+      if (res !== "confirm") return;
+
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async beforeClose(done) {
+      const res = await this.$confirm("确定要退出阅卷参数编辑吗?", "提示", {
+        type: "warning"
+      }).catch(() => {});
+      if (res !== "confirm") return;
+      done();
+    },
+    prevStep() {
+      if (this.isFirstStep) return;
+      this.$refs[this.currentComponent].updateData();
+      this.current -= 1;
+      if (this.steps[this.current].name === "structure") {
+        this.$notify({
+          title: "警告",
+          message: "修改试卷结构会清空已经设置的评卷员,需要重新设置!",
+          type: "warning",
+          duration: 5000
+        });
+      }
+    },
+    nextStep() {
+      this.loading = true;
+      this.$refs[this.currentComponent].checkData();
+    },
+    toNext() {
+      if (this.isLastStep) {
+        this.submit();
+      } else {
+        this.current += 1;
+      }
+    },
+    dataChange(data) {
+      // console.log(data);
+      Object.entries(data).forEach(([key, val]) => {
+        this.infos[key] = Object.assign(this.infos[key], val);
+      });
+    },
+    compReady(type = false) {
+      this.loading = type;
+    },
+    submit() {}
+  }
+};
+</script>

+ 434 - 0
src/modules/stmms/components/ModifyMarkerQuestion.vue

@@ -0,0 +1,434 @@
+<template>
+  <el-dialog
+    class="modify-marker-question"
+    :visible.sync="modalIsShow"
+    append-to-body
+    top="20px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    :show-close="false"
+    @opened="visibleChange"
+  >
+    <div slot="title"></div>
+
+    <el-row type="flex" :gutter="10">
+      <el-col :span="12">
+        <div class="marker-box">
+          <div class="user-title">评卷员</div>
+          <div class="user-search">
+            <el-input
+              v-model="filterLabel"
+              placeholder="请输入评卷员名称"
+              clearable
+              size="mini"
+              prefix-icon="el-icon-search"
+              @input="labelChange"
+            ></el-input>
+          </div>
+          <div class="user-tree">
+            <el-tree
+              ref="UserTree"
+              :data="userTree"
+              node-key="id"
+              :default-checked-keys="selectedUserIds"
+              :props="defaultProps"
+              default-expand-all
+            >
+              <span class="custom-tree-node" slot-scope="{ node, data }">
+                <el-checkbox
+                  v-if="data.isUser"
+                  v-model="node.checked"
+                  @change="userChange"
+                >
+                  {{ node.label }}
+                </el-checkbox>
+                <span v-else>{{ node.label }}</span>
+                <div title="全选" @click.stop>
+                  <el-checkbox
+                    v-if="!data.isUser && data.children.length"
+                    v-model="data.selected"
+                    @change="checked => selectNodeAll(checked, data)"
+                  ></el-checkbox>
+                </div>
+              </span>
+            </el-tree>
+          </div>
+        </div>
+      </el-col>
+      <el-col :span="12">
+        <div class="marker-box marker-box-uq">
+          <el-form
+            ref="modalFormRef"
+            :rules="rules"
+            :model="{}"
+            label-width="100px"
+            label-position="top"
+          >
+            <el-form-item prop="users" label="已选评卷员:">
+              <el-tag
+                v-for="user in selectedUsers"
+                :key="user.id"
+                closable
+                :disable-transitions="false"
+                @close="toDeleteUser(user)"
+              >
+                {{ user.label }}
+              </el-tag>
+            </el-form-item>
+            <el-form-item prop="questions" label="选择评卷题目:">
+              <div class="marker-paper-struct">
+                <div
+                  v-for="mainItem in paperStructs"
+                  :key="mainItem.mainId"
+                  class="struct-item"
+                >
+                  <div class="struct-header box-justify">
+                    <h4>{{ mainItem.mainNumber }}、{{ mainItem.mainTitle }}</h4>
+                    <el-checkbox
+                      v-model="mainItem.selected"
+                      title="全选"
+                      @change="checked => selectQuestionAll(checked, mainItem)"
+                    ></el-checkbox>
+                  </div>
+                  <div class="struct-questions">
+                    <el-checkbox
+                      v-for="question in mainItem.children"
+                      :key="question.id"
+                      v-model="question.selected"
+                      :disabled="question.disabled"
+                      @change="questionChange"
+                    >
+                      {{ question.subNumber }}
+                    </el-checkbox>
+                  </div>
+                </div>
+              </div>
+            </el-form-item>
+          </el-form>
+        </div>
+      </el-col>
+    </el-row>
+
+    <div class="marker-footer">
+      <el-button type="primary" @click="confirm">确认</el-button>
+      <el-button @click="cancel">取消</el-button>
+    </div>
+
+    <div slot="footer"></div>
+  </el-dialog>
+</template>
+
+<script>
+import { organizationList } from "../../base/api";
+
+export default {
+  name: "modify-marker-question",
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      }
+    },
+    disabledQuestionIds: {
+      type: Array,
+      default() {
+        return [];
+      }
+    },
+    paperStructure: {
+      type: Array,
+      default() {
+        return [];
+      }
+    }
+  },
+  data() {
+    const usersValidator = (rule, value, callback) => {
+      if (!this.selectedUserIds.length) {
+        callback(new Error("请选择评卷员"));
+      } else {
+        callback();
+      }
+    };
+    const questionsValidator = (rule, value, callback) => {
+      if (!this.selectedQuestionIds.length) {
+        callback(new Error("请选择试题"));
+      } else {
+        callback();
+      }
+    };
+
+    return {
+      modalIsShow: false,
+      filterLabel: "",
+      orgUsers: [],
+      userTree: [],
+      userList: [],
+      selectedUsers: [],
+      selectedUserIds: [],
+      selectedQuestions: [],
+      selectedQuestionIds: [],
+      paperStructs: [],
+      defaultProps: {
+        children: "children",
+        label: "label"
+      },
+      rules: {
+        users: [
+          {
+            required: true,
+            validator: usersValidator,
+            trigger: "change"
+          }
+        ],
+        questions: [
+          {
+            required: true,
+            validator: questionsValidator,
+            trigger: "change"
+          }
+        ]
+      }
+    };
+  },
+  mounted() {
+    this.getOrgData();
+  },
+  methods: {
+    visibleChange() {
+      this.parseStructs();
+      this.filterLabel = "";
+      this.selectedUsers = this.instance.markers;
+      this.selectedQuestions = this.instance.questions;
+      this.selectedUserIds = this.selectedUsers.map(item => item.id);
+      this.selectedQuestionIds = this.selectedQuestions.map(item => item.id);
+      this.labelChange();
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    // user
+    async getOrgData() {
+      const data = await organizationList();
+      this.parseUserData(data);
+      this.getUserList();
+    },
+    parseUserData(data) {
+      const parseUser = list => {
+        return list.map(item => {
+          // org
+          let nitem = {
+            id: item.id,
+            label: item.name,
+            isUser: false,
+            selected: false,
+            children: []
+          };
+
+          if (item["children"] && item["children"].length) {
+            nitem.children = [...nitem.children, ...parseUser(item.children)];
+          }
+          // user
+          if (item["sysUserList"] && item["sysUserList"].length) {
+            let sysUserList = item.sysUserList;
+            const users = sysUserList.map(user => {
+              const nuser = {
+                id: user.id,
+                userId: user.id,
+                label: user.realName,
+                name: user.realName,
+                orgName: item.name,
+                selected: false,
+                isUser: true
+              };
+              return nuser;
+            });
+            nitem.children = [...nitem.children, ...users];
+          }
+          return nitem;
+        });
+      };
+      this.orgUsers = parseUser(data);
+      this.userTree = this.orgUsers;
+      this.getUserList();
+    },
+    getUserList() {
+      let userList = [];
+      const fetchUser = users => {
+        users.forEach(item => {
+          if (item["children"] && item["children"].length) {
+            fetchUser(item.children);
+          } else {
+            if (item.isUser) {
+              let nitem = { ...item };
+              nitem.label = `${nitem.name}(${nitem.orgName})`;
+              userList.push(nitem);
+            }
+          }
+        });
+      };
+      fetchUser(this.orgUsers);
+
+      this.userList = userList;
+    },
+    labelChange() {
+      if (!this.filterLabel) {
+        this.userTree = this.orgUsers;
+      } else {
+        const escapeRegexpString = (value = "") =>
+          String(value).replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
+        const reg = new RegExp(escapeRegexpString(this.filterLabel), "i");
+
+        this.userTree = this.userList.filter(item => reg.test(item.name));
+      }
+      this.$refs.UserTree.setCheckedKeys(this.selectedUserIds);
+    },
+    selectNodeAll(checked, data) {
+      let userIds = [];
+      const getUserIds = list => {
+        list.forEach(item => {
+          item.selected = checked;
+          if (item.children && item.children.length) {
+            getUserIds(item.children);
+          } else {
+            if (item.isUser) userIds.push(item.userId);
+          }
+        });
+      };
+      getUserIds(data.children);
+
+      userIds.forEach(userId => {
+        const userPos = this.selectedUserIds.indexOf(userId);
+        const includeUser = userPos !== -1;
+        if (checked) {
+          if (!includeUser) this.selectedUserIds.push(userId);
+        } else {
+          if (includeUser) {
+            this.selectedUserIds.splice(userPos, 1);
+          }
+        }
+      });
+
+      this.$refs.UserTree.setCheckedKeys(this.selectedUserIds);
+      this.updateSelectedUsersFromUserIds();
+
+      this.$refs.modalFormRef.validateField("users");
+    },
+    updateSelectedUsersFromUserIds() {
+      this.selectedUsers = this.userList.filter(user =>
+        this.selectedUserIds.includes(user.id)
+      );
+    },
+    userChange() {
+      if (this.filterLabel) {
+        let prevSelectUserIds = this.selectedUsers.map(item => item.id);
+        const prevUserListSelectUserIds = this.userTree
+          .filter(user => prevSelectUserIds.includes(user.id))
+          .map(user => user.id);
+        const selectedUsers = this.$refs.UserTree.getCheckedNodes(true);
+        const sIds = selectedUsers.map(user => user.id);
+        const prevDeletedUserIds = prevUserListSelectUserIds.filter(
+          uid => !sIds.includes(uid)
+        );
+        this.selectedUsers = this.selectedUsers.filter(
+          user => !prevDeletedUserIds.includes(user.id)
+        );
+        prevSelectUserIds = this.selectedUsers.map(item => item.id);
+
+        selectedUsers.forEach(user => {
+          if (prevSelectUserIds.includes(user.id)) return;
+          const nuser = {
+            id: user.id,
+            name: user.name,
+            label: `${user.name}(${user.orgName})`
+          };
+          this.selectedUsers.push(nuser);
+        });
+        this.selectedUserIds = this.selectedUsers.map(item => item.id);
+      } else {
+        const selectedUsers = this.$refs.UserTree.getCheckedNodes(true);
+        this.selectedUsers = selectedUsers.map(user => {
+          const nuser = {
+            id: user.id,
+            name: user.name,
+            label: `${user.name}(${user.orgName})`
+          };
+          return nuser;
+        });
+        this.selectedUserIds = this.selectedUsers.map(item => item.id);
+      }
+    },
+    toDeleteUser(user) {
+      const pos = this.selectedUsers.findIndex(item => item.id === user.id);
+      this.selectedUsers.splice(pos, 1);
+      this.selectedUserIds = this.selectedUsers.map(item => item.id);
+      this.$refs.UserTree.setCheckedKeys(this.selectedUserIds);
+      this.$refs.modalFormRef.validateField("users");
+    },
+    // question
+    parseStructs() {
+      this.selectedQuestionIds = this.instance.questions.map(q => q.id);
+      let paperStructs = [];
+      let struct = null;
+      this.paperStructure.forEach(item => {
+        if (item.isMainFirstSub) {
+          if (struct) paperStructs.push(struct);
+          struct = {
+            mainId: item.mainId,
+            mainTitle: item.mainTitle,
+            mainNumber: item.mainNumber,
+            selected: false,
+            children: []
+          };
+        }
+        struct.children.push({
+          ...item,
+          disabled: this.disabledQuestionIds.includes(item.id),
+          selected: this.selectedQuestionIds.includes(item.id)
+        });
+      });
+      if (struct) paperStructs.push(struct);
+      this.paperStructs = paperStructs;
+    },
+    selectQuestionAll(checked, mainItem) {
+      mainItem.children.forEach(q => {
+        if (!q.disabled) q.selected = checked;
+      });
+      this.questionChange();
+    },
+    updateSelectedQuestions() {
+      let selectedQuestions = [];
+      let selectedQuestionIds = [];
+      this.paperStructs.forEach(s => {
+        s.children.forEach(q => {
+          if (q.selected && !q.disabled) {
+            selectedQuestions.push(q);
+            selectedQuestionIds.push(q.id);
+          }
+        });
+      });
+      this.selectedQuestions = selectedQuestions;
+      this.selectedQuestionIds = selectedQuestionIds;
+    },
+    questionChange() {
+      this.updateSelectedQuestions();
+      this.$refs.modalFormRef.validateField("questions");
+    },
+    // confirm
+    async confirm() {
+      const valid = await this.$refs.modalFormRef.validate().catch(() => {});
+      if (!valid) return;
+
+      let datas = { ...this.instance };
+      datas.markers = this.selectedUsers;
+      datas.questions = this.selectedQuestions;
+      this.$emit("modified", datas);
+      this.cancel();
+    }
+  }
+};
+</script>

+ 28 - 2
src/modules/stmms/views/UploadStructure.vue

@@ -30,6 +30,12 @@
         </el-table-column>
         <el-table-column class-name="action-column" label="操作" width="180px">
           <template slot-scope="scope">
+            <el-button
+              class="btn-primary"
+              type="text"
+              @click="toSetParams(scope.row)"
+              >评卷参数设置</el-button
+            >
             <el-button
               v-if="checkPrivilege('link', 'Upload')"
               class="btn-primary"
@@ -104,6 +110,7 @@
       ref="PreviewPaperStructureDialog"
       :instance="curTask"
     />
+    <ModifyMarkParams ref="ModifyMarkParams" :instance="curTask" />
   </div>
 </template>
 
@@ -111,10 +118,15 @@
 import { examStructureListPage } from "../api";
 import UploadPaperAnswerDialog from "../components/UploadPaperAnswerDialog";
 import PreviewPaperStructureDialog from "../components/PreviewPaperStructureDialog";
+import ModifyMarkParams from "../components/ModifyMarkParams";
 
 export default {
   name: "upload-structure",
-  components: { UploadPaperAnswerDialog, PreviewPaperStructureDialog },
+  components: {
+    UploadPaperAnswerDialog,
+    PreviewPaperStructureDialog,
+    ModifyMarkParams
+  },
   data() {
     return {
       filter: {},
@@ -126,7 +138,17 @@ export default {
     };
   },
   mounted() {
-    this.toPage(1);
+    // this.toPage(1);
+    this.dataList.push({
+      thirdRelateId: 1,
+      thirdRelateName: "考试1",
+      courseName: "语文",
+      courseCode: "yw001",
+      paperNumber: 112345667,
+      paperType: "AB",
+      paperTypes: ["A", "B"],
+      status: "FINISH"
+    });
   },
   methods: {
     async getList() {
@@ -148,6 +170,10 @@ export default {
       this.current = page;
       this.getList();
     },
+    toSetParams(row) {
+      this.curTask = row;
+      this.$refs.ModifyMarkParams.open();
+    },
     toUpload(row) {
       this.curTask = row;
       this.$refs.UploadPaperAnswerDialog.open();

+ 4 - 5
src/plugins/download.js

@@ -1,17 +1,15 @@
-const parseDownloadFilename = dispositionInfo => {
-  if (!dispositionInfo) return;
-
+function parseDownloadFilename(dispositionInfo) {
   const strs = dispositionInfo.split(";");
   let filename = "";
   strs
     .map(item => item.split("="))
     .find(item => {
-      if (item[0].indexOf("fileName") !== -1) {
+      if (item[0].indexOf("filename") !== -1) {
         filename = decodeURI(item[1]);
       }
     });
   return filename;
-};
+}
 
 /**
  * 通过api下载文件
@@ -24,6 +22,7 @@ export function downloadByApi(fetchFunc, fileName) {
     .then(res => {
       const filename =
         fileName || parseDownloadFilename(res.headers["content-disposition"]);
+      console.log(filename);
       downloadByBlob(new Blob([res.data]), filename);
       return true;
     })

+ 5 - 0
src/plugins/utils.js

@@ -329,6 +329,11 @@ export function calcSum(dataList) {
   }, 0);
 }
 
+/** 获取数组最大数 */
+export function maxNum(dataList) {
+  return Math.max.apply(null, dataList);
+}
+
 export function isEmptyObject(obj) {
   return !Object.keys(obj).length;
 }

+ 1 - 2
src/views/Home.vue

@@ -123,8 +123,7 @@
 
 <script>
 import { mapState, mapActions } from "vuex";
-// import localMenus from "@/constants/privileges";
-import localMenus from "@/constants/menus";
+import localMenus from "@/constants/menus-data";
 import { sysMenu, logout } from "../modules/login/api";
 import ResetPwd from "../modules/base/components/ResetPwd";
 import { SYS_ADMIN_NAME } from "@/constants/enumerate";

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff