Michael Wang 4 жил өмнө
parent
commit
5b5dfb14a6
30 өөрчлөгдсөн 1375 нэмэгдсэн , 32 устгасан
  1. 0 0
      src/api/examwork-activity.js
  2. 20 0
      src/api/examwork-course.js
  3. 1 6
      src/api/examwork-exam.js
  4. 23 0
      src/api/examwork-invigilate.js
  5. 50 0
      src/api/examwork-student.js
  6. 16 0
      src/api/examwork-task.js
  7. 68 0
      src/components/CourseSelect.vue
  8. 67 0
      src/components/ExamSelect.vue
  9. 1 0
      src/components/ExamTypeSelect.vue
  10. 1 0
      src/components/OrgSelect.vue
  11. 1 0
      src/components/RoleSelect.vue
  12. 3 1
      src/components/StateSelect.vue
  13. 38 0
      src/components/registerComponents.js
  14. 4 1
      src/features/examwork/ActivityManagement/ActivityManagement.vue
  15. 1 1
      src/features/examwork/ActivityManagement/ActivityManagementDialog.vue
  16. 155 0
      src/features/examwork/CourseManagement/CourseManagement.vue
  17. 0 0
      src/features/examwork/CourseManagement/CoursePaperDialog.vue
  18. 0 0
      src/features/examwork/CourseManagement/PaperImportDialog.vue
  19. 1 1
      src/features/examwork/ExamManagement/ExamEdit.vue
  20. 1 1
      src/features/examwork/ExamManagement/ExamManagement.vue
  21. 122 0
      src/features/examwork/ExamStudentImport/ExamStudentImport.vue
  22. 184 0
      src/features/examwork/ExamStudentManagement/ExamStudentManagement.vue
  23. 132 0
      src/features/examwork/ExamStudentManagement/ExamStudentManagementDialog.vue
  24. 132 0
      src/features/examwork/InvigilateManagement/InvigilateManagement.vue
  25. 137 0
      src/features/examwork/InvigilateManagement/InvigilateManagementDialog.vue
  26. 154 0
      src/features/examwork/StudentManagement/StudentManagement.vue
  27. 1 0
      src/main.js
  28. 40 0
      src/router/index.js
  29. 12 0
      src/utils/utils.js
  30. 10 21
      src/views/Layout/components/menu.js

+ 0 - 0
src/api/activity.js → src/api/examwork-activity.js


+ 20 - 0
src/api/examwork-course.js

@@ -0,0 +1,20 @@
+import { httpApp } from "@/plugins/axiosIndex";
+import { pickBy } from "lodash-es";
+import { object2QueryString } from "@/utils/utils";
+
+export function searchCourses({
+  examId = "",
+  courseName = "",
+  enable = "",
+  pageNumber = 1,
+  pageSize = 10,
+}) {
+  const data = pickBy(
+    { examId, courseName, enable, pageNumber, pageSize },
+    (v) => v !== ""
+  );
+  if (data.examId)
+    return httpApp.post(
+      "/api/admin/exam/course/query?" + object2QueryString(data)
+    );
+}

+ 1 - 6
src/api/exam.js → src/api/examwork-exam.js

@@ -3,17 +3,12 @@ import { pickBy } from "lodash-es";
 import { object2QueryString } from "@/utils/utils";
 
 export function searchExams({
-  role,
-  loginName = "",
   name = "",
   enable = "",
   pageNumber = 1,
   pageSize = 10,
 }) {
-  const data = pickBy(
-    { role, loginName, name, enable, pageNumber, pageSize },
-    (v) => v !== ""
-  );
+  const data = pickBy({ name, enable, pageNumber, pageSize }, (v) => v !== "");
   return httpApp.post("/api/admin/exam/query?" + object2QueryString(data));
 }
 

+ 23 - 0
src/api/examwork-invigilate.js

@@ -0,0 +1,23 @@
+import { httpApp } from "@/plugins/axiosIndex";
+import { pickBy } from "lodash-es";
+import { object2QueryString } from "@/utils/utils";
+
+export function searchInvigilators({
+  roomCode = "",
+  userId = "",
+  pageNumber = 1,
+  pageSize = 10,
+}) {
+  const data = pickBy(
+    { roomCode, userId, pageNumber, pageSize },
+    (v) => v !== ""
+  );
+  return httpApp.post(
+    "/api/admin/invigilate/user/query?" + object2QueryString(data)
+  );
+}
+
+export function saveInvigilator({ roomCode = "", userIds = "" }) {
+  const data = pickBy({ roomCode, userIds }, (v) => v !== "");
+  return httpApp.post("/api/admin/invigilate/user/save", data);
+}

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

