Forráskód Böngészése

评卷参数新增绑定科组长

zhangjie 3 éve
szülő
commit
f17b689ecb

+ 341 - 340
card/mixins/exchange.js

@@ -1,340 +1,341 @@
-import { CARD_VERSION } from "../enumerate";
-import { deepCopy } from "../plugins/utils";
-
-const initIndex = {
-  question: 1,
-  absent: 1,
-  paperType: 1,
-  examNumber: 1,
-  selective: 1,
-  pageNumber: 1
-};
-
-export default {
-  data() {
-    return {
-      fillAreaIndex: {
-        ...initIndex
-      },
-      VALID_ELEMENTS_FOR_EXTERNAL: [
-        "LOCATOR",
-        "BARCODE",
-        "CARD_HEAD",
-        "FILL_QUESTION",
-        "FILL_LINE",
-        "EXPLAIN",
-        "COMPOSITION"
-      ]
-    };
-  },
-  methods: {
-    getFillAreaIndex(type) {
-      return this.fillAreaIndex[type]++;
-    },
-    getElementHumpName(cont) {
-      return cont
-        .split("_")
-        .map(item => item[0] + item.substr(1).toLowerCase())
-        .join("");
-    },
-    getPreviewElementById(id) {
-      return document.getElementById(`preview-${id}`);
-    },
-    parsePageExchange(pages) {
-      const npages = deepCopy(pages);
-      const pageNumberInfo = this.getPageNumberInfo();
-      npages.forEach((page, pindex) => {
-        let exchange = {
-          locator: this.getLocatorInfo(page.locators),
-          barcode: [],
-          info_area: [],
-          fill_area: [],
-          answer_area: []
-        };
-        const elements = [
-          page.globals,
-          ...page.columns.map(column => column.elements)
-        ];
-
-        elements.forEach(elemGroup => {
-          elemGroup.forEach(element => {
-            if (this.VALID_ELEMENTS_FOR_EXTERNAL.includes(element.type)) {
-              const funcName = this.getElementHumpName(element.type);
-              console.log(funcName);
-              const info = this[`get${funcName}Info`](element);
-              Object.entries(info).forEach(([key, vals]) => {
-                exchange[key] = exchange[key].concat(vals);
-              });
-            }
-          });
-        });
-
-        if (!(pindex % 2)) {
-          let pnoInfo = deepCopy(pageNumberInfo);
-          pnoInfo[0].index = this.getFillAreaIndex("pageNumber");
-          exchange.fill_area = exchange.fill_area.concat(pnoInfo);
-        }
-
-        page.exchange = exchange;
-      });
-
-      this.fillAreaIndex = { ...initIndex };
-
-      return npages;
-    },
-    getPageNumberInfo() {
-      const dom = document.querySelector(".page-box-0");
-      let options = [];
-      dom
-        .querySelector(".page-number-rect-list")
-        .childNodes.forEach((item, index) => {
-          options[index] = this.getOffsetInfo(item);
-        });
-      return [
-        {
-          field: "pageNumber",
-          index: 1,
-          single: true,
-          horizontal: true,
-          items: [
-            {
-              main_number: null,
-              sub_number: null,
-              options
-            }
-          ]
-        }
-      ];
-    },
-    getLocatorInfo(locators) {
-      return locators.map(locatorGroup => {
-        const locatorInfos = locatorGroup.map(locator => {
-          return this.getOffsetInfo(document.getElementById(locator.id));
-        });
-        return {
-          top: locatorInfos[0],
-          bottom: locatorInfos[1]
-        };
-      });
-    },
-    getCardHeadInfo(element) {
-      const dom = this.getPreviewElementById(element.id);
-      const headArea = this.getOffsetInfo(dom);
-      let fill_area = [];
-      let barcode = [];
-      // 学生考号
-      if (element.examNumberStyle === "FILL") {
-        // fill_area
-        let listInfos = [];
-        dom
-          .querySelectorAll(".stdno-fill-list")
-          .forEach((questionItem, questionIndex) => {
-            let options = [];
-            questionItem.childNodes.forEach((optionItem, optionIndex) => {
-              options[optionIndex] = this.getOffsetInfo(optionItem);
-            });
-            listInfos[questionIndex] = {
-              main_number: null,
-              sub_number: null,
-              options
-            };
-          });
-
-        fill_area.push({
-          field: "examNumber",
-          index: this.getFillAreaIndex("examNumber"),
-          single: true,
-          horizontal: false,
-          items: listInfos
-        });
-      } else {
-        // barcode
-        const stdnoDom =
-          element.columnNumber <= 2
-            ? dom.querySelector(".head-stdno").parentNode
-            : dom.querySelector(".head-stdno");
-        barcode.push({
-          field: "examNumber",
-          area: this.getOffsetInfo(stdnoDom)
-        });
-      }
-      // 缺考涂填
-      if (element.examAbsent && !element.isSimple) {
-        fill_area.push({
-          field: "absent",
-          index: this.getFillAreaIndex("absent"),
-          single: true,
-          horizontal: true,
-          items: [
-            {
-              main_number: null,
-              sub_number: null,
-              options: [
-                this.getOffsetInfo(document.getElementById("dynamic-miss-area"))
-              ]
-            }
-          ]
-        });
-      }
-      // A/B卷类型
-      if (element.aOrB && !element.isSimple) {
-        if (element.paperType === "PRINT") {
-          // barcode
-          barcode.push({
-            field: "paperType",
-            area: this.getOffsetInfo(
-              document.getElementById("dynamic-aorb-barcode")
-            )
-          });
-        } else {
-          // fill_area
-          let options = [];
-          document
-            .getElementById("head-dynamic-aorb")
-            .querySelectorAll(".head-dynamic-rect")
-            .forEach((optionItem, optionIndex) => {
-              options[optionIndex] = this.getOffsetInfo(optionItem);
-            });
-          fill_area.push({
-            field: "paperType",
-            index: this.getFillAreaIndex("paperType"),
-            single: true,
-            horizontal: true,
-            items: [
-              {
-                main_number: null,
-                sub_number: null,
-                options
-              }
-            ]
-          });
-        }
-      }
-
-      return {
-        info_area: [headArea],
-        fill_area,
-        barcode
-      };
-    },
-    getFillQuestionInfo(element) {
-      const dom = this.getPreviewElementById(element.id);
-      const single = !element.isMultiply;
-      const horizontal = element.optionDirection === "horizontal";
-
-      let fillAreas = [];
-      dom.querySelectorAll(".group-item").forEach(groupItem => {
-        let listInfos = [];
-
-        groupItem
-          .querySelectorAll(".question-item")
-          .forEach((questionItem, questionIndex) => {
-            let options = [];
-            questionItem.childNodes.forEach((optionItem, optionIndex) => {
-              if (optionIndex)
-                options[optionIndex - 1] = this.getOffsetInfo(optionItem);
-            });
-            listInfos[questionIndex] = {
-              main_number: element.topicNo,
-              sub_number: questionItem.firstChild.textContent * 1,
-              options
-            };
-          });
-        fillAreas.push({
-          field: "question",
-          index: this.getFillAreaIndex("question"),
-          single,
-          horizontal,
-          items: listInfos
-        });
-      });
-
-      return {
-        fill_area: fillAreas
-      };
-    },
-    getFillLineInfo(element) {
-      const dom = this.getPreviewElementById(element.id);
-      let sub_numbers = [];
-      for (
-        let i = element.startNumber,
-          len = element.startNumber + element.questionsCount;
-        i < len;
-        i++
-      ) {
-        sub_numbers.push(i);
-      }
-
-      return {
-        answer_area: [
-          {
-            main_number: element.topicNo,
-            sub_numbers,
-            area: this.getOffsetInfo(dom)
-          }
-        ]
-      };
-    },
-    getExplainInfo(element) {
-      const dom = this.getPreviewElementById(element.id);
-
-      return {
-        answer_area: [
-          {
-            main_number: element.topicNo,
-            sub_numbers: [element.serialNumber],
-            area: this.getOffsetInfo(dom)
-          }
-        ]
-      };
-    },
-    getCompositionInfo(element) {
-      const dom = this.getPreviewElementById(element.id);
-
-      return {
-        answer_area: [
-          {
-            main_number: element.topicNo,
-            sub_numbers: [],
-            area: this.getOffsetInfo(dom)
-          }
-        ]
-      };
-    },
-    getOffsetInfo(dom) {
-      let { offsetTop, offsetLeft } = dom;
-      let parentNode = dom.offsetParent;
-      while (parentNode.className.indexOf("page-box") === -1) {
-        offsetTop += parentNode.offsetTop;
-        offsetLeft += parentNode.offsetLeft;
-        parentNode = parentNode.offsetParent;
-      }
-      const pw = parentNode.offsetWidth;
-      const ph = parentNode.offsetHeight;
-
-      const infos = [
-        offsetLeft / pw,
-        offsetTop / ph,
-        dom.offsetWidth / pw,
-        dom.offsetHeight / ph
-      ];
-
-      return infos.map(num => num.toFixed(10) * 1);
-    },
-    getPageModel({ cardConfig, paperParams, pages }) {
-      let npages = this.parsePageExchange(pages);
-      npages.forEach(page => {
-        page.exchange.page_size = cardConfig.pageSize;
-      });
-      return JSON.stringify(
-        {
-          version: CARD_VERSION,
-          cardConfig,
-          paperParams,
-          pages: npages
-        },
-        (k, v) => (k.startsWith("_") ? undefined : v)
-      );
-    }
-  }
-};
+import { CARD_VERSION } from "../enumerate";
+import { deepCopy } from "../plugins/utils";
+
+const initIndex = {
+  question: 1,
+  absent: 1,
+  paperType: 1,
+  examNumber: 1,
+  selective: 1,
+  pageNumber: 1
+};
+
+export default {
+  data() {
+    return {
+      fillAreaIndex: {
+        ...initIndex
+      },
+      VALID_ELEMENTS_FOR_EXTERNAL: [
+        "LOCATOR",
+        "BARCODE",
+        "CARD_HEAD",
+        "FILL_QUESTION",
+        "FILL_LINE",
+        "EXPLAIN",
+        "COMPOSITION"
+      ]
+    };
+  },
+  methods: {
+    getFillAreaIndex(type) {
+      return this.fillAreaIndex[type]++;
+    },
+    getElementHumpName(cont) {
+      return cont
+        .split("_")
+        .map(item => item[0] + item.substr(1).toLowerCase())
+        .join("");
+    },
+    getPreviewElementById(id) {
+      return document.getElementById(`preview-${id}`);
+    },
+    parsePageExchange(pages) {
+      const npages = deepCopy(pages);
+      const pageNumberInfo = this.getPageNumberInfo();
+      npages.forEach((page, pindex) => {
+        let exchange = {
+          locator: this.getLocatorInfo(page.locators),
+          barcode: [],
+          info_area: [],
+          fill_area: [],
+          answer_area: []
+        };
+        const elements = [
+          page.globals,
+          ...page.columns.map(column => column.elements)
+        ];
+
+        elements.forEach(elemGroup => {
+          elemGroup.forEach(element => {
+            if (this.VALID_ELEMENTS_FOR_EXTERNAL.includes(element.type)) {
+              const funcName = this.getElementHumpName(element.type);
+              console.log(funcName);
+              const info = this[`get${funcName}Info`](element);
+              Object.entries(info).forEach(([key, vals]) => {
+                exchange[key] = exchange[key].concat(vals);
+              });
+            }
+          });
+        });
+
+        if (!(pindex % 2)) {
+          let pnoInfo = deepCopy(pageNumberInfo);
+          pnoInfo[0].index = this.getFillAreaIndex("pageNumber");
+          exchange.fill_area = exchange.fill_area.concat(pnoInfo);
+        }
+
+        page.exchange = exchange;
+      });
+
+      this.fillAreaIndex = { ...initIndex };
+
+      return npages;
+    },
+    getPageNumberInfo() {
+      const dom = document.querySelector(".page-box-0");
+      let options = [];
+      dom
+        .querySelector(".page-number-rect-list")
+        .childNodes.forEach((item, index) => {
+          console.log(item);
+          options[index] = this.getOffsetInfo(item);
+        });
+      return [
+        {
+          field: "pageNumber",
+          index: 1,
+          single: true,
+          horizontal: true,
+          items: [
+            {
+              main_number: null,
+              sub_number: null,
+              options
+            }
+          ]
+        }
+      ];
+    },
+    getLocatorInfo(locators) {
+      return locators.map(locatorGroup => {
+        const locatorInfos = locatorGroup.map(locator => {
+          return this.getOffsetInfo(document.getElementById(locator.id));
+        });
+        return {
+          top: locatorInfos[0],
+          bottom: locatorInfos[1]
+        };
+      });
+    },
+    getCardHeadInfo(element) {
+      const dom = this.getPreviewElementById(element.id);
+      const headArea = this.getOffsetInfo(dom);
+      let fill_area = [];
+      let barcode = [];
+      // 学生考号
+      if (element.examNumberStyle === "FILL") {
+        // fill_area
+        let listInfos = [];
+        dom
+          .querySelectorAll(".stdno-fill-list")
+          .forEach((questionItem, questionIndex) => {
+            let options = [];
+            questionItem.childNodes.forEach((optionItem, optionIndex) => {
+              options[optionIndex] = this.getOffsetInfo(optionItem);
+            });
+            listInfos[questionIndex] = {
+              main_number: null,
+              sub_number: null,
+              options
+            };
+          });
+
+        fill_area.push({
+          field: "examNumber",
+          index: this.getFillAreaIndex("examNumber"),
+          single: true,
+          horizontal: false,
+          items: listInfos
+        });
+      } else {
+        // barcode
+        const stdnoDom =
+          element.columnNumber <= 2
+            ? dom.querySelector(".head-stdno").parentNode
+            : dom.querySelector(".head-stdno");
+        barcode.push({
+          field: "examNumber",
+          area: this.getOffsetInfo(stdnoDom)
+        });
+      }
+      // 缺考涂填
+      if (element.examAbsent && !element.isSimple) {
+        fill_area.push({
+          field: "absent",
+          index: this.getFillAreaIndex("absent"),
+          single: true,
+          horizontal: true,
+          items: [
+            {
+              main_number: null,
+              sub_number: null,
+              options: [
+                this.getOffsetInfo(document.getElementById("dynamic-miss-area"))
+              ]
+            }
+          ]
+        });
+      }
+      // A/B卷类型
+      if (element.aOrB && !element.isSimple) {
+        if (element.paperType === "PRINT") {
+          // barcode
+          barcode.push({
+            field: "paperType",
+            area: this.getOffsetInfo(
+              document.getElementById("dynamic-aorb-barcode")
+            )
+          });
+        } else {
+          // fill_area
+          let options = [];
+          document
+            .getElementById("head-dynamic-aorb")
+            .querySelectorAll(".head-dynamic-rect")
+            .forEach((optionItem, optionIndex) => {
+              options[optionIndex] = this.getOffsetInfo(optionItem);
+            });
+          fill_area.push({
+            field: "paperType",
+            index: this.getFillAreaIndex("paperType"),
+            single: true,
+            horizontal: true,
+            items: [
+              {
+                main_number: null,
+                sub_number: null,
+                options
+              }
+            ]
+          });
+        }
+      }
+
+      return {
+        info_area: [headArea],
+        fill_area,
+        barcode
+      };
+    },
+    getFillQuestionInfo(element) {
+      const dom = this.getPreviewElementById(element.id);
+      const single = !element.isMultiply;
+      const horizontal = element.optionDirection === "horizontal";
+
+      let fillAreas = [];
+      dom.querySelectorAll(".group-item").forEach(groupItem => {
+        let listInfos = [];
+
+        groupItem
+          .querySelectorAll(".question-item")
+          .forEach((questionItem, questionIndex) => {
+            let options = [];
+            questionItem.childNodes.forEach((optionItem, optionIndex) => {
+              if (optionIndex)
+                options[optionIndex - 1] = this.getOffsetInfo(optionItem);
+            });
+            listInfos[questionIndex] = {
+              main_number: element.topicNo,
+              sub_number: questionItem.firstChild.textContent * 1,
+              options
+            };
+          });
+        fillAreas.push({
+          field: "question",
+          index: this.getFillAreaIndex("question"),
+          single,
+          horizontal,
+          items: listInfos
+        });
+      });
+
+      return {
+        fill_area: fillAreas
+      };
+    },
+    getFillLineInfo(element) {
+      const dom = this.getPreviewElementById(element.id);
+      let sub_numbers = [];
+      for (
+        let i = element.startNumber,
+          len = element.startNumber + element.questionsCount;
+        i < len;
+        i++
+      ) {
+        sub_numbers.push(i);
+      }
+
+      return {
+        answer_area: [
+          {
+            main_number: element.topicNo,
+            sub_numbers,
+            area: this.getOffsetInfo(dom)
+          }
+        ]
+      };
+    },
+    getExplainInfo(element) {
+      const dom = this.getPreviewElementById(element.id);
+
+      return {
+        answer_area: [
+          {
+            main_number: element.topicNo,
+            sub_numbers: [element.serialNumber],
+            area: this.getOffsetInfo(dom)
+          }
+        ]
+      };
+    },
+    getCompositionInfo(element) {
+      const dom = this.getPreviewElementById(element.id);
+
+      return {
+        answer_area: [
+          {
+            main_number: element.topicNo,
+            sub_numbers: [],
+            area: this.getOffsetInfo(dom)
+          }
+        ]
+      };
+    },
+    getOffsetInfo(dom) {
+      let { offsetTop, offsetLeft } = dom;
+      let parentNode = dom.offsetParent;
+      while (parentNode.className.indexOf("page-box") === -1) {
+        offsetTop += parentNode.offsetTop;
+        offsetLeft += parentNode.offsetLeft;
+        parentNode = parentNode.offsetParent;
+      }
+      const pw = parentNode.offsetWidth;
+      const ph = parentNode.offsetHeight;
+
+      const infos = [
+        offsetLeft / pw,
+        offsetTop / ph,
+        dom.offsetWidth / pw,
+        dom.offsetHeight / ph
+      ];
+
+      return infos.map(num => num.toFixed(10) * 1);
+    },
+    getPageModel({ cardConfig, paperParams, pages }) {
+      let npages = this.parsePageExchange(pages);
+      npages.forEach(page => {
+        page.exchange.page_size = cardConfig.pageSize;
+      });
+      return JSON.stringify(
+        {
+          version: CARD_VERSION,
+          cardConfig,
+          paperParams,
+          pages: npages
+        },
+        (k, v) => (k.startsWith("_") ? undefined : v)
+      );
+    }
+  }
+};

