Ver código fonte

批次新增和编辑

Michael Wang 4 anos atrás
pai
commit
5a8b8eeac6

+ 0 - 1
package.json

@@ -30,7 +30,6 @@
     "register-service-worker": "^1.7.1",
     "vue": "^2.6.11",
     "vue-awesome": "^4.1.0",
-    "vue-navigation": "^1.1.4",
     "vue-router": "^3.3.4",
     "vuex": "^3.5.1",
     "vuex-persistedstate": "^3.0.1"

+ 94 - 0
src/api/exam.js

@@ -17,6 +17,100 @@ export function searchExams({
   return httpApp.post("/api/admin/exam/query?" + object2QueryString(data));
 }
 
+export function getExamDetail({ id }) {
+  return httpApp.post("/api/admin/exam/detail?" + object2QueryString({ id }));
+}
+
 export function toggleEnableExam({ id, enable }) {
   return httpApp.post("/api/admin/exam/toggle", { id, enable });
 }
+
+export function saveExam({
+  id = "",
+  breakExpireSeconds = 0,
+  breakResumeCount = 0,
+  cameraPhotoUpload = 0,
+  code = "",
+  createId = 0,
+  createTime = "",
+  enable = 0,
+  enableIpLimit = 0,
+  endTime = "",
+  entryAuthenticationPolicy = "",
+  examCount = 0,
+  forceFinish = 0,
+  inProcessFaceStrangerIgnore = 0,
+  inProcessFaceVerify = 0,
+  inProcessLivenessFixedRange = "",
+  inProcessLivenessJudgePolicy = "",
+  inProcessLivenessVerify = 0,
+  ipAllow = "",
+  maxDurationSeconds = 0,
+  minDurationSeconds = 0,
+  mode = "",
+  monitorRecord = 0,
+  monitorVideoSource = [""],
+  name = "",
+  objectiveScorePolicy = "",
+  openingSeconds = 0,
+  // "orgId =   0,
+  postNotice = "",
+  preNotice = "",
+  preNoticeStaySeconds = 0,
+  prepareSeconds = 0,
+  progress = 0,
+  recordSelectStrategy = "",
+  reexamAuditing = 0,
+  scoreStatus = "",
+  shortCode = "",
+  showObjectiveScore = 0,
+  startTime = "",
+  mobilePhotoUpload = 0,
+}) {
+  const data = pickBy(
+    {
+      id,
+      breakExpireSeconds,
+      breakResumeCount,
+      cameraPhotoUpload,
+      code,
+      createId,
+      createTime,
+      enable,
+      enableIpLimit,
+      endTime,
+      entryAuthenticationPolicy,
+      examCount,
+      forceFinish,
+      inProcessFaceStrangerIgnore,
+      inProcessFaceVerify,
+      inProcessLivenessFixedRange,
+      inProcessLivenessJudgePolicy,
+      inProcessLivenessVerify,
+      ipAllow,
+      maxDurationSeconds,
+      minDurationSeconds,
+      mode,
+      monitorRecord,
+      monitorVideoSource,
+      name,
+      objectiveScorePolicy,
+      openingSeconds,
+      // "orgId =   0,
+      postNotice,
+      preNotice,
+      preNoticeStaySeconds,
+      prepareSeconds,
+      progress,
+      recordSelectStrategy,
+      reexamAuditing,
+      scoreStatus,
+      shortCode,
+      showObjectiveScore,
+      startTime,
+      mobilePhotoUpload,
+    },
+    (v) => v !== ""
+  );
+  return httpApp.post("/api/admin/exam/save", data);
+}

+ 47 - 0
src/components/MinuteInput.vue

@@ -0,0 +1,47 @@
+<template>
+  <el-input
+    v-model.number="minute"
+    @change="watchMinute"
+    @input="watchMinute"
+  ></el-input>
+</template>
+
+<script>
+import { isFinite } from "lodash-es";
+export default {
+  name: "MinuteInput",
+  props: {
+    value: { type: Number, default: 0 },
+  },
+  data() {
+    return {
+      minute: 0,
+    };
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(v) {
+        if (isFinite(v)) {
+          this.minute = v / 60;
+        } else {
+          this.minute = 0;
+        }
+      },
+    },
+  },
+  methods: {
+    watchMinute() {
+      let v = this.minute * 60;
+      if (!isFinite(v)) {
+        v = 0;
+        this.minute = 0;
+      }
+      this.$emit("change", v);
+      this.$emit("input", v);
+    },
+  },
+};
+</script>
+
+<style></style>