@@ -0,0 +1,50 @@
+import { httpApp } from "@/plugins/axiosIndex";
+import { pickBy } from "lodash-es";
+import { object2QueryString, AESString } from "@/utils/utils";
+
+export function searchStudents({
+  orgId = "",
+  role,
+  loginName = "",
+  name = "",
+  enable = "",
+  pageNumber = 1,
+  pageSize = 10,
+}) {
+  const data = pickBy(
+    { orgId, role, loginName, name, enable, pageNumber, pageSize },
+    (v) => v !== ""
+  );
+  return httpApp.post("/api/admin/student/query?" + object2QueryString(data));
+}
+
+export function saveStudent({
+  orgId = "",
+  id = "",
+  roleCode,
+  loginName = "",
+  name = "",
+  enable = "",
+  password = "",
+  mobileNumber = "",
+}) {
+  const data = pickBy(
+    { orgId, id, roleCode, loginName, name, enable, password, mobileNumber },
+    (v) => v !== ""
+  );
+  return httpApp.post("/api/admin/student/save", {
+    ...data,
+    ...(password.length > 0 ? { password: AESString(password) } : {}),
+  });
+}
+
+export function toggleEnableStudent({ id, enable }) {
+  return httpApp.post("/api/admin/student/toggle", { id, enable });
+}
+
+export function resetStudentPassword({ id, password }) {
+  return httpApp.post("/api/admin/student/updatePwd", {
+    id,
+    password: AESString(password),
+  });
+}

+ 16 - 0
src/api/examwork-task.js

@@ -0,0 +1,16 @@
+import { httpApp } from "@/plugins/axiosIndex";
+import { pickBy } from "lodash-es";
+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));
+}
+
+export function downloadFile({ id = "", fileType = "" }) {
+  const data = pickBy({ id, fileType }, (v) => v !== "");
+  return httpApp.post(
+    "/api/admin/task/file/download?" + object2QueryString(data)
+  );
+}

+ 68 - 0
src/components/CourseSelect.vue

@@ -0,0 +1,68 @@
+<template>
+  <el-select
+    v-model="selected"
+    class="size-select"
+    placeholder="请选择"
+    @change="select"
+    style="width: 100px;"
+    filterable
+    remote
+    :remote-method="search"
+    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>
+import { searchCourses } from "@/api/examwork-course";
+
+export default {
+  name: "CourseSelect",
+  props: {
+    value: String,
+    examId: String,
+  },
+  data() {
+    return {
+      optionList: [],
+      selected: "",
+    };
+  },
+  async created() {
+    this.search();
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(val) {
+        this.selected = val;
+      },
+    },
+  },
+  methods: {
+    async search(query) {
+      const res = await searchCourses({
+        examId: this.examId,
+        courseName: query,
+        pageNumber: 1,
+        pageSize: 30,
+      });
+      this.optionList = res?.data.data.records.records;
+    },
+    select() {
+      this.$emit("input", this.selected);
+      this.$emit("change", this.selected);
+    },
+  },
+};
+</script>
+
+<style></style>

+ 67 - 0
src/components/ExamSelect.vue

@@ -0,0 +1,67 @@
+<template>
+  <el-select
+    v-model="selected"
+    class="size-select"
+    placeholder="请选择"
+    @change="select"
+    style="width: 100px;"
+    filterable
+    remote
+    :remote-method="search"
+    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 { searchExams } from "@/api/examwork-exam";
+
+export default {
+  name: "ExamSelect",
+  props: {
+    value: String,
+  },
+  data() {
+    return {
+      optionList: [],
+      selected: "",
+    };
+  },
+  async created() {
+    this.search();
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(val) {
+        this.selected = val;
+      },
+    },
+  },
+  methods: {
+    async search(query) {
+      const res = await searchExams({
+        name: query,
+        pageNumber: 1,
+        pageSize: 30,
+      });
+      // console.log(res.data);
+      this.optionList = res.data.data.records.records;
+    },
+    select() {
+      this.$emit("input", this.selected);
+      this.$emit("change", this.selected);
+    },
+  },
+};
+</script>
+
+<style></style>

+ 1 - 0
src/components/ExamTypeSelect.vue

@@ -5,6 +5,7 @@
     placeholder="请选择"
     @change="select"
     style="width: 100px;"
+    clearable
   >
     <el-option
       v-for="item in optionList"

+ 1 - 0
src/components/OrgSelect.vue

@@ -5,6 +5,7 @@
     placeholder="请选择"
     @change="select"
     style="width: 100px;"
+    clearable
   >
     <el-option
       v-for="item in optionList"

+ 1 - 0
src/components/RoleSelect.vue

@@ -6,6 +6,7 @@
     @change="select"
     style="width: 100px;"
     :multiple="multiple"
+    clearable
   >
     <el-option
       v-for="item in optionList"

+ 3 - 1
src/components/StateSelect.vue