+ 70 - 67
src/modules/stmms/api.js

@@ -1,67 +1,70 @@
-import { $postParam, $post } from "@/plugins/axios";
-
-export const examStructureListPage = datas => {
-  return $postParam("/api/admin/exam/structure/list", datas);
-};
-export const examStructureUpload = datas => {
-  return $post("/api/admin/exam/structure/upload", datas);
-};
-export const examStructureUploadAnswer = datas => {
-  return $post("/api/admin/exam/structure/upload_answer", datas);
-};
-export const examStructurePreviewStructure = id => {
-  return $postParam("/api/admin/exam/structure/preview_structure", { id });
-};
-export const examStructureFindJpg = datas => {
-  return $postParam("/api/admin/exam/structure/find_jpg_file", datas);
-};
-export const examStructureSubmit = datas => {
-  return $post("/api/admin/exam/structure/submit", datas);
-};
-export const updateObjectiveAnswer = datas => {
-  return $post("/api/admin/exam/structure/update_objective_answer", datas);
-};
-
-// score-archive
-export const scoreListPage = datas => {
-  return $postParam("/api/admin/sync/score/list", datas);
-};
-export const scoreExport = datas => {
-  return $postParam("/api/admin/sync/score/export", datas);
-};
-export const scoreSync = datas => {
-  return $postParam("/api/admin/sync/score/sync", datas);
-};
-export const scorePreview = id => {
-  return $postParam("/api/admin/sync/score/preview", { id });
-};
-export const scoreDownload = id => {
-  return $postParam(
-    "/api/admin/sync/score/download",
-    { id },
-    {
-      responseType: "blob"
-    }
-  );
-};
-export const scoreBatchDownload = datas => {
-  return $postParam("/api/admin/sync/score/batch_download", datas);
-};
-// 第三方登录ypt
-export const userSysRoles = () => {
-  return $post("/api/admin/common/get_open_role", {});
-};
-export const yptAuth = roleType => {
-  if (roleType === "MARKER") {
-    return $post("/api/admin/exam/sso/marker_login", {});
-  } else {
-    return $post("/api/admin/exam/sso/marker_leader_login", {});
-  }
-};
-// sync-result-manage
-export const syncResultListPage = datas => {
-  return $postParam("/api/admin/data/sync/query", datas);
-};
-export const syncResync = id => {
-  return $postParam("/api/admin/data/sync/resync", { id });
-};
+import { $postParam, $post } from "@/plugins/axios";
+
+export const examStructureListPage = datas => {
+  return $postParam("/api/admin/exam/structure/list", datas);
+};
+export const examStructureUpload = datas => {
+  return $post("/api/admin/exam/structure/upload", datas);
+};
+export const examStructureUploadAnswer = datas => {
+  return $post("/api/admin/exam/structure/upload_answer", datas);
+};
+export const examStructurePreviewStructure = id => {
+  return $postParam("/api/admin/exam/structure/preview_structure", { id });
+};
+export const examStructureFindJpg = datas => {
+  return $postParam("/api/admin/exam/structure/find_jpg_file", datas);
+};
+export const examStructureSubmit = datas => {
+  return $post("/api/admin/exam/structure/submit", datas);
+};
+export const updateObjectiveAnswer = datas => {
+  return $post("/api/admin/exam/structure/update_objective_answer", datas);
+};
+export const examBindMarkLeader = datas => {
+  return $post("/api/admin/exam/structure/bind_mark_leader", datas);
+};
+
+// score-archive
+export const scoreListPage = datas => {
+  return $postParam("/api/admin/sync/score/list", datas);
+};
+export const scoreExport = datas => {
+  return $postParam("/api/admin/sync/score/export", datas);
+};
+export const scoreSync = datas => {
+  return $postParam("/api/admin/sync/score/sync", datas);
+};
+export const scorePreview = id => {
+  return $postParam("/api/admin/sync/score/preview", { id });
+};
+export const scoreDownload = id => {
+  return $postParam(
+    "/api/admin/sync/score/download",
+    { id },
+    {
+      responseType: "blob"
+    }
+  );
+};
+export const scoreBatchDownload = datas => {
+  return $postParam("/api/admin/sync/score/batch_download", datas);
+};
+// 第三方登录ypt
+export const userSysRoles = () => {
+  return $post("/api/admin/common/get_open_role", {});
+};
+export const yptAuth = roleType => {
+  if (roleType === "MARKER") {
+    return $post("/api/admin/exam/sso/marker_login", {});
+  } else {
+    return $post("/api/admin/exam/sso/marker_leader_login", {});
+  }
+};
+// sync-result-manage
+export const syncResultListPage = datas => {
+  return $postParam("/api/admin/data/sync/query", datas);
+};
+export const syncResync = id => {
+  return $postParam("/api/admin/data/sync/resync", { id });
+};

