Jelajahi Sumber

conflict fix

zhangjie 4 tahun lalu
induk
melakukan
877e431859
42 mengubah file dengan 1672 tambahan dan 279 penghapusan
  1. 15 2
      src/api/examwork-activity.js
  2. 53 4
      src/api/examwork-course.js
  3. 4 0
      src/api/examwork-exam.js
  4. 15 0
      src/api/examwork-student.js
  5. 49 7
      src/api/examwork-task.js
  6. 9 0
      src/api/system-info.js
  7. 2 2
      src/api/system-user.js
  8. 6 2
      src/components/ActivitySelect.vue
  9. 6 2
      src/components/CourseSelect.vue
  10. 66 0
      src/components/ExamRoomSelect.vue
  11. 3 2
      src/components/ExamSelect.vue
  12. 2 1
      src/components/ExamTypeSelect.vue
  13. 59 0
      src/components/InvigilatorSelect.vue
  14. 3 2
      src/components/OrgSelect.vue
  15. 3 2
      src/components/RoleSelect.vue
  16. 2 1
      src/components/StateSelect.vue
  17. 62 0
      src/components/TaskSelect.vue
  18. 5 0
      src/constant/constants.js
  19. 239 0
      src/features/examwork/ActivityManagement/ActivityEdit.vue
  20. 16 6
      src/features/examwork/ActivityManagement/ActivityManagement.vue
  21. 1 0
      src/features/examwork/ActivityManagement/ActivityManagementDialog.vue
  22. 49 37
      src/features/examwork/CourseManagement/CourseManagement.vue
  23. 173 0
      src/features/examwork/CourseManagement/CoursePaperDialog.vue
  24. 124 0
      src/features/examwork/CourseManagement/PaperImportDialog.vue
  25. 63 52
      src/features/examwork/ExamManagement/ExamEdit.vue
  26. 4 3
      src/features/examwork/ExamManagement/ExamManagement.vue
  27. 47 14
      src/features/examwork/ExamStudentImport/ExamStudentImport.vue
  28. 93 0
      src/features/examwork/ExamStudentImport/ExamStudentImportDialog.vue
  29. 11 7
      src/features/examwork/ExamStudentManagement/ExamStudentManagement.vue
  30. 19 4
      src/features/examwork/ExamStudentManagement/ExamStudentManagementDialog.vue
  31. 129 0
      src/features/examwork/ImportExportTask/ImportExportTask.vue
  32. 83 0
      src/features/examwork/InvigilateManagement/InvigilateImportDialog.vue
  33. 31 32
      src/features/examwork/InvigilateManagement/InvigilateManagement.vue
  34. 26 79
      src/features/examwork/InvigilateManagement/InvigilateManagementDialog.vue
  35. 46 11
      src/features/examwork/StudentManagement/StudentManagement.vue
  36. 121 0
      src/features/examwork/StudentManagement/StudentManagementDialog.vue
  37. 2 2
      src/features/system/OrgManagement/OrgManagement.vue
  38. 4 4
      src/features/system/UserManagement/UserManagement.vue
  39. 5 0
      src/filters/index.js
  40. 17 1
      src/router/index.js
  41. 1 0
      src/utils/utils.js
  42. 4 0
      src/views/Layout/components/menu.js

+ 15 - 2
src/api/examwork-activity.js