@@ -5,6 +5,7 @@
     placeholder="请选择"
     @change="select"
     style="width: 100px;"
+    clearable
   >
     <el-option
       v-for="item in optionList"
@@ -25,10 +26,11 @@ export default {
       type: Number,
       default: 1,
     },
+    options: { type: Array, default: () => null },
   },
   data() {
     return {
-      optionList: [
+      optionList: this.options || [
         { code: 0, name: "禁用" },
         { code: 1, name: "启用" },
       ],

+ 38 - 0
src/components/registerComponents.js

@@ -0,0 +1,38 @@
+import Vue from "vue";
+// import upperFirst from "lodash/upperFirst";
+// import camelCase from "lodash/camelCase";
+
+const requireComponent = require.context(
+  // The relative path of the components folder
+  "./",
+  // Whether or not to look in subfolders
+  false,
+  // The regular expression used to match base component filenames
+  /[A-Z]\w+\.(vue|js)$/
+);
+
+requireComponent.keys().forEach((fileName) => {
+  // Get component config
+  const componentConfig = requireComponent(fileName);
+
+  // Get PascalCase name of component
+  const componentName =
+    // upperFirst(
+    //   camelCase(
+    // Gets the file name regardless of folder depth
+    fileName
+      .split("/")
+      .pop()
+      .replace(/\.\w+$/, "");
+  //   )
+  // );
+
+  // Register component globally
+  Vue.component(
+    componentName,
+    // Look for the component options on `.default`, which will
+    // exist if the component was exported with `export default`,
+    // otherwise fall back to module's root.
+    componentConfig.default || componentConfig
+  );
+});

+ 4 - 1
src/features/examwork/ActivityManagement/ActivityManagement.vue

@@ -79,7 +79,10 @@
 </template>
 
 <script>
-import { searchActivities, toggleEnableActivity } from "@/api/activity";
+import {
+  searchActivities,
+  toggleEnableActivity,
+} from "@/api/examwork-activity";
 import ActivityManagementDialog from "./ActivityManagementDialog";
 
 export default {

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

@@ -66,7 +66,7 @@
 
 <script>
 import MinuteInput from "@/components/MinuteInput";
-import { saveActivity } from "@/api/activity";
+import { saveActivity } from "@/api/examwork-activity";
 export default {
   name: "ActivityManagementDialog",
   components: { MinuteInput },

+ 155 - 0
src/features/examwork/CourseManagement/CourseManagement.vue

@@ -0,0 +1,155 @@
+<template>
+  <div>
+    <el-form ref="form" :model="form" :rules="rules" inline>
+      <el-form-item label="批次名称" prop="examId">
+        <ExamSelect v-model="form.examId" />
+      </el-form-item>
+      <el-form-item label="科目名称">
+        <CourseSelect :examId="form.examId" v-model="form.code" />
+      </el-form-item>
+      <el-form-item label="科目名称">
+        <StateSelect
+          :options="[
+            { code: 0, name: '未绑卷' },
+            { code: 1, name: '已绑卷' },
+          ]"
+          v-model="form.hasPaper"
+        />
+      </el-form-item>
+      <el-button @click="searchForm">查询</el-button>
+      <el-button>导入</el-button>
+    </el-form>
+
+    <el-table :data="tableData" stripe style="width: 100%;">
+      <el-table-column type="selection" width="40" />
+      <el-table-column width="55" 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>
+      <el-table-column label="候考时间">
+        <span slot-scope="scope">{{ scope.row.prepareSeconds / 60 }}</span>
+      </el-table-column>
+      <el-table-column width="120" label="状态">
+        <span slot-scope="scope">{{
+          scope.row.enable | zeroOneEnableDisableFilter
+        }}</span>
+      </el-table-column>
+      <el-table-column width="100" label="开始时间">
+        <span slot-scope="scope">{{
+          scope.row.startTime | datetimeFilter
+        }}</span>
+      </el-table-column>
+      <el-table-column width="100" label="结束时间">
+        <span slot-scope="scope">{{ scope.row.endTime | datetimeFilter }}</span>
+      </el-table-column>
+      <el-table-column width="120" label="更新人">
+        <span slot-scope="scope">{{ scope.row.updateName }}</span>
+      </el-table-column>
+      <el-table-column sortable width="170" label="更新时间">
+        <span slot-scope="scope">{{
+          scope.row.updateTime | datetimeFilter
+        }}</span>
+      </el-table-column>
+      <el-table-column :context="_self" label="操作" width="210">
+        <div slot-scope="scope">
+          <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>
+    <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>
+
+    <!-- <ActivityManagementDialog
+      ref="theDialog"
+      :examId="examId"
+      :activity="selectedActivity"
+      @reload="searchForm"
+    /> -->
+  </div>
+</template>
+
+<script>
+import { searchCourses } from "@/api/examwork-course";
+// import ActivityManagementDialog from "./ActivityManagementDialog";
+
+export default {
+  name: "CourseManagement",
+  // components: {
+  //   ActivityManagementDialog,
+  // },
+  computed: {},
+  data() {
+    return {
+      form: {
+        examId: "",
+        code: "",
+        hasPaper: null,
+      },
+      rules: {
+        examId: [{ required: true, message: "批次必选" }],
+      },
+      tableData: [],
+      currentPage: 1,
+      pageSize: 10,
+      total: 10,
+      selectedActivity: {},
+    };
+  },
+  async created() {},
+  methods: {
+    async searchForm() {
+      const valid = await this.$refs.form.validate();
+      if (!valid) return;
+      const res = await searchCourses({
+        examId: this.form.examId,
+        code: this.form.code,
+        hasPaper: this.form.hasPaper,
+        pageNumber: this.currentPage,
+        pageSize: this.pageSize,
+      });
+      this.tableData = res.data.data.records.records;
+      this.total = res.data.data.records.total;
+    },
+    handleCurrentChange(val) {
+      this.currentPage = val;
+      this.searchForm();
+    },
+    handleSizeChange(val) {
+      this.pageSize = val;
+      this.currentPage = 1;
+      this.searchForm();
+    },
+    add() {
+      this.selectedActivity = {};
+      this.$refs.theDialog.openDialog();
+    },
+    edit(activity) {
+      this.selectedActivity = activity;
+      this.$refs.theDialog.openDialog();
+    },
+  },
+};
+</script>
+
+<style></style>

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


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


+ 1 - 1
src/features/examwork/ExamManagement/ExamEdit.vue

@@ -218,7 +218,7 @@
 <script>
 import ExamTypeSelect from "@/components/ExamTypeSelect";
 import MinuteInput from "@/components/MinuteInput";
-import { saveExam, getExamDetail } from "@/api/exam";
+import { saveExam, getExamDetail } from "@/api/examwork-exam";
 
 export default {
   name: "ExamEdit",

+ 1 - 1
src/features/examwork/ExamManagement/ExamManagement.vue

@@ -99,7 +99,7 @@
 <script>
 import StateSelect from "@/components/StateSelect";
 import ExamTypeSelect from "@/components/ExamTypeSelect";
-import { searchExams, toggleEnableExam, copyExam } from "@/api/exam";
+import { searchExams, toggleEnableExam, copyExam } from "@/api/examwork-exam";
 
 export default {
   name: "ExamManagement",

+ 122 - 0
src/features/examwork/ExamStudentImport/ExamStudentImport.vue

@@ -0,0 +1,122 @@
+<template>
+  <div>
+    <!-- 下载链接 -->
+    <el-form :model="form" inline>
+      <el-form-item label="批次名称" prop="examId">
+        <ExamSelect v-model="form.examId" />
+      </el-form-item>
+      <el-button @click="add">导入</el-button>
+      <!-- <el-button>导入</el-button> -->
+    </el-form>
+
+    <el-table :data="tableData" stripe style="width: 100%;">
+      <el-table-column type="selection" width="40" />
+      <el-table-column width="55" 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.entityName }}</span>
+      </el-table-column>
+      <el-table-column 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 width="100" 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
+            @click="download({ id: scope.row.id, fileType: 'IMPORTFILE' })"
+          >
+            下载文件
+          </el-button>
+          <el-button
+            size="mini"
+            type="primary"
+            plain
+            @click="download({ id: scope.row.id, fileType: 'ERROR' })"
+          >
+            导出报告
+          </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, downloadFile } from "@/api/examwork-task";
+import { downloadFileURL } from "@/utils/utils";
+
+export default {
+  name: "ExamStudentImport",
+  data() {
+    return {
+      form: {
+        examId: "",
+      },
+      rules: {
+        examId: [{ required: true, message: "批次必选" }],
+      },
+      tableData: [],
+      currentPage: 1,
+      pageSize: 10,
+      total: 10,
+      selectedActivity: {},
+    };
+  },
+  created() {
+    this.searchForm();
+  },
+  methods: {
+    async searchForm() {
+      const res = await searchTasks({
+        type: "IMPORT_EXAM_STUDENT",
+        pageNumber: this.currentPage,
+        pageSize: this.pageSize,
+      });
+      this.tableData = res.data.data.records.records;
+      this.total = res.data.data.records.total;
+    },
+    handleCurrentChange(val) {
+      this.currentPage = val;
+      this.searchForm();
+    },
+    handleSizeChange(val) {
+      this.pageSize = val;
+      this.currentPage = 1;
+      this.searchForm();
+    },
+    add() {},
+    async download({ id, fileType }) {
+      const res = await downloadFile({ id, fileType });
+      const url = res.data.data.url;
+      downloadFileURL(url);
+    },
+  },
+};
+</script>
+
+<style></style>

+ 184 - 0
src/features/examwork/ExamStudentManagement/ExamStudentManagement.vue

@@ -0,0 +1,184 @@
+<template>
+  <div>
+    <el-form :model="form" inline>
+      <el-form-item label="批次名称">
+        <el-input v-model.trim="form.code"></el-input>
+      </el-form-item>
+      <el-form-item label="场次代码">
+        <el-input v-model.trim="form.code"></el-input>
+      </el-form-item>
+      <el-form-item label="考场名称">
+        <el-input v-model.trim="form.code"></el-input>
+      </el-form-item>
+      <el-form-item label="科目">
+        <el-input v-model.trim="form.code"></el-input>
+      </el-form-item>
+      <el-form-item label="姓名">
+        <el-input v-model.trim="form.code"></el-input>
+      </el-form-item>
+      <el-form-item label="证件号">
+        <el-input v-model.trim="form.code"></el-input>
+      </el-form-item>
+      <el-form-item label="年级">
+        <el-input v-model.trim="form.code"></el-input>
+      </el-form-item>
+      <el-form-item label="教学班级">
+        <el-input v-model.trim="form.code"></el-input>
+      </el-form-item>
+      <el-button @click="searchForm">查询</el-button>
+      <el-button @click="add">新增</el-button>
+      <!-- <el-button>导入</el-button> -->
+    </el-form>
+
+    <el-table :data="tableData" stripe style="width: 100%;">
+      <el-table-column type="selection" width="40" />
+      <el-table-column width="55" 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>
+      <el-table-column label="姓名">
+        <span slot-scope="scope">{{ scope.row.prepareSeconds / 60 }}</span>
+      </el-table-column>
+      <el-table-column width="200" label="证件号">
+        <span slot-scope="scope">{{ scope.row.code }}</span>
+      </el-table-column>
+      <el-table-column width="200" label="科目名称">
+        <span slot-scope="scope">{{ scope.row.code }}</span>
+      </el-table-column>
+      <el-table-column width="200" label="科目代码">
+        <span slot-scope="scope">{{ scope.row.code }}</span>
+      </el-table-column>
+      <el-table-column width="120" label="状态">
+        <span slot-scope="scope">{{
+          scope.row.enable | zeroOneEnableDisableFilter
+        }}</span>
+      </el-table-column>
+      <el-table-column width="100" label="考场代码">
+        <span slot-scope="scope">{{
+          scope.row.startTime | datetimeFilter
+        }}</span>
+      </el-table-column>
+      <el-table-column width="100" label="考场名称">
+        <span slot-scope="scope">{{ scope.row.endTime | datetimeFilter }}</span>
+      </el-table-column>
+      <el-table-column width="120" label="年级">
+        <span slot-scope="scope">{{ scope.row.updateName }}</span>
+      </el-table-column>
+      <el-table-column sortable width="170" label="教学班级">
+        <span slot-scope="scope">{{
+          scope.row.updateTime | datetimeFilter
+        }}</span>
+      </el-table-column>
+      <el-table-column :context="_self" label="操作" width="210">
+        <div slot-scope="scope">
+          <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>
+    <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>
+
+    <ExamStudentManagementDialog
+      ref="theDialog"
+      :examId="examId"
+      :activity="selectedActivity"
+      @reload="searchForm"
+    />
+  </div>
+</template>
+
+<script>
+import {
+  searchActivities,
+  toggleEnableActivity,
+} from "@/api/examwork-activity";
+import ExamStudentManagementDialog from "./ExamStudentManagementDialog";
+
+export default {
+  name: "ExamStudentManagement",
+  components: {
+    ExamStudentManagementDialog,
+  },
+  computed: {
+    examId() {
+      return this.$route.params.examId;
+    },
+  },
+  data() {
+    return {
+      form: {
+        orgId: "",
+        roleCode: "",
+        loginName: "",
+        name: "",
+        enableState: null,
+      },
+      tableData: [],
+      currentPage: 1,
+      pageSize: 10,
+      total: 10,
+      selectedActivity: {},
+    };
+  },
+  async created() {},
+  methods: {
+    async searchForm() {
+      const res = await searchActivities({
+        examId: this.examId,
+        code: this.form.code,
+        pageNumber: this.currentPage,
+        pageSize: this.pageSize,
+      });
+      this.tableData = res.data.data.records.records;
+      this.total = res.data.data.records.total;
+    },
+    handleCurrentChange(val) {
+      this.currentPage = val;
+      this.searchForm();
+    },
+    handleSizeChange(val) {
+      this.pageSize = val;
+      this.currentPage = 1;
+      this.searchForm();
+    },
+    add() {
+      this.selectedActivity = {};
+      this.$refs.theDialog.openDialog();
+    },
+    edit(activity) {
+      this.selectedActivity = activity;
+      this.$refs.theDialog.openDialog();
+    },
+    async toggleEnableActivity(activity) {
+      await toggleEnableActivity({
+        id: activity.id,
+        enable: activity.enable === 0 ? 1 : 0,
+      });
+      this.searchForm();
+    },
+  },
+};
+</script>
+
+<style></style>

+ 132 - 0
src/features/examwork/ExamStudentManagement/ExamStudentManagementDialog.vue

@@ -0,0 +1,132 @@
+<template>
+  <el-dialog
+    ref="dialog"
+    :title="(isEdit ? '编辑' : '新增') + '场次'"
+    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="考试时间" prop="startTime">
+          <el-date-picker
+            v-model="form.startTime"
+            type="datetime"
+            placeholder="选择日期时间"
+          >
+          </el-date-picker>
+        </el-form-item>
+      </el-row>
+      <el-row>
+        <el-form-item label="交卷时间" prop="finishTime">
+          <el-date-picker
+            v-model="form.finishTime"
+            type="datetime"
+            placeholder="选择日期时间"
+          >
+          </el-date-picker>
+        </el-form-item>
+      </el-row>
+      <el-row>
+        <el-form-item label="考试时长" prop="prepareSeconds">
+          <MinuteInput v-model="form.maxDurationSeconds" />
+        </el-form-item>
+      </el-row>
+      <el-row>
+        <el-form-item label="候考时间" prop="prepareSeconds">
+          <MinuteInput v-model="form.prepareSeconds" />
+        </el-form-item>
+      </el-row>
+      <el-row>
+        <el-form-item label="迟到时长" prop="prepareSeconds">
+          <MinuteInput v-model="form.openingSeconds" />
+        </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>
+      </el-row>
+    </el-form>
+  </el-dialog>
+</template>
+
+<script>
+import MinuteInput from "@/components/MinuteInput";
+import { saveActivity } from "@/api/examwork-activity";
+export default {
+  name: "ExamStudentManagementDialog",
+  components: { MinuteInput },
+  props: {
+    examId: String,
+    activity: Object,
+  },
+  computed: {
+    isEdit() {
+      return this.activity.id;
+    },
+  },
+  data() {
+    return {
+      visible: false,
+      form: {
+        id: "",
+        startTime: null,
+        finishTime: null,
+        prepareSeconds: 0,
+        openingSeconds: 0,
+        maxDurationSeconds: 0,
+      },
+      rules: {},
+    };
+  },
+  watch: {
+    user(val) {
+      let tmp = { ...val };
+      if (!tmp.id) {
+        tmp = {
+          id: "",
+          startTime: null,
+          finishTime: null,
+          prepareSeconds: 0,
+          openingSeconds: 0,
+          maxDurationSeconds: 0,
+        };
+      }
+      this.form = tmp;
+    },
+  },
+  methods: {
+    openDialog() {
+      this.visible = true;
+    },
+    closeDialog() {
+      this.visible = false;
+    },
+    async submitForm() {
+      let data = this.form;
+      if (this.isEdit) {
+        data = { ...data, id: this.activity.id };
+      }
+      await saveActivity(data);
+      this.$emit("reload");
+      this.closeDialog();
+    },
+  },
+};
+</script>
+
+<style></style>

+ 132 - 0
src/features/examwork/InvigilateManagement/InvigilateManagement.vue

@@ -0,0 +1,132 @@
+<template>
+  <div>
+    <el-form :model="form" inline>
+      <!-- <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>
+      <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>
+      <el-button @click="searchForm">查询</el-button>
+      <el-button @click="add">新增</el-button>
+      <!-- <el-button>导入</el-button> -->
+    </el-form>
+
+    <el-table :data="tableData" stripe style="width: 100%;">
+      <el-table-column type="selection" width="40" />
+      <el-table-column width="55" 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.roomCode }}</span>
+      </el-table-column>
+      <el-table-column label="考场名称">
+        <span slot-scope="scope">{{ scope.row.roomName }}</span>
+      </el-table-column>
+      <el-table-column width="120" label="更新人">
+        <span slot-scope="scope">{{ scope.row.updateName }}</span>
+      </el-table-column>
+      <el-table-column :context="_self" label="操作" width="210">
+        <div slot-scope="scope">
+          <el-button size="mini" type="primary" plain @click="edit(scope.row)">
+            编辑
+          </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>
+
+    <InvigilateManagementDialog
+      ref="userDialog"
+      :user="selectedUser"
+      @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";
+
+export default {
+  name: "InvigilateManagement",
+  components: {
+    RoleSelect,
+    StateSelect,
+    // OrgSelect,
+    InvigilateManagementDialog,
+  },
+  data() {
+    return {
+      form: {
+        orgId: "",
+        roleCode: "",
+        loginName: "",
+        name: "",
+        enableState: null,
+      },
+      tableData: [],
+      currentPage: 1,
+      pageSize: 10,
+      total: 10,
+      selectedUser: {},
+    };
+  },
+  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,
+        pageNumber: this.currentPage,
+        pageSize: this.pageSize,
+      });
+      this.tableData = res.data.data.records.records;
+      this.total = res.data.data.records.total;
+    },
+    handleCurrentChange(val) {
+      this.currentPage = val;
+      this.searchForm();
+    },
+    handleSizeChange(val) {
+      this.pageSize = val;
+      this.currentPage = 1;
+      this.searchForm();
+    },
+    add() {
+      this.selectedUser = {};
+      this.$refs.userDialog.openDialog();
+    },
+    edit(user) {
+      this.selectedUser = user;
+      this.$refs.userDialog.openDialog();
+    },
+  },
+};
+</script>
+
+<style></style>