+ 341 - 0
src/modules/stmms/components/SelectTypeUser.vue

@@ -0,0 +1,341 @@
+<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">{{ roleName }}</div>
+          <div class="user-search">
+            <el-input
+              v-model="filterLabel"
+              :placeholder="`请输入${roleName}名称`"
+              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="`已选${roleName}:`">
+              <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>
+        </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: {
+    specialPrivilege: {
+      type: String,
+      default: ""
+    },
+    userIds: {
+      type: Array,
+      default() {
+        return [];
+      }
+    },
+    userLimitCount: {
+      type: Number,
+      default: 5
+    }
+  },
+  data() {
+    const usersValidator = (rule, value, callback) => {
+      if (!this.selectedUserIds.length) {
+        callback(new Error("请选择用户"));
+      } else if (
+        this.userLimitCount &&
+        this.selectedUserIds.length > this.userLimitCount
+      ) {
+        callback(new Error(`选择用户数不得超过${this.userLimitCount}`));
+      } else {
+        callback();
+      }
+    };
+
+    return {
+      modalIsShow: false,
+      filterLabel: "",
+      orgUsers: [],
+      userTree: [],
+      userList: [],
+      selectedUsers: [],
+      selectedUserIds: [],
+      roleName: "",
+      rolesNames: { SUBJECT_HEADER: "科组长" },
+      defaultProps: {
+        children: "children",
+        label: "label"
+      },
+      rules: {
+        users: [
+          {
+            required: true,
+            validator: usersValidator,
+            trigger: "change"
+          }
+        ]
+      }
+    };
+  },
+  mounted() {
+    this.roleName = this.rolesNames[this.specialPrivilege];
+    this.getOrgData();
+  },
+  methods: {
+    visibleChange() {
+      this.filterLabel = "";
+      this.selectedUserIds = [...this.userIds];
+      this.updateSelectedUsersFromUserIds();
+      this.labelChange();
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    // user
+    async getOrgData() {
+      const data = await organizationList({
+        specialPrivilege: this.specialPrivilege
+      });
+      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,
+                loginName: user.loginName,
+                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);
+      }
+      this.updateSelectedUsersFromUserIds();
+    },
+    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");
+    },
+    // confirm
+    async confirm() {
+      const valid = await this.$refs.modalFormRef.validate().catch(() => {});
+      if (!valid) return;
+
+      this.$emit("confirm", this.selectedUsers);
+      this.cancel();
+    }
+  }
+};
+</script>