+ 307 - 0
src/features/examwork/ExamManagement/ExamEdit.vue

@@ -0,0 +1,307 @@
+<template>
+  <div>
+    <el-tabs v-model="activeName" type="card">
+      <el-tab-pane label="考试规则设置" name="first">
+        <el-form :model="form" inline>
+          <el-form-item label="考试模式">
+            <ExamTypeSelect v-model="form.mode"></ExamTypeSelect>
+          </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.name"></el-input>
+          </el-form-item>
+          <el-form-item label="考试时间">
+            <el-date-picker
+              v-model="form.startEndTimeProxy"
+              type="datetimerange"
+              range-separator="至"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+            >
+            </el-date-picker>
+          </el-form-item>
+          <el-form-item label="候考时长(分钟)">
+            <MinuteInput v-model.trim="form.prepareSeconds"> </MinuteInput>
+          </el-form-item>
+          <el-form-item label="考试次数限制">
+            <el-input v-model.number.trim="form.examCount"></el-input>
+          </el-form-item>
+          <el-form-item label="考试时长(分钟)">
+            <el-input v-model.trim="form.maxDurationSeconds"></el-input>
+          </el-form-item>
+          <el-form-item label="迟到时长(分钟)">
+            <el-input v-model.trim="form.openingSeconds"></el-input>
+          </el-form-item>
+          <el-form-item label="冻结时间(分钟)">
+            <el-input v-model.trim="form.minDurationSeconds"></el-input>
+          </el-form-item>
+          <el-form-item label="启用集中收卷">
+            <el-radio v-model="form.forceFinish" :label="1">是</el-radio>
+            <el-radio v-model="form.forceFinish" :label="0">否</el-radio>
+          </el-form-item>
+          <el-form-item label="启用开考口令">
+            <el-radio v-model="form.enableShortCode" :label="1">是</el-radio>
+            <el-radio v-model="form.enableShortCode" :label="0">否</el-radio>
+            <el-input v-model.trim="form.shortCode"></el-input>
+          </el-form-item>
+          <el-form-item label="是否允许断点续考">
+            <el-radio v-model="form.enableBreak" :label="1">是</el-radio>
+            <el-radio v-model="form.enableBreak" :label="0">否</el-radio>
+          </el-form-item>
+          <el-form-item label="断点次数">
+            <el-input v-model.trim="form.breakResumeCount"></el-input>
+          </el-form-item>
+          <el-form-item label="断点时长(分钟)">
+            <el-input v-model.trim="form.breakExpireSeconds"></el-input>
+          </el-form-item>
+          <el-form-item label="重考是否需要审核">
+            <el-radio v-model="form.reexamAuditing" :label="1">是</el-radio>
+            <el-radio v-model="form.reexamAuditing" :label="0">否</el-radio>
+          </el-form-item>
+          <el-form-item label="取分策略">
+            <el-radio
+              v-model="form.recordSelectStrategy"
+              label="HIGHEST_TOTAL_SCORE"
+            >
+              全部阅卷后取最高分
+            </el-radio>
+            <el-radio
+              v-model="form.recordSelectStrategy"
+              label="HIGHEST_OBJECTIVE_SCORE"
+            >
+              客观分最高
+            </el-radio>
+            <el-radio v-model="form.recordSelectStrategy" label="LATEST">
+              最后一次提交
+            </el-radio>
+          </el-form-item>
+          <el-form-item label="多选题给分规则">
+            <el-radio v-model="form.objectiveScorePolicy" label="EQUAL">
+              全对给分
+            </el-radio>
+            <el-radio v-model="form.objectiveScorePolicy" label="PARTIAL">
+              漏选给一半分
+            </el-radio>
+          </el-form-item>
+          <el-form-item label="是否显示客观分">
+            <el-radio v-model="form.showObjectiveScore" :label="1">是</el-radio>
+            <el-radio v-model="form.showObjectiveScore" :label="0">否</el-radio>
+          </el-form-item>
+          <el-form-item label="是否允许摄像头拍照作答">
+            <el-radio v-model="form.cameraPhotoUpload" :label="1">是</el-radio>
+            <el-radio v-model="form.cameraPhotoUpload" :label="0">否</el-radio>
+          </el-form-item>
+          <el-form-item label="是否允许小程序作答">
+            <el-radio v-model="form.mobilePhotoUpload" :label="1">是</el-radio>
+            <el-radio v-model="form.mobilePhotoUpload" :label="0">否</el-radio>
+          </el-form-item>
+        </el-form>
+      </el-tab-pane>
+
+      <el-tab-pane label="监考设置" name="second">
+        <el-form :model="form" inline>
+          <el-form-item label="开考检测">
+            <el-radio v-model="form.entryAuthenticationPolicy" label="OFF"
+              >安全级别:无</el-radio
+            >
+            <el-radio
+              v-model="form.entryAuthenticationPolicy"
+              label="FACE_VERIFY_OPTIONAL"
+              >安全级别:低</el-radio
+            >
+            <el-radio
+              v-model="form.entryAuthenticationPolicy"
+              label="FACE_VERIFY_FORCE"
+              >安全级别:中</el-radio
+            >
+            <el-radio v-model="form.entryAuthenticationPolicy" label="LIVENESS"
+              >安全级别:高</el-radio
+            >
+          </el-form-item>
+          <h2>过程监控</h2>
+          <el-form-item label="是否考中人脸识别">
+            <el-radio v-model="form.inProcessFaceVerify" :label="1"
+              >是</el-radio
+            >
+            <el-radio v-model="form.inProcessFaceVerify" :label="0"
+              >否</el-radio
+            >
+          </el-form-item>
+          <el-form-item label="是否考中陌生人脸识别">
+            <el-radio v-model="form.inProcessFaceStrangerIgnore" :label="0"
+              >是</el-radio
+            >
+            <el-radio v-model="form.inProcessFaceStrangerIgnore" :label="1"
+              >否</el-radio
+            >
+          </el-form-item>
+          <el-form-item label="是否考中活体检测">
+            <el-radio v-model="form.inProcessLivenessVerify" :label="0"
+              >是</el-radio
+            >
+            <el-radio v-model="form.inProcessLivenessVerify" :label="1"
+              >否</el-radio
+            >
+          </el-form-item>
+          <el-form-item label="活体验证弹出时间段">
+            <el-input
+              v-model.number.trim="form.inProcessLivenessFixedRange[0]"
+            ></el-input>
+            ~
+            <el-input
+              v-model.number.trim="form.inProcessLivenessFixedRange[1]"
+            ></el-input>
+            分钟
+          </el-form-item>
+          <el-form-item label="活体验证结果的判定方案">
+            <el-radio v-model="form.inProcessLivenessJudgePolicy" label="ANY">
+              单条成功则通过</el-radio
+            >
+            <el-radio v-model="form.inProcessLivenessJudgePolicy" label="ALL"
+              >所有验证成功则通过</el-radio
+            >
+            <el-radio v-model="form.inProcessLivenessJudgePolicy" label="MORE"
+              >成功次数大于失败则通过</el-radio
+            >
+            <h2>监考直播</h2>
+            <el-form-item label="是否开启考生端监考直播">
+              <el-radio v-model="form.monitorProxy" :label="1">是</el-radio>
+              <el-radio v-model="form.monitorProxy" :label="0">否</el-radio>
+            </el-form-item>
+            <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-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">手机监考机位1</el-checkbox>
+                <el-checkbox label="mobile_second">手机监考机位2</el-checkbox>
+              </el-checkbox-group>
+            </el-form-item>
+          </el-form-item>
+        </el-form>
+      </el-tab-pane>
+
+      <el-tab-pane label="其他设置" name="third">
+        <el-form :model="form" inline>
+          <el-form-item label="考试须知">
+            <el-input v-model.trim="form.preNotice"></el-input>
+          </el-form-item>
+          <el-form-item label="须知强制阅读时长(秒)">
+            <el-input v-model.trim="form.preNoticeStaySeconds"></el-input>
+          </el-form-item>
+          <el-form-item label="考后说明">
+            <el-input v-model.trim="form.postNotice"></el-input>
+          </el-form-item>
+          <el-form-item label="IP限制">
+            <el-radio v-model="form.enableIpLimit" :label="1">是</el-radio>
+            <el-radio v-model="form.enableIpLimit" :label="0">否</el-radio>
+          </el-form-item>
+          <el-form-item label="IP段(*表示任意):">
+            <el-input v-model.trim="form.ipAllow"></el-input>
+          </el-form-item>
+        </el-form>
+      </el-tab-pane>
+    </el-tabs>
+
+    <el-button @click="save">保存</el-button>
+    <el-button @click="cancel">取消</el-button>
+  </div>
+</template>
+
+<script>
+import ExamTypeSelect from "@/components/ExamTypeSelect";
+import MinuteInput from "@/components/MinuteInput";
+import { saveExam, getExamDetail } from "@/api/exam";
+
+export default {
+  name: "ExamEdit",
+  components: { ExamTypeSelect, MinuteInput },
+  computed: {
+    examId() {
+      return this.$route.params.id;
+    },
+    isEdit() {
+      return !!this.examId;
+    },
+  },
+  watch: {
+    "form.startEndTimeProxy": {
+      immediate: true,
+      handler(v) {
+        this.form.startTime = v[0];
+        this.form.endTime = v[1];
+      },
+    },
+    "form.monitorProxy": {
+      immediate: true,
+      handler(v) {
+        if (!v) {
+          this.form.monitorVideoSource = [];
+          this.form.monitorRecord = 0;
+        }
+      },
+    },
+  },
+  async created() {
+    if (this.isEdit) {
+      const res = await getExamDetail({ id: this.examId });
+      this.form = { ...this.form, ...res.data.data.records };
+      this.form.startEndTimeProxy = [this.form.startTime, this.form.endTime];
+    }
+  },
+  data() {
+    return {
+      activeName: "first",
+      form: {
+        mode: "",
+        code: "",
+        name: "",
+        startEndTimeProxy: [],
+        startTime: 0,
+        endTime: 0,
+        prepareSeconds: 0,
+        maxDurationSeconds: 0,
+        openingSeconds: 0,
+        minDurationSeconds: 0,
+        forceFinish: 1,
+        enableShortCode: 1,
+        shortCode: "",
+        enableBreak: 1,
+        breakResumeCount: 0,
+        breakExpireSeconds: 0,
+        reexamAuditing: 0,
+        recordSelectStrategy: "HIGHEST_OBJECTIVE_SCORE",
+        objectiveScorePolicy: "EQUAL",
+        showObjectiveScore: 0,
+        cameraPhotoUpload: 0,
+        mobilePhotoUpload: 0,
+        entryAuthenticationPolicy: "OFF",
+        inProcessFaceVerify: 0,
+        inProcessFaceStrangerIgnore: 0,
+        inProcessLivenessVerify: 0,
+        inProcessLivenessFixedRange: [],
+        inProcessLivenessJudgePolicy: "ALL",
+        monitorProxy: 0,
+        monitorRecord: 0,
+        monitorVideoSource: [],
+        ipAllow: "",
+      },
+    };
+  },
+  methods: {
+    save() {
+      saveExam(this.form);
+    },
+    cancel() {},
+  },
+};
+</script>
+
+<style></style>

