123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465 |
- <template>
- <div class="list">
- <table>
- <tbody class="list-row">
- <tr class="list-header qm-primary-strong-text">
- <td>课程</td>
- <td v-if="!isEpcc" key="cc">层次</td>
- <td v-if="!isEpcc" key="zy">专业</td>
- <td>考试开放时间</td>
- <td>剩余考试次数</td>
- <td style="max-width: 200px">操作</td>
- </tr>
- <tr v-for="course in courses" :key="course.examId + course.courseId">
- <td>{{ course.courseName }}</td>
- <td v-if="!isEpcc" key="cc">{{ course.courseLevel }}</td>
- <td v-if="!isEpcc" key="zy">{{ course.specialtyName }}</td>
- <td>
- {{ course.startTime }} <br />
- ~ <br />
- {{ course.endTime }}
- </td>
- <td>{{ course.allowExamCount }}</td>
- <td style="min-width: 180px">
- <div
- style="display: grid; grid-template-columns: repeat( auto-fit, minmax(100px, 1fr) );
- grid-gap: 10px"
- >
- <i-button
- class="qm-primary-button qm-primary-button-padding-fix"
- :disabled="disableTheCourse(course)"
- @click="
- () => {
- if (isEpcc) {
- raceEnter(course);
- } else {
- enterExam(course);
- }
- }
- "
- >
- 进入考试{{ isEpcc && countdown > 0 ? `(${countdown})` : "" }}
- </i-button>
- <i-poptip
- v-if="!isEpcc"
- :trigger="course.isObjScoreView ? 'hover' : 'click'"
- placement="left"
- class="online-exam-list-override-poptip"
- @on-popper-show="cid = course.courseId"
- @on-popper-hide="cid = null"
- >
- <i-button
- class="qm-primary-button qm-primary-button-padding-fix"
- style="width: 100%"
- :disabled="!course.isObjScoreView"
- >
- 客观分
- </i-button>
- <ecs-online-exam-result-list
- slot="content"
- :popper-show="cid === course.courseId"
- :exam-student-id="course.examStudentId"
- ></ecs-online-exam-result-list>
- </i-poptip>
- </div>
- </td>
- </tr>
- </tbody>
- </table>
- <Spin v-if="spinShow" size="large" fix>{{ processingMessage }}</Spin>
- <OnlineExamFaceCheckModal
- :open="faceCheckModalOpen"
- :course="selectedCourse"
- ></OnlineExamFaceCheckModal>
- <Modal
- ref="checkEnvModal"
- v-model="shouldShowCheckEnvModal"
- title="环境检测"
- footer-hide
- width="800"
- :closable="false"
- :mask-closable="false"
- >
- <CheckComputer
- v-if="shouldShowCheckEnvModal"
- @on-close="resumeEnterExam"
- />
- </Modal>
- </div>
- </template>
- <script>
- import { createNamespacedHelpers } from "vuex";
- import OnlineExamResultList from "./OnlineExamResultList.vue";
- import OnlineExamFaceCheckModal from "./OnlineExamFaceCheckModal.vue";
- import moment from "moment";
- import {
- mapState as globalMapState,
- mapGetters as globalMapGetters,
- } from "vuex";
- const { mapState, mapMutations } = createNamespacedHelpers("examHomeModule");
- import CheckComputer from "./CheckComputer";
- export default {
- name: "EcsOnlineList",
- components: {
- "ecs-online-exam-result-list": OnlineExamResultList,
- OnlineExamFaceCheckModal,
- CheckComputer,
- },
- props: {
- courses: {
- type: Array,
- default() {
- return [];
- },
- },
- },
- data() {
- return {
- now: new Date(),
- selectedCourse: null,
- spinShow: false,
- processingMessage: "",
- cid: null,
- shouldShowCheckEnvModal: false,
- countdown: 0,
- };
- },
- computed: {
- ...globalMapState(["user", "timeDifference"]),
- ...mapState(["faceCheckModalOpen"]),
- ...globalMapGetters(["isEpcc"]),
- },
- created() {
- this.getNow();
- this.intervalID = setInterval(() => this.getNow(), 1000);
- },
- beforeDestroy() {
- this.toggleFaceCheckModal(false);
- clearInterval(this.intervalID);
- clearInterval(this.countdownInterval);
- },
- methods: {
- ...mapMutations(["toggleFaceCheckModal"]),
- getNow() {
- this.now = Date.now() + this.timeDifference;
- },
- courseInBetween(course) {
- return moment(this.now).isBetween(
- moment(course.startTime),
- moment(course.endTime)
- );
- },
- disableTheCourse(course) {
- return (
- !this.courseInBetween(course) ||
- course.allowExamCount < 1 ||
- (this.isEpcc && this.countdown > 0)
- );
- },
- raceEnter(course) {
- const successRatePerMinute = [
- 0.1,
- 0.15,
- 0.2,
- 0.25,
- 0.3,
- 0.35,
- 0.4,
- 0.45,
- 0.5,
- 0.6,
- 0.7,
- 0.8,
- 0.9,
- 1,
- ]; // 30秒步进
- const minutesAfterCourseStart = Math.floor(
- moment(this.getNow()).diff(moment(course.startTime), "seconds") / 30
- );
- let idx = 0;
- if (minutesAfterCourseStart < 0) {
- idx = 0;
- } else if (
- minutesAfterCourseStart >= 0 &&
- minutesAfterCourseStart < successRatePerMinute.length
- ) {
- idx = minutesAfterCourseStart;
- } else {
- idx = successRatePerMinute.length - 1;
- }
- if (Math.random() > 1 - successRatePerMinute[idx]) {
- if (minutesAfterCourseStart < 120) {
- window._hmt.push([
- "_trackEvent",
- "在线考试列表页面",
- "摇号进入考试-courseId-" + course.examId,
- minutesAfterCourseStart / 2 + "分进入",
- ]);
- } else {
- window._hmt.push([
- "_trackEvent",
- "在线考试列表页面",
- "摇号进入考试-courseId-" + course.examId,
- "60分后进入",
- ]);
- }
- this.enterExam(course);
- // return true;
- } else {
- this.$Modal.warning({
- title: "提示",
- content: "考试人员过多,请稍等2分钟再试。",
- onOk: () => {
- clearInterval(this.countdownInterval);
- this.countdown = 10;
- this.countdownInterval = setInterval(() => {
- this.countdown--;
- }, 1 * 1000);
- // this.enterExam(course);
- },
- });
- // this.$Message.warning({
- // content: "考试人员过多,请稍等2分钟再试",
- // duration: 10,
- // closable: true,
- // });
- // return false;
- }
- },
- async enterExam(course, alreadyChecked) {
- this.spinShow = true;
- this.processingMessage = "正在检测断点续考信息...";
- try {
- const alreadyInExam = await this.checkExamInProgress();
- if (alreadyInExam) {
- this.spinShow = false;
- window._hmt.push([
- "_trackEvent",
- "在线考试列表页面",
- "断点续考",
- "进入",
- ]);
- return;
- }
- } catch (error) {
- this.spinShow = false;
- return;
- }
- this.spinShow = true;
- this.processingMessage = "正在检测IP合法性...";
- try {
- const ipLimit = (await this.$http.get(
- "/api/ecs_exam_work/exam/ipLimit/" + course.examId
- )).data;
- // sleep function: await new Promise(resolve => setTimeout(() => resolve(), 3000));
- if (ipLimit.limited) {
- window._hmt.push(["_trackEvent", "在线考试列表页面", "IP受限"]);
- this.spinShow = false;
- this.$Message.error({
- content: "IP受限,请到中心指定地点进行考试!",
- duration: 15,
- closable: true,
- });
- return;
- }
- } catch (error) {
- this.$Message.error({
- content: "查询IP限制出错!",
- duration: 15,
- closable: true,
- });
- this.spinShow = false;
- return;
- }
- this.processingMessage = "正在获取考试设置...";
- if (!alreadyChecked) {
- let checkEnv = null;
- try {
- checkEnv = await this.$http.get(
- "/api/ecs_exam_work/exam/examOrgPropertyFromCache4StudentSession/" +
- course.examId +
- `/CHECK_ENVIRONMENT`
- );
- if (checkEnv.data.CHECK_ENVIRONMENT === "true") {
- const skipCheck = await new Promise(resolve => {
- this.$Modal.confirm({
- title: "进行环境检测",
- content:
- "环境检测可以检测电脑的硬件配置、网络速度和常用操作。环境检测不通过的话,可能影响考试的正常进行。",
- okText: "进行检测",
- cancelText: "跳过检测",
- onOk: () => {
- // sessionStorage.setItem(
- // "computer_env_ok_save_course_id",
- // course.courseId
- // );
- // this.$router.push("/check-computer");
- // this.$refs.checkEnvModal.show();
- this.shouldShowCheckEnvModal = true;
- this.selectedCourse = course;
- resolve();
- },
- onCancel: () => {
- resolve(true);
- },
- });
- });
- if (!skipCheck) {
- this.spinShow = false;
- return;
- }
- }
- } catch (error) {
- this.spinShow = false;
- this.$Message.error({
- content: "查询考试的环境检测设置属性出错!",
- duration: 15,
- closable: true,
- });
- return;
- }
- }
- if (course.faceEnable) {
- // if 人脸检测 && 没有底照,提示,并返回
- if (!this.user.photoPath) {
- this.spinShow = false;
- window._hmt.push(["_trackEvent", "在线考试列表页面", "无底照"]);
- this.$Message.info(
- "本场考试需要进行人脸检测,但是您没有上传底照,请联系老师!"
- );
- return;
- }
- let faceLiveness = null;
- try {
- faceLiveness = await this.$http.get(
- "/api/ecs_exam_work/exam/examOrgPropertyFromCache4StudentSession/" +
- course.examId +
- `/IS_FACE_VERIFY`
- );
- } catch (error) {
- this.spinShow = false;
- this.$Message.error({
- content: "查询考试的人脸检测设置属性出错!",
- duration: 15,
- closable: true,
- });
- return;
- }
- if (
- faceLiveness.data.IS_FACE_VERIFY &&
- JSON.parse(faceLiveness.data.IS_FACE_VERIFY)
- ) {
- this.processingMessage = "正在检测底照是否满足活体检测标准...";
- let checkBasePhoto;
- try {
- checkBasePhoto = (await this.$http.get(
- "/api/ecs_oe_student/examFaceLivenessVerify/checkFaceLiveness" +
- `?${this.user.token}` // 考生采用相同的机器考试,使用不同的请求的缓存
- )).data;
- this.spinShow = false;
- if (!checkBasePhoto.success) {
- this.$Message.error(
- "您上传的底照不符合活体检测的要求,请联系老师!"
- );
- return;
- }
- } catch (error) {
- this.spinShow = false;
- this.$Message.error({
- content: "查询检测底照是否满足活体检测标准的接口出错!",
- duration: 15,
- closable: true,
- });
- return;
- }
- }
- this.spinShow = false;
- // open face check modal, then
- // if 人脸识别失败 && 考试开启强制人脸识别 return
- // if 人脸识别失败 && 考试未开启强制人脸识别
- // 让学生手动确认进入考试,若取消,则返回
- this.selectedCourse = course;
- window._hmt.push([
- "_trackEvent",
- "在线考试列表页面",
- "人脸识别框",
- "弹出框",
- ]);
- this.toggleFaceCheckModal(true);
- } else {
- this.spinShow = false;
- window._hmt.push([
- "_trackEvent",
- "在线考试列表页面",
- "进入考试",
- "无人脸检测",
- ]);
- this.$router.push(
- `/online-exam/exam/${course.examId}/overview?examStudentId=${course.examStudentId}`
- );
- }
- },
- // eslint-disable-next-line
- async faceCheckResultCallback(course, faceMatched) {
- // if faceMatched
- },
- async resumeEnterExam() {
- this.shouldShowCheckEnvModal = false;
- this.enterExam(this.selectedCourse, true);
- },
- },
- // watch: {
- // courses(value) {
- // if (value.length > 0) {
- // let courseId = sessionStorage.getItem("computer_env_ok_save_course_id");
- // if (courseId !== null) {
- // courseId = +courseId; // 转为数字
- // const course = value.find(v => v.courseId === courseId);
- // if (course) {
- // this.enterExam(course, true);
- // sessionStorage.removeItem("computer_env_ok_save_course_id");
- // }
- // }
- // }
- // },
- // },
- };
- </script>
- <style scoped>
- .list {
- border: 1px solid #eeeeee;
- border-radius: 6px;
- }
- .list table {
- width: 100%;
- border-collapse: collapse !important;
- border-spacing: 0;
- }
- .list td {
- border: 1px solid #eeeeee;
- border-radius: 6px;
- border-collapse: separate !important;
- padding: 10px;
- }
- </style>
- <style>
- .online-exam-list-override-poptip .ivu-poptip-rel {
- width: 100%;
- }
- </style>
|