+ 245 - 207
src/modules/stmms/views/UploadStructure.vue

@@ -1,207 +1,245 @@
-<template>
-  <div class="upload-structure">
-    <div class="part-box part-box-filter part-box-flex">
-      <el-form ref="FilterForm" label-position="left" 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>
-        </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>
-    <div class="part-box part-box-pad">
-      <el-table ref="TableList" :data="dataList">
-        <el-table-column
-          prop="thirdRelateId"
-          label="云阅卷考试ID"
-          width="120"
-        ></el-table-column>
-        <el-table-column
-          prop="thirdRelateName"
-          label="云阅卷考试名称"
-        ></el-table-column>
-        <el-table-column prop="courseName" label="课程(代码)">
-          <span slot-scope="scope">
-            {{ scope.row.courseName }}({{ scope.row.courseCode }})
-          </span>
-        </el-table-column>
-        <el-table-column prop="paperNumber" label="试卷编号"></el-table-column>
-        <el-table-column
-          prop="paperType"
-          label="试卷类型"
-          width="100"
-        ></el-table-column>
-        <el-table-column prop="statusStr" label="状态" width="100">
-        </el-table-column>
-        <el-table-column class-name="action-column" label="操作" width="220px">
-          <template slot-scope="scope">
-            <el-button
-              class="btn-primary"
-              type="text"
-              :disabled="scope.row.taskStatus === 'RUNNING'"
-              @click="toSetParams(scope.row)"
-              >评卷参数设置</el-button
-            >
-            <el-button
-              class="btn-primary"
-              type="text"
-              :disabled="
-                !scope.row.paperInfoJson || scope.row.taskStatus === 'RUNNING'
-              "
-              @click="toSetAnswer(scope.row)"
-              >设置客观题标答</el-button
-            >
-            <el-button
-              v-if="checkPrivilege('link', 'Upload')"
-              class="btn-primary"
-              type="text"
-              :disabled="scope.row.taskStatus === 'RUNNING'"
-              @click="toUpload(scope.row)"
-              >上传标答文件</el-button
-            >
-            <el-button
-              v-if="checkPrivilege('link', 'Preview')"
-              class="btn-primary"
-              type="text"
-              :disabled="!scope.row.paperAnswer"
-              @click="toViewAnswer(scope.row, scope.row.paperType)"
-              >查看标答文件</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>
-
-    <UploadPaperAnswerDialog
-      ref="UploadPaperAnswerDialog"
-      :instance="curTask"
-      @modified="delayUpdateList"
-    />
-    <ModifyMarkParams
-      ref="ModifyMarkParams"
-      :instance="curTask"
-      @modified="delayUpdateList"
-    />
-    <ModifyObjectiveAnswer
-      ref="ModifyObjectiveAnswer"
-      :instance="curTask"
-      @modified="delayUpdateList"
-    />
-  </div>
-</template>
-
-<script>
-import { examStructureListPage } from "../api";
-import UploadPaperAnswerDialog from "../components/UploadPaperAnswerDialog";
-import ModifyMarkParams from "../components/markParam/ModifyMarkParams";
-import ModifyObjectiveAnswer from "../components/markParam/ModifyObjectiveAnswer.vue";
-
-export default {
-  name: "upload-structure",
-  components: {
-    UploadPaperAnswerDialog,
-    ModifyMarkParams,
-    ModifyObjectiveAnswer
-  },
-  data() {
-    return {
-      filter: {
-        semesterId: "",
-        examId: ""
-      },
-      current: 1,
-      size: this.GLOBAL.pageSize,
-      total: 0,
-      dataList: [],
-      curTask: {}
-    };
-  },
-  mounted() {
-    this.toPage(1);
-    // this.dataList.push({
-    //   thirdRelateId: 1,
-    //   thirdRelateName: "考试1",
-    //   courseName: "语文",
-    //   courseCode: "yw001",
-    //   paperNumber: 112345667,
-    //   paperType: "AB",
-    //   paperTypes: ["A", "B"],
-    //   status: "FINISH"
-    // });
-  },
-  methods: {
-    delayUpdateList() {
-      setTimeout(() => {
-        this.getList();
-      }, 1000);
-    },
-    async getList() {
-      if (!this.checkPrivilege("list", "list")) return;
-      const datas = {
-        ...this.filter,
-        pageNumber: this.current,
-        pageSize: this.size
-      };
-      const data = await examStructureListPage(datas);
-      this.dataList = data.records;
-      this.total = data.total;
-    },
-    toPage(page) {
-      this.current = page;
-      this.getList();
-    },
-    toSetParams(row) {
-      this.curTask = row;
-      this.$refs.ModifyMarkParams.open();
-    },
-    toSetAnswer(row) {
-      this.curTask = row;
-      this.$refs.ModifyObjectiveAnswer.open();
-    },
-    toUpload(row) {
-      this.curTask = row;
-      this.$refs.UploadPaperAnswerDialog.open();
-    },
-    toViewAnswer(row, paperType) {
-      if (!row.paperAnswer) {
-        this.$message.error("暂时还没有标答可预览!");
-        return;
-      }
-
-      const paperAnswer = JSON.parse(row.paperAnswer);
-      const paper = paperAnswer.find(item => item.paperType === paperType);
-      if (!paper) {
-        this.$message.error("标答文件不存在!");
-        return;
-      }
-
-      window.open(paper.answerUrl);
-    }
-  }
-};
-</script>
+<template>
+  <div class="upload-structure">
+    <div class="part-box part-box-filter part-box-flex">
+      <el-form ref="FilterForm" label-position="left" 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>
+        </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>
+    <div class="part-box part-box-pad">
+      <el-table ref="TableList" :data="dataList">
+        <el-table-column
+          prop="thirdRelateId"
+          label="云阅卷考试ID"
+          width="120"
+        ></el-table-column>
+        <el-table-column
+          prop="thirdRelateName"
+          label="云阅卷考试名称"
+        ></el-table-column>
+        <el-table-column prop="courseName" label="课程(代码)">
+          <span slot-scope="scope">
+            {{ scope.row.courseName }}({{ scope.row.courseCode }})
+          </span>
+        </el-table-column>
+        <el-table-column prop="paperNumber" label="试卷编号"></el-table-column>
+        <el-table-column
+          prop="paperType"
+          label="试卷类型"
+          width="100"
+        ></el-table-column>
+        <el-table-column prop="statusStr" label="状态" width="100">
+        </el-table-column>
+        <el-table-column class-name="action-column" label="操作" width="220px">
+          <template slot-scope="scope">
+            <el-button
+              class="btn-primary"
+              type="text"
+              :disabled="scope.row.taskStatus === 'RUNNING'"
+              @click="toSetParams(scope.row)"
+              >评卷参数设置</el-button
+            >
+            <el-button
+              class="btn-primary"
+              type="text"
+              :disabled="
+                !scope.row.paperInfoJson || scope.row.taskStatus === 'RUNNING'
+              "
+              @click="toSetAnswer(scope.row)"
+              >设置客观题标答</el-button
+            >
+            <el-button
+              class="btn-primary"
+              type="text"
+              :disabled="
+                !scope.row.paperInfoJson || scope.row.taskStatus === 'RUNNING'
+              "
+              @click="toBindMarkLeader(scope.row)"
+              >绑定科组长</el-button
+            >
+            <el-button
+              v-if="checkPrivilege('link', 'Upload')"
+              class="btn-primary"
+              type="text"
+              :disabled="scope.row.taskStatus === 'RUNNING'"
+              @click="toUpload(scope.row)"
+              >上传标答文件</el-button
+            >
+            <el-button
+              v-if="checkPrivilege('link', 'Preview')"
+              class="btn-primary"
+              type="text"
+              :disabled="!scope.row.paperAnswer"
+              @click="toViewAnswer(scope.row, scope.row.paperType)"
+              >查看标答文件</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>
+
+    <UploadPaperAnswerDialog
+      ref="UploadPaperAnswerDialog"
+      :instance="curTask"
+      @modified="delayUpdateList"
+    />
+    <ModifyMarkParams
+      ref="ModifyMarkParams"
+      :instance="curTask"
+      @modified="delayUpdateList"
+    />
+    <ModifyObjectiveAnswer
+      ref="ModifyObjectiveAnswer"
+      :instance="curTask"
+      @modified="delayUpdateList"
+    />
+    <SelectTypeUser
+      ref="SelectTypeUser"
+      special-privilege="SUBJECT_HEADER"
+      :user-ids="curMarkLeaders"
+      :user-limit-count="0"
+      @confirm="headerBind"
+    />
+  </div>
+</template>
+
+<script>
+import { examStructureListPage, examBindMarkLeader } from "../api";
+import UploadPaperAnswerDialog from "../components/UploadPaperAnswerDialog";
+import ModifyMarkParams from "../components/markParam/ModifyMarkParams";
+import ModifyObjectiveAnswer from "../components/markParam/ModifyObjectiveAnswer.vue";
+import SelectTypeUser from "../components/SelectTypeUser.vue";
+
+export default {
+  name: "upload-structure",
+  components: {
+    UploadPaperAnswerDialog,
+    ModifyMarkParams,
+    ModifyObjectiveAnswer,
+    SelectTypeUser
+  },
+  data() {
+    return {
+      filter: {
+        semesterId: "",
+        examId: ""
+      },
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      dataList: [],
+      curTask: {},
+      curMarkLeaders: []
+    };
+  },
+  mounted() {
+    this.toPage(1);
+    // this.dataList.push({
+    //   thirdRelateId: 1,
+    //   thirdRelateName: "考试1",
+    //   courseName: "语文",
+    //   courseCode: "yw001",
+    //   paperNumber: 112345667,
+    //   paperType: "AB",
+    //   paperTypes: ["A", "B"],
+    //   status: "FINISH"
+    // });
+  },
+  methods: {
+    delayUpdateList() {
+      setTimeout(() => {
+        this.getList();
+      }, 1000);
+    },
+    async getList() {
+      if (!this.checkPrivilege("list", "list")) return;
+      const datas = {
+        ...this.filter,
+        pageNumber: this.current,
+        pageSize: this.size
+      };
+      const data = await examStructureListPage(datas);
+      this.dataList = data.records;
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    toSetParams(row) {
+      this.curTask = row;
+      this.$refs.ModifyMarkParams.open();
+    },
+    toSetAnswer(row) {
+      this.curTask = row;
+      this.$refs.ModifyObjectiveAnswer.open();
+    },
+    toUpload(row) {
+      this.curTask = row;
+      this.$refs.UploadPaperAnswerDialog.open();
+    },
+    toViewAnswer(row, paperType) {
+      if (!row.paperAnswer) {
+        this.$message.error("暂时还没有标答可预览!");
+        return;
+      }
+
+      const paperAnswer = JSON.parse(row.paperAnswer);
+      const paper = paperAnswer.find(item => item.paperType === paperType);
+      if (!paper) {
+        this.$message.error("标答文件不存在!");
+        return;
+      }
+
+      window.open(paper.answerUrl);
+    },
+    toBindMarkLeader(row) {
+      this.curTask = row;
+      const markLeaders = row.markLeader ? JSON.parse(row.markLeader) : [];
+      this.curMarkLeaders = markLeaders.map(item => item.id);
+      this.$refs.SelectTypeUser.open();
+    },
+    async headerBind(users) {
+      await examBindMarkLeader({
+        id: this.curTask.id,
+        markLeader: users.map(item => {
+          return {
+            id: item.id,
+            loginName: item.loginName
+          };
+        })
+      });
+      this.$message.success("绑定成功!");
+      this.getList();
+    }
+  }
+};
+</script>