@@ -12,9 +12,19 @@ export function searchActivities({
   return httpApp.post("/api/admin/activity/query?" + object2QueryString(data));
 }
 
+export function getActivityDetail({ id = "", pageNumber = 1, pageSize = 10 }) {
+  if (id) {
+    const data = pickBy({ id, pageNumber, pageSize }, (v) => v !== "");
+    return httpApp.post(
+      "/api/admin/activity/query?" + object2QueryString(data)
+    );
+  } else {
+    throw "id必填";
+  }
+}
+
 export function saveActivity({
   id = "",
-  code = "",
   enable = 1,
   examId = "",
   finishTime = "",
@@ -27,7 +37,6 @@ export function saveActivity({
     {
       examId,
       id,
-      code,
       enable,
       finishTime,
       maxDurationSeconds,
@@ -40,6 +49,10 @@ export function saveActivity({
   return httpApp.post("/api/admin/activity/save", [data]);
 }
 
+export function saveActivities(ary) {
+  return httpApp.post("/api/admin/activity/save", ary);
+}
+
 export function toggleEnableActivity({ examId, id, enable }) {
   return httpApp.post("/api/admin/activity/save", [{ examId, id, enable }]);
 }

+ 53 - 4
src/api/examwork-course.js

@@ -3,18 +3,67 @@ import { pickBy } from "lodash-es";
 import { object2QueryString } from "@/utils/utils";
 
 export function searchCourses({
+  id = "",
   examId = "",
-  courseName = "",
-  enable = "",
+  code = "",
+  hasPaper = "",
   pageNumber = 1,
   pageSize = 10,
 }) {
   const data = pickBy(
-    { examId, courseName, enable, pageNumber, pageSize },
+    { id, examId, code, hasPaper, pageNumber, pageSize },
     (v) => v !== ""
   );
-  if (data.examId)
+  if (data.examId || data.id)
     return httpApp.post(
       "/api/admin/exam/course/query?" + object2QueryString(data)
     );
 }
+
+export function searchPapers({ examId = "", courseCode = "" }) {
+  const data = pickBy({ examId, courseCode }, (v) => v !== "");
+  return httpApp.post(
+    "/api/admin/exam/paper/query?" + object2QueryString(data)
+  );
+}
+
+export function saveCourse({
+  examId = "",
+  courseCode = "",
+  objectiveShuffle = "",
+  optionShuffle = "",
+}) {
+  const data = pickBy(
+    {
+      examId,
+      courseCode,
+      objectiveShuffle,
+      optionShuffle,
+    },
+    (v) => v !== ""
+  );
+  return httpApp.post("/api/admin/exam/course/save", data);
+}
+
+export function savePaper({ id = "", weight = "", audioPlayCount = "" }) {
+  const data = pickBy(
+    {
+      id,
+      weight,
+      audioPlayCount,
+    },
+    (v) => v !== ""
+  );
+  return httpApp.post("/api/admin/exam/paper/save", data);
+}
+
+/**
+ *
+ * @param {Object[]} papers
+ * @param {String} papers.id
+ * @param {Number} papers.weight
+ * @param {Number} papers.audioPlayCount
+ */
+export function savePapers(papers) {
+  return httpApp.post("/api/admin/exam/paper/save", papers);
+}

+ 4 - 0
src/api/examwork-exam.js

@@ -113,3 +113,7 @@ export function saveExam({
   );
   return httpApp.post("/api/admin/exam/save", data);
 }
+
+export function getExamRoom() {
+  return httpApp.post("/api/admin/sys/examRoom/query");
+}

+ 15 - 0
src/api/examwork-student.js

@@ -48,3 +48,18 @@ export function resetStudentPassword({ id, password }) {
     password: AESString(password),
   });
 }
+
+export function searchStudentExamRecord({
+  studentId = "",
+  examId = "",
+  pageNumber = 1,
+  pageSize = 10,
+}) {
+  const data = pickBy(
+    { studentId, examId, pageNumber, pageSize },
+    (v) => v !== ""
+  );
+  return httpApp.post(
+    "/api/admin/student/exam_record/query?" + object2QueryString(data)
+  );
+}

+ 49 - 7
src/api/examwork-task.js

@@ -4,13 +4,55 @@ import { object2QueryString } from "@/utils/utils";
 
 export function searchTasks({ type = "", pageNumber = 1, pageSize = 10 }) {
   const data = pickBy({ type, pageNumber, pageSize }, (v) => v !== "");
-  if (data.type)
-    return httpApp.post("/api/admin/task/query?" + object2QueryString(data));
+  return httpApp.post("/api/admin/task/query?" + object2QueryString(data));
 }
 
-export function downloadFile({ id = "", fileType = "" }) {
-  const data = pickBy({ id, fileType }, (v) => v !== "");
-  return httpApp.post(
-    "/api/admin/task/file/download?" + object2QueryString(data)
-  );
+export function importPaper({
+  examId,
+  processPaper,
+  processAnswer,
+  encryptMode = "auto",
+  objectiveShuffle = "",
+  optionShuffle = "",
+  audioPlayCount = "",
+  fileName,
+  file,
+  md5,
+}) {
+  const form = new FormData();
+  form.append("examId", examId);
+  form.append("processPaper", processPaper);
+  form.append("processAnswer", processAnswer);
+  form.append("encryptMode", encryptMode);
+  form.append("objectiveShuffle", objectiveShuffle);
+  form.append("optionShuffle", optionShuffle);
+  form.append("audioPlayCount", audioPlayCount);
+  form.append("fileName", fileName);
+  form.append("file", file);
+  return httpApp.post("/api/admin/exam/paper/import", form, {
+    headers: { "Content-Type": "multipart/form-data", md5 },
+  });
+}
+
+export function importExamStudent({ examId, fileName, file, md5 }) {
+  const form = new FormData();
+  form.append("examId", examId);
+  form.append("fileName", fileName);
+  form.append("file", file);
+  return httpApp.post("/api/admin/examStudent/import", form, {
+    headers: { "Content-Type": "multipart/form-data", md5 },
+  });
+}
+
+export function importInvigilator({ fileName, file, md5 }) {
+  const form = new FormData();
+  form.append("fileName", fileName);
+  form.append("file", file);
+  return httpApp.post("/api/admin/invigilateUser/import", form, {
+    headers: { "Content-Type": "multipart/form-data", md5 },
+  });
+}
+
+export function exportInvigilate() {
+  return httpApp.post("/api/admin/invigilateUser/export");
 }

+ 9 - 0
src/api/system-info.js

@@ -1,5 +1,6 @@
 import { httpApp } from "@/plugins/axiosIndex";
 import { object2QueryString } from "@/utils/utils";
+import { pickBy } from "lodash-es";
 
 export function uploadFile({ file, md5 }) {
   const form = new FormData();
@@ -10,3 +11,11 @@ export function uploadFile({ file, md5 }) {
     { headers: { "Content-Type": "multipart/form-data", md5: md5 } }
   );
 }
+
+export function downloadFile({ id = "", type = "" }) {
+  const data = pickBy({ id, type }, (v) => v !== "");
+  return httpApp.post(
+    "/api/admin/sys/file/download?" +
+      object2QueryString({ ...data, type: type })
+  );
+}

+ 2 - 2
src/api/system-user.js

@@ -4,7 +4,7 @@ import { object2QueryString, AESString } from "@/utils/utils";
 
 export function searchUsers({
   orgId = "",
-  role,
+  roleCode,
   loginName = "",
   name = "",
   enable = "",
@@ -12,7 +12,7 @@ export function searchUsers({
   pageSize = 10,
 }) {
   const data = pickBy(
-    { orgId, role, loginName, name, enable, pageNumber, pageSize },
+    { orgId, roleCode, loginName, name, enable, pageNumber, pageSize },
     (v) => v !== ""
   );
   return httpApp.post("/api/admin/user/query?" + object2QueryString(data));

+ 6 - 2
src/components/ActivitySelect.vue

@@ -4,7 +4,7 @@
     class="size-select"
     placeholder="请选择"
     @change="select"
-    style="width: 100px;"
+    :style="styles"
     filterable
     remote
     :remote-method="search"
@@ -29,6 +29,7 @@ export default {
   props: {
     value: String,
     examId: String,
+    styles: { type: String, default: "width: 100px;" },
   },
   data() {
     return {
@@ -46,6 +47,9 @@ export default {
         this.selected = val;
       },
     },
+    examId() {
+      this.search();
+    },
   },
   methods: {
     async search(query) {
@@ -55,7 +59,7 @@ export default {
         pageNumber: 1,
         pageSize: 30,
       });
-      this.optionList = res?.data.data.records.records;
+      this.optionList = res?.data.data.records;
     },
     select() {
       this.$emit("input", this.selected);

+ 6 - 2
src/components/CourseSelect.vue

@@ -4,7 +4,7 @@
     class="size-select"
     placeholder="请选择"
     @change="select"
-    style="width: 100px;"
+    :style="styles"
     filterable
     remote
     :remote-method="search"
@@ -29,6 +29,7 @@ export default {
   props: {
     value: String,
     examId: String,
+    styles: { type: String, default: "width: 100px;" },
   },
   data() {
     return {
@@ -46,6 +47,9 @@ export default {
         this.selected = val;
       },
     },
+    examId() {
+      this.search();
+    },
   },
   methods: {
     async search(query) {
@@ -55,7 +59,7 @@ export default {
         pageNumber: 1,
         pageSize: 30,
       });
-      this.optionList = res?.data.data.records.records;
+      this.optionList = res?.data.data.records;
     },
     select() {
       this.$emit("input", this.selected);

+ 66 - 0
src/components/ExamRoomSelect.vue

@@ -0,0 +1,66 @@
+<template>
+  <el-select
+    v-model="selected"
+    class="size-select"
+    placeholder="请选择"
+    @change="select"
+    :style="styles"
+    filterable
+    remote
+    :remote-method="search"
+    clearable
+  >
+    <el-option
+      v-for="item in optionList"
+      :key="item.roomCode"
+      :label="item.roomName"
+      :value="item.roomCode"
+    >
+      <span>{{ item.roomName }}</span>
+    </el-option>
+  </el-select>
+</template>
+
+<script>
+import { object2QueryString } from "@/utils/utils";
+export default {
+  name: "ExamRoomSelect",
+  props: {
+    value: [String, Array],
+    styles: { type: String, default: "width: 100px;" },
+  },
+  data() {
+    return {
+      optionList: [],
+      selected: "",
+    };
+  },
+  async created() {
+    this.search();
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(val) {
+        this.selected = val;
+      },
+    },
+  },
+  methods: {
+    async search(query) {
+      const res = await this.$http.post(
+        "/api/admin/sys/examRoom/query?" +
+          object2QueryString({ roomName: query })
+      );
+      // console.log(res.data);
+      this.optionList = res.data.data;
+    },
+    select() {
+      this.$emit("input", this.selected);
+      this.$emit("change", this.selected);
+    },
+  },
+};
+</script>
+
+<style></style>

+ 3 - 2
src/components/ExamSelect.vue

@@ -4,7 +4,7 @@
     class="size-select"
     placeholder="请选择"
     @change="select"
-    style="width: 100px;"
+    :style="styles"
     filterable
     remote
     :remote-method="search"
@@ -28,6 +28,7 @@ export default {
   name: "ExamSelect",
   props: {
     value: String,
+    styles: { type: String, default: "width: 100px;" },
   },
   data() {
     return {
@@ -54,7 +55,7 @@ export default {
         pageSize: 30,
       });
       // console.log(res.data);
-      this.optionList = res.data.data.records.records;
+      this.optionList = res.data.data.records;
     },
     select() {
       this.$emit("input", this.selected);

+ 2 - 1
src/components/ExamTypeSelect.vue

@@ -4,7 +4,7 @@
     class="size-select"
     placeholder="请选择"
     @change="select"
-    style="width: 100px;"
+    :style="styles"
     clearable
   >
     <el-option
@@ -26,6 +26,7 @@ export default {
       type: String,
       default: "",
     },
+    styles: { type: String, default: "width: 100px;" },
   },
   data() {
     return {

+ 59 - 0
src/components/InvigilatorSelect.vue

@@ -0,0 +1,59 @@
+<template>
+  <el-select
+    v-model="selected"
+    class="size-select"
+    placeholder="请选择"
+    @change="select"
+    :style="styles"
+    :multiple="multiple"
+    clearable
+  >
+    <el-option
+      v-for="item in optionList"
+      :key="item.id"
+      :label="item.name"
+      :value="item.id"
+    >
+      <span>{{ item.name }}</span>
+    </el-option>
+  </el-select>
+</template>
+
+<script>
+import { searchUsers } from "@/api/system-user";
+export default {
+  name: "InvigilatorSelect",
+  props: {
+    value: [String, Array],
+    multiple: Boolean,
+    styles: { type: String, default: "width: 100px;" },
+  },
+  data() {
+    return {
+      optionList: [],
+      selected: "",
+    };
+  },
+  async created() {
+    const res = await searchUsers({ roleCode: "INVIGILATE", pageSize: 1000 });
+    // console.log(res.data);
+    this.optionList = res.data.data.records;
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(val) {
+        this.selected = val;
+      },
+    },
+  },
+  methods: {
+    select() {
+      this.$emit("input", this.selected);
+      this.$emit("change", this.selected);
+    },
+  },
+};
+</script>
+
+<style></style>

+ 3 - 2
src/components/OrgSelect.vue

@@ -4,7 +4,7 @@
     class="size-select"
     placeholder="请选择"
     @change="select"
-    style="width: 100px;"
+    :style="styles"
     clearable
   >
     <el-option
@@ -23,6 +23,7 @@ export default {
   name: "OrgSelect",
   props: {
     value: [String, Array],
+    styles: { type: String, default: "width: 100px;" },
   },
   data() {
     return {
@@ -33,7 +34,7 @@ export default {
   async created() {
     const res = await this.$http.post("/api/admin/sys/org/query");
     // console.log(res.data);
-    this.optionList = res.data.data.records;
+    this.optionList = res.data.data;
   },
   watch: {
     value: {

+ 3 - 2
src/components/RoleSelect.vue

@@ -4,7 +4,7 @@
     class="size-select"
     placeholder="请选择"
     @change="select"
-    style="width: 100px;"
+    :style="styles"
     :multiple="multiple"
     clearable
   >
@@ -25,6 +25,7 @@ export default {
   props: {
     value: [String, Array],
     multiple: Boolean,
+    styles: { type: String, default: "width: 100px;" },
   },
   data() {
     return {
@@ -35,7 +36,7 @@ export default {
   async created() {
     const res = await this.$http.post("/api/admin/sys/role/query");
     // console.log(res.data);
-    this.optionList = res.data.data.records;
+    this.optionList = res.data.data;
   },
   watch: {
     value: {

+ 2 - 1
src/components/StateSelect.vue

@@ -4,7 +4,7 @@
     class="size-select"
     placeholder="请选择"
     @change="select"
-    style="width: 100px;"
+    :style="styles"
     clearable
   >
     <el-option
@@ -27,6 +27,7 @@ export default {
       default: 1,
     },
     options: { type: Array, default: () => null },
+    styles: { type: String, default: "width: 100px;" },
   },
   data() {
     return {

+ 62 - 0
src/components/TaskSelect.vue

@@ -0,0 +1,62 @@
+<template>
+  <el-select
+    v-model="selected"
+    class="size-select"
+    placeholder="请选择"
+    @change="select"
+    :style="styles"
+    clearable
+  >
+    <el-option
+      v-for="item in optionList"
+      :key="item.code"
+      :label="item.name"
+      :value="item.code"
+    >
+      <span>{{ item.name }}</span>
+    </el-option>
+  </el-select>
+</template>
+
+<script>
+export default {
+  name: "TaskSelect",
+  props: {
+    value: {
+      type: String,
+      default: "",
+    },
+    options: { type: Array, default: () => null },
+    styles: { type: String, default: "width: 100px;" },
+  },
+  data() {
+    return {
+      optionList: this.options || [
+        { code: "CALCULATE_EXAM_SCORE", name: "考试重新算分" },
+        { code: "IMPORT_EXAM_STUDENT", name: "导入考生" },
+        { code: "IMPORT_EXAM_PAPER", name: "导入试卷" },
+        { code: "IMPORT_INVIGILATE_USER", name: "导入监考员" },
+        { code: "EXPORT_INVIGILATE_USER", name: "导出监考员" },
+      ],
+      selected: "",
+    };
+  },
+  async created() {},
+  watch: {
+    value: {
+      immediate: true,
+      handler(val) {
+        this.selected = val;
+      },
+    },
+  },
+  methods: {
+    select() {
+      this.$emit("input", this.selected);
+      this.$emit("change", this.selected);
+    },
+  },
+};
+</script>
+
+<style></style>

+ 5 - 0
src/constant/constants.js

@@ -8,3 +8,8 @@ export const DEVICE_ID = localStorage.getItem("deviceId");
 
 export const ORG_CODE =
   process.env.VUE_APP_ORG_CODE || window.location.hostname.split(".")[0];
+
+export const INVIGILATOR_IMPORT_TEMPLATE_DOWNLOAD_URL =
+  "/file/考场监考老师导入.xlsx";
+export const EXAM_STUDENT_IMPORT_TEMPLATE_DOWNLOAD_URL =
+  "/file/考生导入_在线考试.xlsx";

+ 239 - 0
src/features/examwork/ActivityManagement/ActivityEdit.vue

@@ -0,0 +1,239 @@
+<template>
+  <div>
+    <h2>{{ "新增" + "场次" }}</h2>
+
+    <el-row>
+      <el-col :span="3"> 考试名称: </el-col>
+      <el-col :span="21">
+        {{ exam.name }}
+      </el-col>
+    </el-row>
+    <el-row>
+      <el-col :span="3"> 考试时间段: </el-col>
+      <el-col :span="21">
+        {{ exam.startTime | datetimeFilter }} ~
+        {{ exam.endTime | datetimeFilter }}
+      </el-col>
+    </el-row>
+    <el-row>
+      <el-form
+        :model="item"
+        :ref="'form' + index"
+        :rules="rules"
+        label-position="right"
+        inline
+        v-for="(item, index) in form"
+        :key="index"
+      >
+        <div class="d-flex">
+          <el-form-item label="考试时间" prop="startTime">
+            <el-date-picker
+              v-model="item.startTime"
+              type="datetime"
+              placeholder="选择日期时间"
+              style="width: 188px;"
+            >
+            </el-date-picker>
+          </el-form-item>
+          <el-form-item label="交卷时间" prop="finishTime">
+            <el-date-picker
+              v-model="item.finishTime"
+              type="datetime"
+              placeholder="选择日期时间"
+              style="width: 188px;"
+            >
+            </el-date-picker>
+          </el-form-item>
+          <el-form-item label="考试时长" prop="prepareSeconds">
+            <MinuteInput
+              v-model="item.maxDurationSeconds"
+              style="width: 125px;"
+            />
+          </el-form-item>
+          <el-form-item label="候考时间" prop="prepareSeconds">
+            <MinuteInput v-model="item.prepareSeconds" style="width: 125px;" />
+          </el-form-item>
+          <el-form-item label="迟到时长" prop="prepareSeconds">
+            <MinuteInput v-model="item.openingSeconds" style="width: 125px;" />
+          </el-form-item>
+        </div>
+      </el-form>
+    </el-row>
+    <el-row class="d-flex justify-content-center">
+      <el-button type="primary" @click="submitForm">保 存</el-button>
+      <el-button type="primary" @click="addActivity">新 增</el-button>
+      <el-button @click="() => this.$router.back()">取 消</el-button>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { saveActivities } from "@/api/examwork-activity";
+import { getExamDetail } from "@/api/examwork-exam";
+import moment from "moment";
+
+export default {
+  name: "ActivityEdit",
+  computed: {
+    examId() {
+      return this.$route.params.examId;
+    },
+    activityId() {
+      return this.$route.params.activityId;
+    },
+    isEdit() {
+      return !!this.$route.params.activityId;
+    },
+  },
+  data() {
+    const that = this;
+    return {
+      form: [
+        {
+          id: "",
+          startTime: null,
+          finishTime: null,
+          prepareSeconds: 0,
+          openingSeconds: 0,
+          maxDurationSeconds: 0,
+        },
+      ],
+      rules: {
+        startTime: [
+          { required: true, message: "开始时间必填" },
+          {
+            validator(rule, value) {
+              return new Promise((resolve, reject) => {
+                if (
+                  moment(value).isBetween(
+                    moment(that.exam.startTime),
+                    moment(that.exam.endTime)
+                  )
+                ) {
+                  resolve(); // reject with error message
+                } else {
+                  reject("reject");
+                }
+              });
+            },
+            // type: "date",
+            // asyncValidator: (rule, value) => {
+            //   console.log(value);
+            //   return new Promise((resolve, reject) => {
+            //     if (
+            //       moment(value).isBetween(
+            //         moment(that.exam.startTime),
+            //         moment(that.exam.endTime)
+            //       )
+            //     ) {
+            //       resolve(); // reject with error message
+            //     } else {
+            //       reject();
+            //     }
+            //   });
+            // },
+            message: "场次的开始时间不在考试的时间范围",
+          },
+        ],
+        finishTime: [
+          { required: true, message: "交卷时间必填" },
+          {
+            validator(rule, value) {
+              return new Promise((resolve, reject) => {
+                if (
+                  moment(value).isBetween(
+                    moment(that.exam.startTime),
+                    moment(that.exam.endTime)
+                  )
+                ) {
+                  resolve(); // reject with error message
+                } else {
+                  reject("reject");
+                }
+              });
+            },
+            message: "场次的交卷时间不在考试的时间范围",
+          },
+        ],
+        maxDurationSeconds: [{ required: true, message: "考试时长必填" }],
+        prepareSeconds: [{ required: true, message: "候考时间必填" }],
+        openingSeconds: [{ required: true, message: "迟到时长必填" }],
+      },
+      exam: {},
+      activity: {},
+    };
+  },
+  async created() {
+    try {
+      this.exam = (await getExamDetail({ id: this.examId }))?.data.data;
+      this.form[0].prepareSeconds = this.exam.prepareSeconds;
+      this.form[0].openingSeconds = this.exam.openingSeconds;
+      this.form[0].maxDurationSeconds = this.exam.maxDurationSeconds;
+    } catch (error) {
+      console.log(error);
+      this.$notify({ type: "error", title: "获取考试详情失败" });
+    }
+
+    // if (this.isEdit) {
+    //   try {
+    //     this.activity = (
+    //       await getActivityDetail({ id: this.activityId })
+    //     )?.data.data.records[0];
+    //     this.form = this.activity;
+    //   } catch (error) {
+    //     console.log(error);
+    //     this.$notify({ type: "error", title: "获取场次详情失败" });
+    //   }
+    // } else {
+    // this.form = {
+    //   id: "",
+    //   startTime: null,
+    //   finishTime: null,
+    //   prepareSeconds: 0,
+    //   openingSeconds: 0,
+    //   maxDurationSeconds: 0,
+    //   enable: 0,
+    // };
+    // }
+  },
+  methods: {
+    addActivity() {
+      this.form.push({
+        id: "",
+        startTime: null,
+        finishTime: null,
+        prepareSeconds: this.exam.prepareSeconds,
+        openingSeconds: this.exam.openingSeconds,
+        maxDurationSeconds: this.exam.maxDurationSeconds,
+      });
+    },
+    async submitForm() {
+      let data = [];
+      for (let i = 0; i < this.form.length; i++) {
+        try {
+          const valid = await this.$refs["form" + i][0].validate();
+          data.push({
+            examId: this.examId,
+            startTime: new Date(this.form[i].startTime).valueOf(),
+            finishTime: new Date(this.form[i].finishTime).valueOf(),
+            prepareSeconds: this.form[i].prepareSeconds,
+            openingSeconds: this.form[i].openingSeconds,
+            maxDurationSeconds: this.form[i].maxDurationSeconds,
+            enable: 1,
+          });
+          console.log(data);
+          if (!valid) return;
+        } catch (error) {
+          console.log(error);
+          return;
+        }
+      }
+
+      await saveActivities(data);
+      this.$emit("reload");
+    },
+  },
+};
+</script>
+
+<style></style>

+ 16 - 6
src/features/examwork/ActivityManagement/ActivityManagement.vue

@@ -31,7 +31,9 @@
         }}</span>
       </el-table-column>
       <el-table-column width="100" label="结束时间">
-        <span slot-scope="scope">{{ scope.row.endTime | datetimeFilter }}</span>
+        <span slot-scope="scope">{{
+          scope.row.finishTime | datetimeFilter
+        }}</span>
       </el-table-column>
       <el-table-column width="120" label="更新人">
         <span slot-scope="scope">{{ scope.row.updateName }}</span>
@@ -52,7 +54,7 @@
             plain
             @click="toggleEnableActivity(scope.row)"
           >
-            {{ scope.row.enable ? "禁用" : "启用" }}
+            {{ !scope.row.enable | booleanEnableDisableFilter }}
           </el-button>
         </div>
       </el-table-column>
@@ -117,8 +119,8 @@ export default {
         pageNumber: this.currentPage,
         pageSize: this.pageSize,
       });
-      this.tableData = res.data.data.records.records;
-      this.total = res.data.data.records.total;
+      this.tableData = res.data.data.records;
+      this.total = res.data.data.total;
     },
     handleCurrentChange(val) {
       this.currentPage = val;
@@ -130,12 +132,20 @@ export default {
       this.searchForm();
     },
     add() {
-      this.selectedActivity = {};
-      this.$refs.theDialog.openDialog();
+      // this.selectedActivity = {};
+      // this.$refs.theDialog.openDialog();
+      this.$router.push({
+        name: "ActivityEdit",
+        params: { examId: this.examId },
+      });
     },
     edit(activity) {
       this.selectedActivity = activity;
       this.$refs.theDialog.openDialog();
+      // this.$router.push({
+      //   name: "ActivityEdit",
+      //   params: { examId: this.examId, activityId: activity.id },
+      // });
     },
     async toggleEnableActivity(activity) {
       await toggleEnableActivity({

+ 1 - 0
src/features/examwork/ActivityManagement/ActivityManagementDialog.vue

@@ -120,6 +120,7 @@ export default {
     },
     async submitForm() {
       let data = this.form;
+      data = { ...data, examId: this.examId };
       if (this.isEdit) {
         data = { ...data, id: this.activity.id };
       }

+ 49 - 37
src/features/examwork/CourseManagement/CourseManagement.vue

@@ -7,7 +7,7 @@
       <el-form-item label="科目名称">
         <CourseSelect :examId="form.examId" v-model="form.code" />
       </el-form-item>
-      <el-form-item label="科目名称">
+      <el-form-item label="状态">
         <StateSelect
           :options="[
             { code: 0, name: '未绑卷' },
@@ -17,7 +17,7 @@
         />
       </el-form-item>
       <el-button @click="searchForm">查询</el-button>
-      <el-button>导入</el-button>
+      <el-button @click="importPaper">导入</el-button>
     </el-form>
 
     <el-table :data="tableData" stripe style="width: 100%;">
@@ -25,24 +25,26 @@
       <el-table-column width="100" label="ID">
         <span slot-scope="scope">{{ scope.row.id }}</span>
       </el-table-column>
-      <el-table-column width="200" label="场次代码">
-        <span slot-scope="scope">{{ scope.row.code }}</span>
+      <el-table-column width="200" label="科目代码">
+        <span slot-scope="scope">{{ scope.row.courseCode }}</span>
       </el-table-column>
-      <el-table-column label="候考时间">
-        <span slot-scope="scope">{{ scope.row.prepareSeconds / 60 }}</span>
+      <el-table-column label="科目名称">
+        <span slot-scope="scope">{{ scope.row.courseName }}</span>
       </el-table-column>
       <el-table-column width="120" label="状态">
         <span slot-scope="scope">{{
-          scope.row.enable | zeroOneEnableDisableFilter
+          scope.row.hasPaper === 1 ? "已绑卷" : "未绑卷"
         }}</span>
       </el-table-column>
-      <el-table-column width="100" label="开始时间">
+      <el-table-column width="100" label="音频题">
         <span slot-scope="scope">{{
-          scope.row.startTime | datetimeFilter
+          scope.row.hasAudio === 1 ? "是" : "否"
         }}</span>
       </el-table-column>
-      <el-table-column width="100" label="结束时间">
-        <span slot-scope="scope">{{ scope.row.endTime | datetimeFilter }}</span>
+      <el-table-column width="100" label="提交答案">
+        <span slot-scope="scope">{{
+          scope.row.hasAnswer | zeroOneEnableDisableFilter
+        }}</span>
       </el-table-column>
       <el-table-column width="120" label="更新人">
         <span slot-scope="scope">{{ scope.row.updateName }}</span>
@@ -57,14 +59,6 @@
           <el-button size="mini" type="primary" plain @click="edit(scope.row)">
             编辑
           </el-button>
-          <el-button
-            size="mini"
-            type="primary"
-            plain
-            @click="toggleEnableActivity(scope.row)"
-          >
-            {{ scope.row.enable ? "禁用" : "启用" }}
-          </el-button>
         </div>
       </el-table-column>
     </el-table>
@@ -80,24 +74,31 @@
       />
     </div>
 
-    <!-- <ActivityManagementDialog
+    <PaperImportDialog
       ref="theDialog"
-      :examId="examId"
-      :activity="selectedActivity"
+      :examId="form.examId"
       @reload="searchForm"
-    /> -->
+    />
+
+    <CoursePaperDialog
+      ref="theDialog2"
+      :course="selectedCourse"
+      @reload="searchForm"
+    />
   </div>
 </template>
 
 <script>
 import { searchCourses } from "@/api/examwork-course";
-// import ActivityManagementDialog from "./ActivityManagementDialog";
+import PaperImportDialog from "./PaperImportDialog";
+import CoursePaperDialog from "./CoursePaperDialog";
 
 export default {
   name: "CourseManagement",
-  // components: {
-  //   ActivityManagementDialog,
-  // },
+  components: {
+    PaperImportDialog,
+    CoursePaperDialog,
+  },
   computed: {},
   data() {
     return {
@@ -113,14 +114,19 @@ export default {
       currentPage: 1,
       pageSize: 10,
       total: 10,
-      selectedActivity: {},
+      selectedCourse: {},
     };
   },
   async created() {},
   methods: {
     async searchForm() {
-      const valid = await this.$refs.form.validate();
-      if (!valid) return;
+      try {
+        const valid = await this.$refs.form.validate();
+        if (!valid) return;
+      } catch (error) {
+        console.log(error);
+        return;
+      }
       const res = await searchCourses({
         examId: this.form.examId,
         code: this.form.code,
@@ -128,8 +134,8 @@ export default {
         pageNumber: this.currentPage,
         pageSize: this.pageSize,
       });
-      this.tableData = res.data.data.records.records;
-      this.total = res.data.data.records.total;
+      this.tableData = res.data.data.records;
+      this.total = res.data.data.total;
     },
     handleCurrentChange(val) {
       this.currentPage = val;
@@ -140,13 +146,19 @@ export default {
       this.currentPage = 1;
       this.searchForm();
     },
-    add() {
-      this.selectedActivity = {};
+    async importPaper() {
+      try {
+        const valid = await this.$refs.form.validate();
+        if (!valid) return;
+      } catch (error) {
+        console.log(error);
+        return;
+      }
       this.$refs.theDialog.openDialog();
     },
-    edit(activity) {
-      this.selectedActivity = activity;
-      this.$refs.theDialog.openDialog();
+    edit(course) {
+      this.selectedCourse = course;
+      this.$refs.theDialog2.openDialog();
     },
   },
 };

+ 173 - 0
src/features/examwork/CourseManagement/CoursePaperDialog.vue

@@ -0,0 +1,173 @@
+<template>
+  <el-dialog
+    ref="dialog"
+    title="导入试卷"
+    width="800px"
+    :visible.sync="visible"
+    @close="closeDialog"
+  >
+    <el-form
+      :model="form"
+      ref="form"
+      :rules="rules"
+      label-position="right"
+      label-width="120px"
+      inline
+    >
+      <el-row>
+        <el-form-item label="批次名称">
+          <ExamSelect v-model="course.examId" disabled />
+        </el-form-item>
+        <el-form-item label="科目名称">
+          <CourseSelect
+            :examId="course.examId"
+            v-model="course.courseCode"
+            disabled
+          />
+        </el-form-item>
+      </el-row>
+      <el-row>
+        <el-table :data="papers" stripe style="width: 100%;">
+          <el-table-column width="42" />
+          <el-table-column width="100" label="ID">
+            <span slot-scope="scope">{{ scope.row.id }}</span>
+          </el-table-column>
+          <el-table-column label="试卷名称">
+            <span slot-scope="scope">{{ scope.row.name }}</span>
+          </el-table-column>
+          <el-table-column width="100" label="分值">
+            <span slot-scope="scope">{{ scope.row.totalScore }}</span>
+          </el-table-column>
+          <el-table-column width="170" label="抽卷几率">
+            <span slot-scope="scope">
+              <el-input-number v-model.trim="scope.row.weight">
+                <template slot="append">%</template></el-input-number
+              >
+            </span>
+          </el-table-column>
+          <el-table-column width="170" label="音频播放次数">
+            <span slot-scope="scope">
+              <el-input-number
+                v-model.trim="scope.row.audioPlayCount"
+              ></el-input-number>
+            </span>
+          </el-table-column>
+        </el-table>
+      </el-row>
+      <el-row>
+        <el-form-item label="客观题小题乱序" prop="objectiveShuffle">
+          <el-radio-group
+            class="pull_right_sm"
+            v-model="refreshCourse.objectiveShuffle"
+          >
+            <el-radio :label="1">启用</el-radio>
+            <el-radio :label="0">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-row>
+      <el-row>
+        <el-form-item label="客观题选项乱序" prop="optionShuffle">
+          <el-radio-group
+            class="pull_right_sm"
+            v-model="refreshCourse.optionShuffle"
+          >
+            <el-radio :label="1">启用</el-radio>
+            <el-radio :label="0">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-row>
+      <el-row class="d-flex justify-content-center">
+        <el-button type="primary" @click="submitForm">保 存</el-button>
+        <el-button @click="closeDialog">取 消</el-button>
+      </el-row>
+    </el-form>
+  </el-dialog>
+</template>
+
+<script>
+import {
+  searchCourses,
+  searchPapers,
+  saveCourse,
+  savePapers,
+} from "@/api/examwork-course";
+
+export default {
+  name: "CoursePaperDialog",
+  props: {
+    course: Object,
+  },
+  data() {
+    return {
+      visible: false,
+      form: {},
+      rules: {},
+      refreshCourse: {},
+      papers: [],
+    };
+  },
+  watch: {
+    course: {
+      immediate: true,
+      handler() {
+        this.refreshCourse = {};
+        this.papers = [];
+        this.initData();
+      },
+    },
+  },
+  methods: {
+    async initData() {
+      if (!this.course?.examId) return;
+      const courseRes = await searchCourses({
+        id: this.course.id,
+        examId: this.course.examId,
+        pageNumber: 1,
+        pageSize: 1,
+      });
+      this.refreshCourse = courseRes?.data.data.records[0];
+
+      const res = await searchPapers({
+        examId: this.refreshCourse.examId,
+        courseCode: this.refreshCourse.courseCode,
+        pageNumber: this.currentPage,
+        pageSize: this.pageSize,
+      });
+      this.papers = res?.data.data.records;
+    },
+    openDialog() {
+      this.visible = true;
+    },
+    closeDialog() {
+      this.visible = false;
+    },
+    async submitForm() {
+      try {
+        await saveCourse({
+          examId: this.refreshCourse.examId,
+          courseCode: this.refreshCourse.courseCode,
+          objectiveShuffle: this.refreshCourse.objectiveShuffle,
+          optionShuffle: this.refreshCourse.optionShuffle,
+        });
+        const ps = [];
+        for (const paper of this.papers) {
+          ps.push({
+            id: paper.id,
+            weight: paper.weight,
+            audioPlayCount: paper.audioPlayCount,
+          });
+        }
+        await savePapers(ps);
+        this.$emit("reload");
+        this.$notify({ title: "保存成功", type: "success" });
+        this.closeDialog();
+      } catch (error) {
+        this.initData();
+        this.$notify({ title: "保存失败", type: "warning" });
+      }
+    },
+  },
+};
+</script>
+
+<style></style>

+ 124 - 0
src/features/examwork/CourseManagement/PaperImportDialog.vue

@@ -0,0 +1,124 @@
+<template>
+  <el-dialog
+    ref="dialog"
+    title="导入试卷"
+    width="550px"
+    :visible.sync="visible"
+    @close="closeDialog"
+  >
+    <el-form
+      :model="form"
+      ref="form"
+      :rules="rules"
+      label-position="right"
+      label-width="120px"
+    >
+      <el-row>
+        <el-form-item label="选择文件">
+          <input @change="selectFile" type="file" /> *系统支持数据包
+        </el-form-item>
+      </el-row>
+      <el-row>
+        <el-form-item label="试卷包解析内容">
+          <el-checkbox v-model="form.processPaper">试卷</el-checkbox>
+          <el-checkbox v-model="form.processAnswer">答案</el-checkbox>
+        </el-form-item>
+      </el-row>
+      <el-row>
+        <el-form-item label="音频播放次数">
+          <el-input-number v-model.trim="form.audioPlayCount"></el-input-number>
+        </el-form-item>
+      </el-row>
+      <el-row>
+        <el-form-item label="客观题小题乱序" prop="objectiveShuffle">
+          <el-radio-group class="pull_right_sm" v-model="form.objectiveShuffle">
+            <el-radio :label="true">启用</el-radio>
+            <el-radio :label="false">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-row>
+      <el-row>
+        <el-form-item label="客观题选项乱序" prop="optionShuffle">
+          <el-radio-group class="pull_right_sm" v-model="form.optionShuffle">
+            <el-radio :label="true">启用</el-radio>
+            <el-radio :label="false">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-row>
+      <el-row class="d-flex justify-content-center">
+        <el-button type="primary" @click="submitForm">保 存</el-button>
+        <el-button @click="closeDialog">取 消</el-button>
+      </el-row>
+    </el-form>
+  </el-dialog>
+</template>
+
+<script>
+import MD5 from "js-md5";
+import { importPaper } from "@/api/examwork-task";
+
+export default {
+  name: "PaperImportDialog",
+  props: {
+    examId: String,
+  },
+  data() {
+    return {
+      visible: false,
+      form: {},
+      rules: {},
+    };
+  },
+  watch: {
+    examId: {
+      immediate: true,
+      handler() {
+        this.form = {
+          processPaper: false,
+          processAnswer: false,
+          objectiveShuffle: false,
+          optionShuffle: false,
+          audioPlayCount: 0,
+          file: "",
+          fileName: "",
+        };
+      },
+    },
+  },
+  methods: {
+    openDialog() {
+      this.visible = true;
+    },
+    closeDialog() {
+      this.visible = false;
+    },
+    selectFile(e) {
+      this.form.file = e.target.files[0];
+      this.form.fileName = this.form.file?.name;
+    },
+    async submitForm() {
+      async function blobToArray(blob) {
+        return new Promise((resolve) => {
+          var reader = new FileReader();
+          reader.addEventListener("loadend", function () {
+            // reader.result contains the contents of blob as a typed array
+            resolve(reader.result);
+          });
+          reader.readAsArrayBuffer(blob);
+        });
+      }
+      const ab = await blobToArray(this.form.file);
+      const md5 = MD5(ab);
+
+      let data = this.form;
+
+      await importPaper({ ...data, examId: this.examId, md5 });
+      this.$emit("reload");
+      this.$notify({ title: "导入任务已成功启动", type: "success" });
+      this.closeDialog();
+    },
+  },
+};
+</script>
+
+<style></style>

+ 63 - 52
src/features/examwork/ExamManagement/ExamEdit.vue

@@ -95,11 +95,8 @@
           </el-row>
           <el-row>
             <el-form-item label="取分策略">
-              <el-radio
-                v-model="form.recordSelectStrategy"
-                label="HIGHEST_TOTAL_SCORE"
-              >
-                全部阅卷后取最高分
+              <el-radio v-model="form.recordSelectStrategy" label="LATEST">
+                最后一次提交
               </el-radio>
               <el-radio
                 v-model="form.recordSelectStrategy"
@@ -107,8 +104,11 @@
               >
                 客观分最高
               </el-radio>
-              <el-radio v-model="form.recordSelectStrategy" label="LATEST">
-                最后一次提交
+              <el-radio
+                v-model="form.recordSelectStrategy"
+                label="HIGHEST_TOTAL_SCORE"
+              >
+                总分最高
               </el-radio>
             </el-form-item>
           </el-row>
@@ -206,15 +206,15 @@
           </el-row>
           <el-row>
             <el-form-item label="是否考中活体检测">
-              <el-radio v-model="form.inProcessLivenessVerify" :label="0"
+              <el-radio v-model="form.inProcessLivenessVerify" :label="1"
                 >是
               </el-radio>
-              <el-radio v-model="form.inProcessLivenessVerify" :label="1"
+              <el-radio v-model="form.inProcessLivenessVerify" :label="0"
                 >否
               </el-radio>
             </el-form-item>
           </el-row>
-          <el-row>
+          <el-row v-if="form.inProcessLivenessVerify">
             <el-form-item label="活体验证弹出时间段">
               <MinuteInput
                 v-model.trim="form.inProcessLivenessFixedRange[0]"
@@ -225,9 +225,12 @@
                 v-model.trim="form.inProcessLivenessFixedRange[1]"
                 style="width: 150px;"
               />
+              <span style="color: red;"
+                >*活体验证将该时间段内随机出现,且时间段设置不能超过冻结时间</span
+              >
             </el-form-item>
           </el-row>
-          <el-row>
+          <el-row v-if="form.inProcessLivenessVerify">
             <el-form-item label="活体验证结果的判定方案">
               <el-radio v-model="form.inProcessLivenessJudgePolicy" label="ANY">
                 单条成功则通过
@@ -241,48 +244,38 @@
               >
                 成功次数大于失败则通过
               </el-radio>
-              <h2>监考直播</h2>
-              <el-row>
-                <el-form-item label="是否开启考生端监考直播">
-                  <el-radio v-model="form.monitorProxy" :label="true"
-                    >是
-                  </el-radio>
-                  <el-radio v-model="form.monitorProxy" :label="false"
-                    >否
-                  </el-radio>
-                </el-form-item>
-              </el-row>
-              <el-row>
-                <el-form-item v-if="form.monitorProxy" label="是否需要视频转录">
-                  <el-radio v-model="form.monitorRecord" :label="1"
-                    >是
-                  </el-radio>
-                  <el-radio v-model="form.monitorRecord" :label="0"
-                    >否
-                  </el-radio>
-                </el-form-item>
-              </el-row>
-              <el-row>
-                <el-form-item
-                  v-if="form.monitorProxy"
-                  label="电脑&手机监控方案"
+            </el-form-item>
+          </el-row>
+          <h2>监考直播</h2>
+          <el-row>
+            <el-form-item label="是否开启考生端监考直播">
+              <el-radio v-model="form.monitorProxy" :label="true">是 </el-radio>
+              <el-radio v-model="form.monitorProxy" :label="false"
+                >否
+              </el-radio>
+            </el-form-item>
+          </el-row>
+          <el-row>
+            <el-form-item v-if="form.monitorProxy" label="是否需要视频转录">
+              <el-radio v-model="form.monitorRecord" :label="1">是 </el-radio>
+              <el-radio v-model="form.monitorRecord" :label="0">否 </el-radio>
+            </el-form-item>
+          </el-row>
+          <el-row>
+            <el-form-item v-if="form.monitorProxy" label="电脑&手机监控方案">
+              <el-checkbox-group v-model="form.monitorVideoSource">
+                <el-checkbox label="client_camera"
+                  >电脑摄像头为主机位</el-checkbox
+                >
+                <el-checkbox label="client_screen">电脑开启录频</el-checkbox>
+                <el-checkbox label="mobile_first">手机主机位</el-checkbox>
+                <el-checkbox
+                  :disabled="!form.monitorVideoSource.includes('mobile_first')"
+                  label="mobile_second"
+                  >手机辅机位</el-checkbox
                 >
-                  <el-checkbox-group v-model="form.monitorVideoSource">
-                    <el-checkbox label="client_camera"
-                      >电脑摄像头为主机位</el-checkbox
-                    >
-                    <el-checkbox label="client_screen"
-                      >电脑开启录频</el-checkbox
-                    >
-                    <el-checkbox label="mobile_first"
-                      >手机监考机位1</el-checkbox
-                    >
-                    <el-checkbox label="mobile_second"
-                      >手机监考机位2</el-checkbox
-                    >
-                  </el-checkbox-group>
-                </el-form-item>
-              </el-row>
+              </el-checkbox-group>
+              <span style="color: red;">*主机位设备负责收音及播放监考提示</span>
             </el-form-item>
           </el-row>
         </el-form>
@@ -363,6 +356,24 @@ export default {
         }
       },
     },
+    "form.monitorVideoSource": {
+      immediate: true,
+      handler(v, ov) {
+        if (!v) {
+          this.form.monitorVideoSource = [];
+        }
+        if (
+          // 没动静,不修改,避免死循环
+          (v || []).includes("mobile_first") !==
+            (ov || []).includes("mobile_first") &&
+          !this.form.monitorVideoSource.includes("mobile_first")
+        ) {
+          this.form.monitorVideoSource = this.form.monitorVideoSource.filter(
+            (v) => v !== "mobile_second"
+          );
+        }
+      },
+    },
   },
   async created() {
     if (this.isEdit) {

+ 4 - 3
src/features/examwork/ExamManagement/ExamManagement.vue

@@ -63,7 +63,7 @@
             plain
             @click="toggleEnableExam(scope.row)"
           >
-            {{ scope.row.enable ? "禁用" : "启用" }}
+            {{ !scope.row.enable | booleanEnableDisableFilter }}
           </el-button>
           <el-button size="mini" type="primary" plain @click="edit(scope.row)">
             编辑
@@ -74,6 +74,7 @@
             type="primary"
             plain
             @click="editActivities(scope.row)"
+            v-if="scope.row.mode === 'TOGETHER'"
           >
             场次设置
           </el-button>
@@ -155,8 +156,8 @@ export default {
         pageNumber: this.currentPage,
         pageSize: this.pageSize,
       });
-      this.tableData = res.data.data.records.records;
-      this.total = res.data.data.records.total;
+      this.tableData = res.data.data.records;
+      this.total = res.data.data.total;
     },
     handleCurrentChange(val) {
       this.currentPage = val;

+ 47 - 14
src/features/examwork/ExamStudentImport/ExamStudentImport.vue

@@ -1,12 +1,14 @@
 <template>
   <div>
     <!-- 下载链接 -->
-    <el-form :model="form" inline>
+    <el-form ref="form" :model="form" :rules="rules" inline>
       <el-form-item label="批次名称" prop="examId">
         <ExamSelect v-model="form.examId" />
       </el-form-item>
-      <el-button @click="add">导入</el-button>
+      <el-button @click="searchForm">查询</el-button>
+      <el-button @click="importFile">导入</el-button>
       <!-- <el-button>导入</el-button> -->
+      <a :href="downloadUrl" download class="mx-2">下载模板</a>
     </el-form>
 
     <el-table :data="tableData" stripe style="width: 100%;">
@@ -37,7 +39,13 @@
             size="mini"
             type="primary"
             plain
-            @click="download({ id: scope.row.id, fileType: 'IMPORTFILE' })"
+            v-if="scope.row.status === 'FINISH'"
+            @click="
+              download({
+                id: scope.row.id,
+                type: scope.row.hasResultFile === '1' ? 'RESULT' : 'IMPORTFILE',
+              })
+            "
           >
             下载文件
           </el-button>
@@ -45,7 +53,8 @@
             size="mini"
             type="primary"
             plain
-            @click="download({ id: scope.row.id, fileType: 'ERROR' })"
+            v-if="scope.row.hasReportFile === '1'"
+            @click="download({ id: scope.row.id, type: 'REPORT' })"
           >
             导出报告
           </el-button>
@@ -63,12 +72,21 @@
         :total="total"
       />
     </div>
+
+    <ExamStudentImportDialog
+      ref="theDialog"
+      :examId="form.examId"
+      @reload="searchForm"
+    />
   </div>
 </template>
 
 <script>
-import { searchTasks, downloadFile } from "@/api/examwork-task";
+import { searchTasks } from "@/api/examwork-task";
 import { downloadFileURL } from "@/utils/utils";
+import ExamStudentImportDialog from "./ExamStudentImportDialog";
+import { EXAM_STUDENT_IMPORT_TEMPLATE_DOWNLOAD_URL } from "@/constant/constants";
+import { downloadFile } from "@/api/system-info";
 
 export default {
   name: "ExamStudentImport",
@@ -84,21 +102,27 @@ export default {
       currentPage: 1,
       pageSize: 10,
       total: 10,
-      selectedActivity: {},
+      downloadUrl: EXAM_STUDENT_IMPORT_TEMPLATE_DOWNLOAD_URL,
     };
   },
-  created() {
-    this.searchForm();
-  },
+  components: { ExamStudentImportDialog },
   methods: {
     async searchForm() {
+      // try {
+      //   const valid = await this.$refs.form.validate();
+      //   if (!valid) return;
+      // } catch (error) {
+      //   console.log(error);
+      //   return;
+      // }
       const res = await searchTasks({
         type: "IMPORT_EXAM_STUDENT",
+        entityId: this.form.examId,
         pageNumber: this.currentPage,
         pageSize: this.pageSize,
       });
-      this.tableData = res.data.data.records.records;
-      this.total = res.data.data.records.total;
+      this.tableData = res.data.data.records;
+      this.total = res.data.data.total;
     },
     handleCurrentChange(val) {
       this.currentPage = val;
@@ -109,9 +133,18 @@ export default {
       this.currentPage = 1;
       this.searchForm();
     },
-    add() {},
-    async download({ id, fileType }) {
-      const res = await downloadFile({ id, fileType });
+    async importFile() {
+      try {
+        const valid = await this.$refs.form.validate();
+        if (!valid) return;
+      } catch (error) {
+        console.log(error);
+        return;
+      }
+      this.$refs.theDialog.openDialog();
+    },
+    async download({ id, type }) {
+      const res = await downloadFile({ id, type });
       const url = res.data.data.url;
       downloadFileURL(url);
     },

+ 93 - 0
src/features/examwork/ExamStudentImport/ExamStudentImportDialog.vue

@@ -0,0 +1,93 @@
+<template>
+  <el-dialog
+    ref="dialog"
+    :title="'导入' + '考生'"
+    width="450px"
+    :visible.sync="visible"
+    @close="closeDialog"
+  >
+    <el-form
+      :model="form"
+      ref="form"
+      :rules="rules"
+      label-position="right"
+      label-width="120px"
+    >
+      <el-row>
+        <el-form-item label="选择文件">
+          <input @change="selectFile" type="file" />
+        </el-form-item>
+      </el-row>
+      <el-row class="d-flex justify-content-center">
+        <el-button type="primary" @click="submitForm">导入</el-button>
+        <el-button @click="closeDialog">取消</el-button>
+      </el-row>
+    </el-form>
+  </el-dialog>
+</template>
+
+<script>
+import { importExamStudent } from "@/api/examwork-task";
+import MD5 from "js-md5";
+
+export default {
+  name: "ExamStudentImportDialog",
+  props: {
+    examId: String,
+    examStudent: Object,
+  },
+  computed: {
+    isEdit() {
+      return this.examStudent.id;
+    },
+  },
+  data() {
+    return {
+      visible: false,
+      form: {
+        file: "",
+        fileName: "",
+      },
+      rules: {},
+    };
+  },
+  methods: {
+    openDialog() {
+      this.visible = true;
+    },
+    closeDialog() {
+      this.visible = false;
+    },
+    selectFile(e) {
+      this.form.file = e.target.files[0];
+      this.form.fileName = this.form.file?.name;
+    },
+    async submitForm() {
+      async function blobToArray(blob) {
+        return new Promise((resolve) => {
+          var reader = new FileReader();
+          reader.addEventListener("loadend", function () {
+            // reader.result contains the contents of blob as a typed array
+            resolve(reader.result);
+          });
+          reader.readAsArrayBuffer(blob);
+        });
+      }
+      const ab = await blobToArray(this.form.file);
+      const md5 = MD5(ab);
+
+      await importExamStudent({
+        examId: this.examId,
+        file: this.form.file,
+        fileName: this.form.fileName,
+        md5,
+      });
+      this.$emit("reload");
+      this.$notify({ title: "导入任务已成功启动", type: "success" });
+      this.closeDialog();
+    },
+  },
+};
+</script>
+
+<style></style>

+ 11 - 7
src/features/examwork/ExamStudentManagement/ExamStudentManagement.vue

@@ -8,7 +8,7 @@
         <ActivitySelect :examId="form.examId" v-model="form.activityId" />
       </el-form-item>
       <el-form-item label="考场名称">
-        <el-input v-model.trim="form.roomCode"></el-input>
+        <ExamRoomSelect v-model="form.roomCode" />
       </el-form-item>
       <el-form-item label="科目">
         <CourseSelect :examId="form.examId" v-model="form.courseCode" />
@@ -84,7 +84,7 @@
             plain
             @click="toggleEnableExamStudent(scope.row)"
           >
-            {{ scope.row.enable ? "禁用" : "启用" }}
+            {{ !scope.row.enable | booleanEnableDisableFilter }}
           </el-button>
         </div>
       </el-table-column>
@@ -153,16 +153,20 @@ export default {
   async created() {},
   methods: {
     async searchForm() {
-      // console.log(this.form);
-      const valid = await this.$refs.form.validate();
-      if (!valid) return;
+      try {
+        const valid = await this.$refs.form.validate();
+        if (!valid) return;
+      } catch (error) {
+        console.log(error);
+        return;
+      }
       const res = await searchExamStudents({
         ...this.form,
         pageNumber: this.currentPage,
         pageSize: this.pageSize,
       });
-      this.tableData = res.data.data.records.records;
-      this.total = res.data.data.records.total;
+      this.tableData = res.data.data.records;
+      this.total = res.data.data.total;
     },
     handleCurrentChange(val) {
       this.currentPage = val;

+ 19 - 4
src/features/examwork/ExamStudentManagement/ExamStudentManagementDialog.vue

@@ -15,22 +15,30 @@
     >
       <el-row>
         <el-form-item label="批次名称" prop="examId">
-          <ExamSelect v-model="form.examId" />
+          <ExamSelect v-model="form.examId" styles="width: 100%" />
         </el-form-item>
       </el-row>
       <el-row>
         <el-form-item label="场次代码">
-          <ActivitySelect :examId="form.examId" v-model="form.activityId" />
+          <ActivitySelect
+            :examId="form.examId"
+            v-model="form.examActivityId"
+            styles="width: 100%"
+          />
         </el-form-item>
       </el-row>
       <el-row>
         <el-form-item label="考场名称">
-          <el-input v-model.trim="form.roomCode"></el-input>
+          <ExamRoomSelect v-model="form.roomCode" styles="width: 100%" />
         </el-form-item>
       </el-row>
       <el-row>
         <el-form-item label="科目">
-          <CourseSelect :examId="form.examId" v-model="form.courseCode" />
+          <CourseSelect
+            :examId="form.examId"
+            v-model="form.courseCode"
+            styles="width: 100%"
+          />
         </el-form-item>
       </el-row>
       <el-row>
@@ -126,6 +134,13 @@ export default {
       this.visible = false;
     },
     async submitForm() {
+      try {
+        const valid = await this.$refs.form.validate();
+        if (!valid) return;
+      } catch (error) {
+        console.log(error);
+        return;
+      }
       let data = this.form;
       if (this.isEdit) {
         data = { ...data, id: this.examStudent.id };

+ 129 - 0
src/features/examwork/ImportExportTask/ImportExportTask.vue

@@ -0,0 +1,129 @@
+<template>
+  <div>
+    <!-- 下载链接 -->
+    <el-form ref="form" :model="form" :rules="rules" inline>
+      <el-form-item label="任务名称" prop="type">
+        <TaskSelect v-model="form.type" />
+      </el-form-item>
+      <el-button @click="searchForm">查询</el-button>
+    </el-form>
+
+    <el-table :data="tableData" stripe style="width: 100%;">
+      <el-table-column type="selection" width="42" />
+      <el-table-column width="100" label="ID">
+        <span slot-scope="scope">{{ scope.row.id }}</span>
+      </el-table-column>
+      <el-table-column width="150" label="任务名称">
+        <span slot-scope="scope">{{ scope.row.entityName }}</span>
+      </el-table-column>
+      <el-table-column width="150" label="上传文件名">
+        <span slot-scope="scope">{{ scope.row.importFileName }}</span>
+      </el-table-column>
+      <el-table-column width="120" label="状态">
+        <span slot-scope="scope">{{ scope.row.status }}</span>
+      </el-table-column>
+      <el-table-column label="异常">
+        <span slot-scope="scope">{{ scope.row.summary }}</span>
+      </el-table-column>
+      <el-table-column width="100" label="上传时间">
+        <span slot-scope="scope">{{
+          scope.row.createTime | datetimeFilter
+        }}</span>
+      </el-table-column>
+      <el-table-column :context="_self" label="操作" width="210">
+        <div slot-scope="scope">
+          <el-button
+            size="mini"
+            type="primary"
+            plain
+            v-if="scope.row.status === 'FINISH'"
+            @click="
+              download({
+                id: scope.row.id,
+                type: scope.row.hasResultFile === '1' ? 'RESULT' : 'IMPORTFILE',
+              })
+            "
+          >
+            下载文件
+          </el-button>
+          <el-button
+            size="mini"
+            type="primary"
+            plain
+            v-if="scope.row.hasReportFile === '1'"
+            @click="download({ id: scope.row.id, type: 'REPORT' })"
+          >
+            导出报告
+          </el-button>
+        </div>
+      </el-table-column>
+    </el-table>
+    <div class="page float-right">
+      <el-pagination
+        @current-change="handleCurrentChange"
+        :current-page="currentPage"
+        :page-size="pageSize"
+        :page-sizes="[10, 20, 50, 100, 200, 300]"
+        @size-change="handleSizeChange"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="total"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+import { searchTasks } from "@/api/examwork-task";
+import { downloadFileURL } from "@/utils/utils";
+import { downloadFile } from "@/api/system-info";
+
+export default {
+  name: "ImportExportTask",
+  data() {
+    return {
+      form: {
+        type: "",
+      },
+      rules: {},
+      tableData: [],
+      currentPage: 1,
+      pageSize: 10,
+      total: 10,
+    };
+  },
+  methods: {
+    async searchForm() {
+      // try {
+      //   const valid = await this.$refs.form.validate();
+      //   if (!valid) return;
+      // } catch (error) {
+      //   console.log(error);
+      //   return;
+      // }
+      const res = await searchTasks({
+        type: this.form.type,
+        pageNumber: this.currentPage,
+        pageSize: this.pageSize,
+      });
+      this.tableData = res.data.data.records;
+      this.total = res.data.data.total;
+    },
+    handleCurrentChange(val) {
+      this.currentPage = val;
+      this.searchForm();
+    },
+    handleSizeChange(val) {
+      this.pageSize = val;
+      this.currentPage = 1;
+      this.searchForm();
+    },
+    async download({ id, type }) {
+      const res = await downloadFile({ id, type });
+      const url = res.data.data.url;
+      downloadFileURL(url);
+    },
+  },
+};
+</script>
+
+<style></style>

+ 83 - 0
src/features/examwork/InvigilateManagement/InvigilateImportDialog.vue

@@ -0,0 +1,83 @@
+<template>
+  <el-dialog
+    ref="dialog"
+    :title="'导入' + '监考设置'"
+    width="450px"
+    :visible.sync="visible"
+    @close="closeDialog"
+  >
+    <el-form
+      :model="form"
+      ref="form"
+      :rules="rules"
+      label-position="right"
+      label-width="120px"
+    >
+      <el-row>
+        <el-form-item label="选择文件">
+          <input @change="selectFile" type="file" />
+        </el-form-item>
+      </el-row>
+      <el-row class="d-flex justify-content-center">
+        <el-button type="primary" @click="submitForm">导入</el-button>
+        <el-button @click="closeDialog">取消</el-button>
+      </el-row>
+    </el-form>
+  </el-dialog>
+</template>
+
+<script>
+import { importInvigilator } from "@/api/examwork-task";
+import MD5 from "js-md5";
+
+export default {
+  name: "InvigilateImportDialog",
+  data() {
+    return {
+      visible: false,
+      form: {
+        file: "",
+        fileName: "",
+      },
+      rules: {},
+    };
+  },
+  methods: {
+    openDialog() {
+      this.visible = true;
+    },
+    closeDialog() {
+      this.visible = false;
+    },
+    selectFile(e) {
+      this.form.file = e.target.files[0];
+      this.form.fileName = this.form.file?.name;
+    },
+    async submitForm() {
+      async function blobToArray(blob) {
+        return new Promise((resolve) => {
+          var reader = new FileReader();
+          reader.addEventListener("loadend", function () {
+            // reader.result contains the contents of blob as a typed array
+            resolve(reader.result);
+          });
+          reader.readAsArrayBuffer(blob);
+        });
+      }
+      const ab = await blobToArray(this.form.file);
+      const md5 = MD5(ab);
+
+      await importInvigilator({
+        file: this.form.file,
+        fileName: this.form.fileName,
+        md5,
+      });
+      this.$emit("reload");
+      this.$notify({ title: "导入任务已成功启动", type: "success" });
+      this.closeDialog();
+    },
+  },
+};
+</script>
+
+<style></style>

+ 31 - 32
src/features/examwork/InvigilateManagement/InvigilateManagement.vue

@@ -4,20 +4,16 @@
       <!-- <el-form-item v-if="$store.state.user.orgId === null" label="机构">
         <OrgSelect v-model="form.orgId"></OrgSelect>
       </el-form-item> -->
-      <el-form-item label="角色">
-        <RoleSelect v-model="form.roleCode"></RoleSelect>
+      <el-form-item label="考场">
+        <ExamRoomSelect v-model="form.roomCode"></ExamRoomSelect>
       </el-form-item>
-      <el-form-item label="登录名">
-        <el-input v-model.trim="form.loginName"></el-input>
-      </el-form-item>
-      <el-form-item label="姓名">
-        <el-input v-model.trim="form.name"></el-input>
-      </el-form-item>
-      <el-form-item label="状态">
-        <StateSelect v-model="form.enableState"></StateSelect>
+      <el-form-item label="监考老师">
+        <InvigilatorSelect v-model="form.userId"></InvigilatorSelect>
       </el-form-item>
       <el-button @click="searchForm">查询</el-button>
-      <el-button @click="add">新增</el-button>
+      <el-button @click="importDialog">导入考场设置</el-button>
+      <el-button @click="exportInvigilate">导出考场安排</el-button>
+      <a :href="downloadUrl" download class="mx-2">下载导入模板</a>
       <!-- <el-button>导入</el-button> -->
     </el-form>
 
@@ -60,53 +56,53 @@
       :user="selectedUser"
       @reload="searchForm"
     />
+
+    <InvigilateImportDialog
+      ref="theDialog2"
+      :examId="form.examId"
+      @reload="searchForm"
+    />
   </div>
 </template>
 
 <script>
-import RoleSelect from "@/components/RoleSelect.vue";
-import StateSelect from "@/components/StateSelect";
 import InvigilateManagementDialog from "./InvigilateManagementDialog";
 import { searchInvigilators } from "@/api/examwork-invigilate";
+import { INVIGILATOR_IMPORT_TEMPLATE_DOWNLOAD_URL } from "@/constant/constants";
+import InvigilateImportDialog from "./InvigilateImportDialog";
+import { exportInvigilate } from "@/api/examwork-task";
 
 export default {
   name: "InvigilateManagement",
   components: {
-    RoleSelect,
-    StateSelect,
-    // OrgSelect,
+    InvigilateImportDialog,
     InvigilateManagementDialog,
   },
   data() {
     return {
       form: {
-        orgId: "",
-        roleCode: "",
-        loginName: "",
-        name: "",
-        enableState: null,
+        roomCode: "",
+        userId: "",
       },
       tableData: [],
       currentPage: 1,
       pageSize: 10,
       total: 10,
       selectedUser: {},
+      downloadUrl: INVIGILATOR_IMPORT_TEMPLATE_DOWNLOAD_URL,
     };
   },
   async created() {},
   methods: {
     async searchForm() {
       const res = await searchInvigilators({
-        orgId: this.form.orgId,
-        role: this.form.roleCode,
-        enable: this.form.enableState,
-        loginName: this.form.loginName,
-        name: this.form.name,
+        userId: this.form.userId,
+        roomCode: this.form.roomCode,
         pageNumber: this.currentPage,
         pageSize: this.pageSize,
       });
-      this.tableData = res.data.data.records.records;
-      this.total = res.data.data.records.total;
+      this.tableData = res.data.data.records;
+      this.total = res.data.data.total;
     },
     handleCurrentChange(val) {
       this.currentPage = val;
@@ -117,14 +113,17 @@ export default {
       this.currentPage = 1;
       this.searchForm();
     },
-    add() {
-      this.selectedUser = {};
-      this.$refs.theDialog.openDialog();
-    },
     edit(user) {
       this.selectedUser = user;
       this.$refs.theDialog.openDialog();
     },
+    importDialog() {
+      this.$refs.theDialog2.openDialog();
+    },
+    exportInvigilate() {
+      exportInvigilate();
+      this.$notify({ title: "导入任务已成功启动", type: "success" });
+    },
   },
 };
 </script>

+ 26 - 79
src/features/examwork/InvigilateManagement/InvigilateManagementDialog.vue

@@ -1,7 +1,7 @@
 <template>
   <el-dialog
     ref="dialog"
-    :title="(isEdit ? '编辑' : '新增') + '用户'"
+    title="考场监考管理"
     width="450px"
     :visible.sync="visible"
     @close="closeDialog"
@@ -14,59 +14,21 @@
       label-width="120px"
     >
       <el-row>
-        <el-form-item v-if="$store.state.user.orgId === null" label="机构">
-          <OrgSelect v-model="form.orgId"></OrgSelect>
+        <el-form-item label="考场">
+          <ExamRoomSelect
+            v-model="form.roomCode"
+            style="width: 100%;"
+          ></ExamRoomSelect>
         </el-form-item>
-      </el-row>
-      <el-row>
-        <el-form-item label="登录名" prop="loginName">
-          <el-input
-            class="pull_length"
-            v-model="form.loginName"
-            placeholder="登录名"
+        <el-form-item label="考场">
+          <InvigilatorSelect
+            v-model="form.userIds"
+            style="width: 100%;"
+            multiple
           />
         </el-form-item>
       </el-row>
-      <el-row>
-        <el-form-item label="姓名" prop="name">
-          <el-input
-            class="pull_length"
-            v-model="form.name"
-            placeholder="姓名"
-          />
-        </el-form-item>
-      </el-row>
-      <el-row>
-        <el-form-item label="密码" prop="password">
-          <el-input
-            class="pull_length"
-            v-model="form.password"
-            placeholder="密码"
-          />
-        </el-form-item>
-      </el-row>
-      <el-row>
-        <el-form-item label="角色" prop="roleCode">
-          <RoleSelect v-model="form.roleCode" multiple />
-        </el-form-item>
-      </el-row>
-      <el-row>
-        <el-form-item label="手机号" prop="mobileNumber">
-          <el-input
-            class="pull_length"
-            v-model="form.mobileNumber"
-            placeholder="联系方式"
-          />
-        </el-form-item>
-      </el-row>
-      <el-row>
-        <el-form-item label="状态" prop="enable">
-          <el-radio-group class="pull_right_sm" v-model="form.enable">
-            <el-radio :label="1">启用</el-radio>
-            <el-radio :label="0">禁用</el-radio>
-          </el-radio-group>
-        </el-form-item>
-      </el-row>
+
       <el-row class="d-flex justify-content-center">
         <el-button type="primary" @click="submitForm">保 存</el-button>
         <el-button @click="closeDialog">取 消</el-button>
@@ -76,44 +38,32 @@
 </template>
 
 <script>
-import RoleSelect from "@/components/RoleSelect";
-import { saveUser } from "@/api/system-user";
-import OrgSelect from "@/components/OrgSelect";
+import { saveInvigilator } from "@/api/examwork-invigilate";
 export default {
   name: "InvigilateManagementDialog",
-  components: { RoleSelect, OrgSelect },
   props: {
     user: Object,
   },
-  computed: {
-    isEdit() {
-      return this.user.id;
+  watch: {
+    user: {
+      immediate: true,
+      handler(val) {
+        this.form.roomCode = val.roomCode;
+        const u = val.userId || "";
+        this.form.userIds = u.split(",");
+      },
     },
   },
   data() {
     return {
       visible: false,
-      form: {},
+      form: {
+        roomCode: "",
+        userIds: [],
+      },
       rules: {},
     };
   },
-  watch: {
-    user(val) {
-      let tmp = { ...val };
-      if (!tmp.id) {
-        tmp = {
-          orgId: this.$store.state.user.orgId,
-          name: "",
-          loginName: "",
-          password: "",
-          mobileNumber: "",
-          roleCode: [],
-          enable: 1,
-        };
-      }
-      this.form = tmp;
-    },
-  },
   methods: {
     openDialog() {
       this.visible = true;
@@ -123,10 +73,7 @@ export default {
     },
     async submitForm() {
       let data = this.form;
-      if (this.isEdit) {
-        data = { ...data, id: this.user.id };
-      }
-      await saveUser(data);
+      await saveInvigilator(data);
       this.$emit("reload");
       this.closeDialog();
     },

+ 46 - 11
src/features/examwork/StudentManagement/StudentManagement.vue

@@ -33,7 +33,18 @@
         }}</span>
       </el-table-column>
       <el-table-column width="120" label="照片">
-        <span slot-scope="scope">{{ scope.row.basePhotoPath }}</span>
+        <span slot-scope="scope">
+          <el-button
+            size="mini"
+            type="primary"
+            plain
+            @click="openBasePhotoDialog(scope.row.basePhotoPath)"
+            v-if="scope.row.basePhotoPath"
+          >
+            查看底照
+          </el-button>
+          <span v-else>无底照</span>
+        </span>
       </el-table-column>
       <el-table-column width="120" label="更新人">
         <span slot-scope="scope">{{ scope.row.updateName }}</span>
@@ -45,7 +56,12 @@
       </el-table-column>
       <el-table-column :context="_self" label="操作" width="260">
         <div slot-scope="scope">
-          <el-button size="mini" type="primary" plain @click="edit(scope.row)">
+          <el-button
+            size="mini"
+            type="primary"
+            plain
+            @click="openExamRecord(scope.row)"
+          >
             考试记录
           </el-button>
           <el-button
@@ -62,7 +78,7 @@
             plain
             @click="toggleEnableStudent(scope.row)"
           >
-            {{ scope.row.enable ? "禁用" : "启用" }}
+            {{ !scope.row.enable | booleanEnableDisableFilter }}
           </el-button>
         </div>
       </el-table-column>
@@ -78,6 +94,16 @@
         :total="total"
       />
     </div>
+
+    <el-dialog
+      title="底照"
+      :visible.sync="basePhotoDialogVisible"
+      destroy-on-close
+    >
+      <img :src="selectedBasePhoto" style="width: 400px;" />
+    </el-dialog>
+
+    <StudentManagementDialog ref="theDialog" :student="selectedStudent" />
   </div>
 </template>
 
@@ -87,6 +113,7 @@ import {
   toggleEnableStudent,
   resetStudentPassword,
 } from "@/api/examwork-student";
+import StudentManagementDialog from "./StudentManagementDialog";
 
 export default {
   name: "StudentManagement",
@@ -101,9 +128,12 @@ export default {
       currentPage: 1,
       pageSize: 10,
       total: 10,
-      selectedUser: {},
+      basePhotoDialogVisible: false,
+      selectedBasePhoto: null,
+      selectedStudent: {},
     };
   },
+  components: { StudentManagementDialog },
   async created() {},
   methods: {
     async searchForm() {
@@ -113,8 +143,13 @@ export default {
         pageNumber: this.currentPage,
         pageSize: this.pageSize,
       });
-      this.tableData = res.data.data.records.records;
-      this.total = res.data.data.records.total;
+      this.tableData = res.data.data.records;
+      // this.tableData.forEach(
+      //   (v) =>
+      //     (v.basePhotoPath =
+      //       "https://ecs-test-static.qmth.com.cn/org_logo/0/1597046412749.png")
+      // );
+      this.total = res.data.data.total;
     },
     handleCurrentChange(val) {
       this.currentPage = val;
@@ -125,12 +160,12 @@ export default {
       this.currentPage = 1;
       this.searchForm();
     },
-    add() {
-      this.selectedUser = {};
-      this.$refs.theDialog.openDialog();
+    openBasePhotoDialog(url) {
+      this.selectedBasePhoto = url;
+      this.basePhotoDialogVisible = true;
     },
-    edit(user) {
-      this.selectedUser = user;
+    openExamRecord(user) {
+      this.selectedStudent = user;
       this.$refs.theDialog.openDialog();
     },
     async toggleEnableStudent(user) {

+ 121 - 0
src/features/examwork/StudentManagement/StudentManagementDialog.vue

@@ -0,0 +1,121 @@
+<template>
+  <el-dialog
+    ref="dialog"
+    title="考试记录"
+    width="600px"
+    :visible.sync="visible"
+    @close="closeDialog"
+  >
+    <el-form
+      :model="form"
+      ref="form"
+      :rules="rules"
+      label-position="right"
+      label-width="120px"
+      inline
+    >
+      <el-form-item label="批次名称" prop="examId">
+        <ExamSelect v-model="form.examId" />
+      </el-form-item>
+      <el-button type="primary" @click="searchForm">查询</el-button>
+    </el-form>
+
+    <el-table :data="tableData" stripe style="width: 100%;">
+      <el-table-column width="100" label="姓名">
+        <span slot-scope="scope">{{ scope.row.name }}</span>
+      </el-table-column>
+      <el-table-column width="180" label="证件号">
+        <span slot-scope="scope">{{ scope.row.identity }}</span>
+      </el-table-column>
+      <el-table-column label="批次名称">
+        <span slot-scope="scope">{{ scope.row.examName }}</span>
+      </el-table-column>
+      <el-table-column width="100" label="课程">
+        <span slot-scope="scope">{{ scope.row.courseName }}</span>
+      </el-table-column>
+      <el-table-column width="100" label="状态">
+        <span slot-scope="scope">{{ scope.row.status }}</span>
+      </el-table-column>
+    </el-table>
+    <div class="page float-right">
+      <el-pagination
+        @current-change="handleCurrentChange"
+        :current-page="currentPage"
+        :page-size="pageSize"
+        :page-sizes="[10, 20, 50, 100, 200, 300]"
+        @size-change="handleSizeChange"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="total"
+      />
+    </div>
+    <div class="my-2"></div>
+  </el-dialog>
+</template>
+
+<script>
+import { searchStudentExamRecord } from "@/api/examwork-student";
+export default {
+  name: "StudentManagementDialog",
+  props: {
+    student: Object,
+  },
+  watch: {
+    student() {
+      this.tableData = [];
+      this.pageSize = 10;
+      this.total = 0;
+    },
+  },
+  data() {
+    return {
+      visible: false,
+      form: {
+        examId: "",
+      },
+      rules: {
+        examId: [{ required: true, message: "批次必选" }],
+      },
+      tableData: [],
+      currentPage: 1,
+      pageSize: 10,
+      total: 10,
+    };
+  },
+  methods: {
+    openDialog() {
+      this.visible = true;
+    },
+    closeDialog() {
+      this.visible = false;
+    },
+    handleCurrentChange(val) {
+      this.currentPage = val;
+      this.searchForm();
+    },
+    handleSizeChange(val) {
+      this.pageSize = val;
+      this.currentPage = 1;
+      this.searchForm();
+    },
+    async searchForm() {
+      try {
+        const valid = await this.$refs.form.validate();
+        if (!valid) return;
+      } catch (error) {
+        console.log(error);
+        return;
+      }
+      const res = await searchStudentExamRecord({
+        examId: this.form.examId,
+        studentId: this.student.id,
+        pageNumber: this.currentPage,
+        pageSize: this.pageSize,
+      });
+      this.tableData = res.data.data.records;
+      this.total = res.data.data.total;
+    },
+  },
+};
+</script>
+
+<style></style>

+ 2 - 2
src/features/system/OrgManagement/OrgManagement.vue

@@ -56,7 +56,7 @@
             plain
             @click="toggleEnableOrg(scope.row)"
           >
-            {{ scope.row.enable ? "禁用" : "启用" }}
+            {{ !scope.row.enable | booleanEnableDisableFilter }}
           </el-button>
         </div>
       </el-table-column>
@@ -114,7 +114,7 @@ export default {
           pageNumber: this.currentPage,
           pageSize: this.pageSize,
         })
-      ).data.data.records.records;
+      ).data.data.records;
     },
     handleCurrentChange(val) {
       this.currentPage = val;

+ 4 - 4
src/features/system/UserManagement/UserManagement.vue

@@ -74,7 +74,7 @@
             plain
             @click="toggleEnableUser(scope.row)"
           >
-            {{ scope.row.enable ? "禁用" : "启用" }}
+            {{ !scope.row.enable | booleanEnableDisableFilter }}
           </el-button>
         </div>
       </el-table-column>
@@ -139,15 +139,15 @@ export default {
     async searchForm() {
       const res = await searchUsers({
         orgId: this.form.orgId,
-        role: this.form.roleCode,
+        roleCode: this.form.roleCode,
         enable: this.form.enableState,
         loginName: this.form.loginName,
         name: this.form.name,
         pageNumber: this.currentPage,
         pageSize: this.pageSize,
       });
-      this.tableData = res.data.data.records.records;
-      this.total = res.data.data.records.total;
+      this.tableData = res.data.data.records;
+      this.total = res.data.data.total;
     },
     handleCurrentChange(val) {
       this.currentPage = val;

+ 5 - 0
src/filters/index.js

@@ -16,6 +16,11 @@ Vue.filter("booleanPassFilter", function (val) {
   return { true: "通过", false: "不通过" }[val];
 });
 
+Vue.filter("booleanEnableDisableFilter", function (val) {
+  if (val === null) return "无";
+  return { true: "启用", false: "禁用" }[val];
+});
+
 Vue.filter("zeroOneYesNoFilter", function (val) {
   if (val === null) return "无";
   return { 1: "是", 0: "否" }[val];

+ 17 - 1
src/router/index.js

@@ -36,11 +36,11 @@ Vue.use(VueRouter);
 const routes = [
   {
     path: "/home",
-    name: "Home",
     component: Layout,
     children: [
       {
         path: "",
+        name: "Home",
         component: Home,
       },
       ...invigilation,
@@ -98,6 +98,14 @@ const routes = [
             /* webpackChunkName: "exam" */ "../features/examwork/ActivityManagement/ActivityManagement.vue"
           ),
       },
+      {
+        path: ":examId/activity/new",
+        name: "ActivityEdit",
+        component: () =>
+          import(
+            /* webpackChunkName: "exam" */ "../features/examwork/ActivityManagement/ActivityEdit.vue"
+          ),
+      },
       {
         path: "examstudent",
         name: "ExamStudentManagement",
@@ -138,6 +146,14 @@ const routes = [
             /* webpackChunkName: "exam" */ "../features/examwork/InvigilateManagement/InvigilateManagement.vue"
           ),
       },
+      {
+        path: "task",
+        name: "ImportExportTask",
+        component: () =>
+          import(
+            /* webpackChunkName: "exam" */ "../features/examwork/ImportExportTask/ImportExportTask.vue"
+          ),
+      },
     ],
   },
   {

+ 1 - 0
src/utils/utils.js

@@ -45,6 +45,7 @@ export function object2QueryString(obj) {
 export function downloadFileURL(url, name) {
   const link = document.createElement("a");
   link.style.display = "none";
+  // txt 文件会直接在浏览器里面打开,这时候只能修改服务器设置,加上 Content-Disposition: attachment
   link.href = url;
   const fileName = name || url.split("/").pop();
   link.setAttribute("download", fileName);

+ 4 - 0
src/views/Layout/components/menu.js

@@ -68,6 +68,10 @@ const businessMenuConfig = [
         title: "考场监考设置",
         name: "InvigilateManagement",
       },
+      {
+        title: "导入导出任务",
+        name: "ImportExportTask",
+      },
     ],
   },
 ];