+ 15 - 18
src/features/examwork/ExamManagement/ExamManagement.vue

@@ -19,6 +19,9 @@
       <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 width="200" label="批次名称">
         <span slot-scope="scope">{{ scope.row.name }}</span>
       </el-table-column>
@@ -39,7 +42,9 @@
         <span slot-scope="scope">{{ scope.row.endTime | datetimeFilter }}</span>
       </el-table-column>
       <el-table-column width="100" label="算分进度">
-        <span slot-scope="scope">{{ scope.row.scoreStatus }}</span>
+        <span slot-scope="scope">{{
+          scope.row.scoreStatus | scoreStatusFilter
+        }}</span>
       </el-table-column>
       <el-table-column width="120" label="更新人">
         <span slot-scope="scope">{{ scope.row.updateName }}</span>
@@ -51,17 +56,6 @@
       </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="resetUserPassword(scope.row)"
-          >
-            重置密码
-          </el-button>
           <el-button
             size="mini"
             type="primary"
@@ -70,6 +64,12 @@
           >
             {{ scope.row.enable ? "禁用" : "启用" }}
           </el-button>
+          <el-button size="mini" type="primary" plain @click="edit(scope.row)">
+            编辑
+          </el-button>
+          <el-button size="mini" type="primary" plain>
+            重新算分
+          </el-button>
         </div>
       </el-table-column>
     </el-table>