+ 137 - 0
src/features/examwork/InvigilateManagement/InvigilateManagementDialog.vue

@@ -0,0 +1,137 @@
+<template>
+  <el-dialog
+    ref="dialog"
+    :title="(isEdit ? '编辑' : '新增') + '用户'"
+    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 v-if="$store.state.user.orgId === null" label="机构">
+          <OrgSelect v-model="form.orgId"></OrgSelect>
+        </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>
+      </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>
+      </el-row>
+    </el-form>
+  </el-dialog>
+</template>
+
+<script>
+import RoleSelect from "@/components/RoleSelect";
+import { saveUser } from "@/api/system-user";
+import OrgSelect from "@/components/OrgSelect";
+export default {
+  name: "InvigilateManagementDialog",
+  components: { RoleSelect, OrgSelect },
+  props: {
+    user: Object,
+  },
+  computed: {
+    isEdit() {
+      return this.user.id;
+    },
+  },
+  data() {
+    return {
+      visible: false,
+      form: {},
+      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;
+    },
+    closeDialog() {
+      this.visible = false;
+    },
+    async submitForm() {
+      let data = this.form;
+      if (this.isEdit) {
+        data = { ...data, id: this.user.id };
+      }
+      await saveUser(data);
+      this.$emit("reload");
+      this.closeDialog();
+    },
+  },
+};
+</script>
+
+<style></style>