@@ -111,7 +111,6 @@ export default {
       currentPage: 1,
       pageSize: 10,
       total: 10,
-      selectedUser: {},
     };
   },
   async created() {},
@@ -136,12 +135,10 @@ export default {
       this.searchForm();
     },
     add() {
-      this.selectedUser = {};
-      this.$refs.userDialog.openDialog();
+      this.$router.push("/exam/edit/");
     },
-    edit(user) {
-      this.selectedUser = user;
-      this.$refs.userDialog.openDialog();
+    edit(exam) {
+      this.$router.push("/exam/edit/" + exam.id);
     },
     async toggleEnableExam(user) {
       await toggleEnableExam({

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

@@ -78,7 +78,7 @@ export default {
       form: {
         code: "",
         name: "",
-        enableState: "",
+        enableState: null,
       },
       tableData: [],
       currentPage: 1,

+ 9 - 0
src/filters/index.js

@@ -35,3 +35,12 @@ Vue.filter("datetimeFilter", function (val) {
   if (val === null) return "";
   return dateFormatForAPI(val);
 });
+
+Vue.filter("scoreStatusFilter", function (val) {
+  if (val === null) return "无";
+  return {
+    NEVER: "从未算分",
+    CALCULATING: "正在算分",
+    FINISH: "算分完成",
+  }[val];
+});

+ 0 - 4
src/main.js

@@ -1,6 +1,5 @@
 import Vue from "vue";
 // 4KB non-zip
-import Navigation from "vue-navigation";
 import App from "./App.vue";
 import router from "./router";
 import store from "./store";
@@ -25,9 +24,6 @@ import "./styles/icons.scss";
 import "./styles/base.scss";
 // styles end
 
-// 可以回退到上次route的状态,不重新执行生命周期函数
-Vue.use(Navigation, { router });
-
 Vue.config.productionTip = false;
 
 if (

+ 9 - 9
src/router/index.js

@@ -75,21 +75,21 @@ const routes = [
     component: Layout,
     children: [
       {
-        path: "exam",
+        path: "list",
         name: "ExamManagement",
         component: () =>
           import(
             /* webpackChunkName: "system" */ "../features/examwork/ExamManagement/ExamManagement.vue"
           ),
       },
-      // {
-      //   path: "org",
-      //   name: "OrgManagement",
-      //   component: () =>
-      //     import(
-      //       /* webpackChunkName: "system" */ "../features/examwork/OrgManagement/OrgManagement.vue"
-      //     ),
-      // },
+      {
+        path: "edit/:id?",
+        name: "ExamEdit",
+        component: () =>
+          import(
+            /* webpackChunkName: "system" */ "../features/examwork/ExamManagement/ExamEdit.vue"
+          ),
+      },
     ],
   },
   {

+ 2 - 4
src/views/Layout/components/NavBar.vue

@@ -48,8 +48,7 @@
 
 <script>
 import {
-  baseMenuConfig,
-  userMenuConfig,
+  systemMenuConfig,
   businessMenuConfig,
   invigilationMenuConfig,
   headerMenuConfig,
@@ -67,8 +66,7 @@ export default {
       navs: headerMenuConfig,
       curNav: "",
       modaleNavs: {
-        base: baseMenuConfig,
-        user: userMenuConfig,
+        systemMenuConfig,
         business: businessMenuConfig,
         invigilation: invigilationMenuConfig,
       },

+ 1 - 1
vue.config.js

@@ -1,7 +1,7 @@
 let proxy = {
   "/api": {
     target: "http://192.168.10.36:6001/",
-    // target: "http://192.168.11.224:6001/backend",
+    // target: "http://192.168.10.86:6001/",
     changeOrigin: true,
   },
 };

+ 0 - 5
yarn.lock

@@ -11098,11 +11098,6 @@ vue-loader@^15.9.2:
     vue-hot-reload-api "^2.3.0"
     vue-style-loader "^4.1.0"
 
-vue-navigation@^1.1.4:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/vue-navigation/-/vue-navigation-1.1.4.tgz#be990190624647daa34b8cd525fad4fb6d472542"
-  integrity sha512-flOvttNizFmnAj73hxrvGEMyOrbUiS7omKOTs+My+7wYdDjcW6JB8emGijy26/HyzcuPaiXbaUjZmXKRPFGfow==
-
 vue-router@^3.3.4:
   version "3.3.4"
   resolved "https://registry.npm.taobao.org/vue-router/download/vue-router-3.3.4.tgz?cache=0&sync_timestamp=1595736697854&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-router%2Fdownload%2Fvue-router-3.3.4.tgz#4e38abc34a11c41b6c3d8244449a2e363ba6250b"