+ 154 - 0
src/features/examwork/StudentManagement/StudentManagement.vue

@@ -0,0 +1,154 @@
+<template>
+  <div>
+    <el-form :model="form" inline>
+      <el-form-item label="姓名">
+        <el-input v-model.trim="form.name"></el-input>
+      </el-form-item>
+      <el-form-item label="证件号">
+        <el-input v-model.trim="form.identity"></el-input>
+      </el-form-item>
+      <el-form-item label="状态">
+        <StateSelect v-model="form.enable"></StateSelect>
+      </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="40" />
+      <el-table-column width="55" 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.name }}</span>
+      </el-table-column>
+      <el-table-column label="证件号">
+        <span slot-scope="scope">{{ scope.row.identity }}</span>
+      </el-table-column>
+      <el-table-column width="100" label="机构">
+        <span slot-scope="scope">{{ scope.row.orgName }}</span>
+      </el-table-column>
+      <el-table-column width="120" label="状态">
+        <span slot-scope="scope">{{
+          scope.row.enable | zeroOneEnableDisableFilter
+        }}</span>
+      </el-table-column>
+      <el-table-column width="120" label="照片">
+        <span slot-scope="scope">{{ scope.row.basePhotoPath }}</span>
+      </el-table-column>
+      <el-table-column width="120" label="更新人">
+        <span slot-scope="scope">{{ scope.row.updateName }}</span>
+      </el-table-column>
+      <el-table-column sortable width="170" label="更新时间">
+        <span slot-scope="scope">{{
+          scope.row.updateTime | datetimeFilter
+        }}</span>
+      </el-table-column>
+      <el-table-column :context="_self" label="操作" width="210">
+        <div slot-scope="scope">
+          <el-button size="mini" type="primary" plain @click="edit(scope.row)">
+            考试记录
+          </el-button>
+          <el-button
+            size="mini"
+            type="primary"
+            plain
+            @click="resetStudentPassword(scope.row)"
+          >
+            重置密码
+          </el-button>
+          <el-button
+            size="mini"
+            type="primary"
+            plain
+            @click="toggleEnableStudent(scope.row)"
+          >
+            {{ scope.row.enable ? "禁用" : "启用" }}
+          </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 {
+  searchStudents,
+  toggleEnableStudent,
+  resetStudentPassword,
+} from "@/api/examwork-student";
+
+export default {
+  name: "StudentManagement",
+  data() {
+    return {
+      form: {
+        name: "",
+        identity: "",
+        enable: null,
+      },
+      tableData: [],
+      currentPage: 1,
+      pageSize: 10,
+      total: 10,
+      selectedUser: {},
+    };
+  },
+  async created() {},
+  methods: {
+    async searchForm() {
+      const res = await searchStudents({
+        enable: this.form.enable,
+        name: this.form.name,
+        pageNumber: this.currentPage,
+        pageSize: this.pageSize,
+      });
+      this.tableData = res.data.data.records.records;
+      this.total = res.data.data.records.total;
+    },
+    handleCurrentChange(val) {
+      this.currentPage = val;
+      this.searchForm();
+    },
+    handleSizeChange(val) {
+      this.pageSize = val;
+      this.currentPage = 1;
+      this.searchForm();
+    },
+    add() {
+      this.selectedUser = {};
+      this.$refs.userDialog.openDialog();
+    },
+    edit(user) {
+      this.selectedUser = user;
+      this.$refs.userDialog.openDialog();
+    },
+    async toggleEnableStudent(user) {
+      await toggleEnableStudent({
+        id: user.id,
+        enable: user.enable === 0 ? 1 : 0,
+      });
+      this.searchForm();
+    },
+    async resetStudentPassword(user) {
+      await resetStudentPassword({
+        id: user.id,
+        password: "123456",
+      });
+      this.searchForm();
+    },
+  },
+};
+</script>
+
+<style></style>

+ 1 - 0
src/main.js

@@ -6,6 +6,7 @@ import store from "./store";
 // import "./registerServiceWorker";
 // 27KB non-zip
 import "./plugins/axiosIndex";
+import "./components/registerComponents";
 import "./plugins/customComponents";
 import "./filters";
 import "./mixins/logout";

+ 40 - 0
src/router/index.js

@@ -98,6 +98,46 @@ const routes = [
             /* webpackChunkName: "exam" */ "../features/examwork/ActivityManagement/ActivityManagement.vue"
           ),
       },
+      {
+        path: "examstudent",
+        name: "ExamStudentManagement",
+        component: () =>
+          import(
+            /* webpackChunkName: "exam" */ "../features/examwork/ExamStudentManagement/ExamStudentManagement.vue"
+          ),
+      },
+      {
+        path: "examstudent/import",
+        name: "ExamStudentImport",
+        component: () =>
+          import(
+            /* webpackChunkName: "exam" */ "../features/examwork/ExamStudentImport/ExamStudentImport.vue"
+          ),
+      },
+      {
+        path: "course",
+        name: "CourseManagement",
+        component: () =>
+          import(
+            /* webpackChunkName: "exam" */ "../features/examwork/CourseManagement/CourseManagement.vue"
+          ),
+      },
+      {
+        path: "student",
+        name: "StudentManagement",
+        component: () =>
+          import(
+            /* webpackChunkName: "exam" */ "../features/examwork/StudentManagement/StudentManagement.vue"
+          ),
+      },
+      {
+        path: "invigilate",
+        name: "InvigilateManagement",
+        component: () =>
+          import(
+            /* webpackChunkName: "exam" */ "../features/examwork/InvigilateManagement/InvigilateManagement.vue"
+          ),
+      },
     ],
   },
   {

+ 12 - 0
src/utils/utils.js

@@ -40,3 +40,15 @@ export function AESString(content) {
 export function object2QueryString(obj) {
   return queryString.stringify(obj);
 }
+
+// 下载文件
+export function downloadFileURL(url, name) {
+  const link = document.createElement("a");
+  link.style.display = "none";
+  link.href = url;
+  const fileName = name || url.split("/").pop();
+  link.setAttribute("download", fileName);
+  document.body.appendChild(link);
+  link.click();
+  document.body.removeChild(link);
+}

+ 10 - 21
src/views/Layout/components/menu.js

@@ -53,35 +53,24 @@ const businessMenuConfig = [
         name: "ActivityManagement",
       },
       {
-        title: "调卷规则",
-        name: "Base",
-      },
-      {
-        title: "学生档案设置",
-        name: "Base",
+        title: "考生管理",
+        name: "ExamStudentManagement",
       },
       {
-        title: "考场监考设置",
-        name: "Base",
+        title: "考生导入",
+        name: "ExamStudentImport",
       },
-    ],
-  },
-  {
-    title: "系统设置",
-    name: "System",
-    icon: "icon-business",
-    children: [
       {
-        title: "用户管理",
-        name: "Base",
+        title: "调卷规则",
+        name: "CourseManagement",
       },
       {
-        title: "角色权限",
-        name: "Base",
+        title: "学生档案",
+        name: "StudentManagement",
       },
       {
-        title: "机构管理",
-        name: "Base",
+        title: "考场监考设置",
+        name: "InvigilateManagement",
       },
     ],
   },