captureDetail.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. <template>
  2. <el-container>
  3. <el-header id="capture-detail-header">
  4. <LinkTitlesCustom :current-paths="currentPaths" />
  5. </el-header>
  6. <el-main style="overflow: unset; margin-left: 20px" class="el-main-padding">
  7. <el-row>
  8. <el-col :span="5">
  9. <img :src="studentBasePhotoPath" alt width="180" />
  10. <img :src="syncCapturePhotoPath" alt class="syncPhto" />
  11. </el-col>
  12. <el-col :span="19">
  13. <el-row>
  14. <el-col :span="8" class="capture-title">
  15. <span>监考数据ID:{{ examRecordDataId }}</span>
  16. </el-col>
  17. <el-col v-if="showAudit" :span="8" style="text-align: center">
  18. <el-button
  19. circle
  20. size="small"
  21. type="primary"
  22. icon="el-icon-d-arrow-left"
  23. title="上一条"
  24. @click="openNextAuditDetail('0')"
  25. ></el-button>
  26. <el-button
  27. size="small"
  28. type="success"
  29. icon="el-icon-success"
  30. title="通过"
  31. @click="auditPass"
  32. >通过</el-button
  33. >
  34. <el-button
  35. size="small"
  36. type="danger"
  37. icon="el-icon-error"
  38. title="不通过"
  39. @click="openAuditDialog"
  40. >不通过</el-button
  41. >
  42. <el-button
  43. circle
  44. size="small"
  45. type="primary"
  46. icon="el-icon-d-arrow-right"
  47. title="下一条"
  48. @click="openNextAuditDetail('1')"
  49. ></el-button>
  50. </el-col>
  51. <el-col v-if="showAudit" :span="8" style="text-align: right">
  52. <el-button
  53. size="small"
  54. icon="el-icon-arrow-left"
  55. type="primary"
  56. @click="back"
  57. >返回</el-button
  58. >
  59. </el-col>
  60. <el-col v-if="!showAudit" :span="16" style="text-align: right">
  61. <el-button
  62. size="small"
  63. icon="el-icon-arrow-left"
  64. type="primary"
  65. @click="back"
  66. >返回</el-button
  67. >
  68. </el-col>
  69. </el-row>
  70. <el-row class="margin-top-10">
  71. <el-col :span="24">
  72. <el-table :data="examAuditData" border>
  73. <el-table-column
  74. sortable
  75. label="学习中心"
  76. prop="orgName"
  77. min-width="110px"
  78. ></el-table-column>
  79. <el-table-column
  80. sortable
  81. label="年级"
  82. prop="grade"
  83. ></el-table-column>
  84. <el-table-column
  85. sortable
  86. label="学号"
  87. prop="studentCode"
  88. min-width="110px"
  89. ></el-table-column>
  90. <el-table-column
  91. sortable
  92. label="身份证号"
  93. prop="identityNumber"
  94. min-width="120px"
  95. ></el-table-column>
  96. <el-table-column
  97. sortable
  98. label="姓名"
  99. prop="studentName"
  100. ></el-table-column>
  101. <el-table-column
  102. sortable
  103. label="课程代码"
  104. prop="courseCode"
  105. min-width="110px"
  106. ></el-table-column>
  107. <el-table-column
  108. sortable
  109. label="课程名称"
  110. prop="courseName"
  111. min-width="110px"
  112. ></el-table-column>
  113. <el-table-column sortable label="课程层次" min-width="110px">
  114. <template slot-scope="scope">
  115. <span v-html="getLevel(scope.row.courseLevel)"></span>
  116. </template>
  117. </el-table-column>
  118. <el-table-column
  119. sortable
  120. label="客观题总分"
  121. prop="objectiveTotalScore"
  122. min-width="150px"
  123. ></el-table-column>
  124. </el-table>
  125. </el-col>
  126. </el-row>
  127. <el-row class="margin-top-20">
  128. <el-col :span="24">
  129. <el-table :data="examAuditData" border>
  130. <el-table-column
  131. sortable
  132. label="切屏次数"
  133. prop="switchScreenCount"
  134. width="110"
  135. ></el-table-column>
  136. <el-table-column
  137. sortable
  138. label="校验(次)"
  139. prop="faceTotalCount"
  140. width="100"
  141. ></el-table-column>
  142. <el-table-column
  143. sortable
  144. label="成功(次)"
  145. prop="faceSuccessCount"
  146. width="100"
  147. ></el-table-column>
  148. <el-table-column
  149. sortable
  150. label="陌生人(次)"
  151. prop="faceStrangerCount"
  152. width="120"
  153. ></el-table-column>
  154. <el-table-column
  155. width="140"
  156. sortable
  157. label="人脸比对(%)"
  158. prop="faceSuccessPercent"
  159. ></el-table-column>
  160. <el-table-column
  161. width="140"
  162. sortable
  163. label="人脸真实性(%)"
  164. prop="baiduFaceLivenessSuccessPercent"
  165. ></el-table-column>
  166. <el-table-column
  167. width="120"
  168. sortable
  169. label="虚拟设备"
  170. prop="virtualCameraNames"
  171. ></el-table-column>
  172. <el-table-column
  173. sortable
  174. label="违纪类型"
  175. prop="disciplineType"
  176. width="130"
  177. ></el-table-column>
  178. <el-table-column sortable label="违纪说明" width="260">
  179. <template slot-scope="scope">
  180. <span
  181. v-html="disciplineTypeFilter(scope.row.disciplineDetail)"
  182. ></span>
  183. </template>
  184. </el-table-column>
  185. <el-table-column
  186. width="120"
  187. sortable
  188. label="审核结果"
  189. prop="status"
  190. ></el-table-column>
  191. </el-table>
  192. </el-col>
  193. </el-row>
  194. <el-row class="margin-top-10 photorow">
  195. <el-col
  196. v-for="item in capturesList"
  197. :key="item.id"
  198. :span="6"
  199. class="photocol"
  200. >
  201. <div v-show="item.pass" class="photo-pass">通过</div>
  202. <div v-show="!item.pass" class="photo-nopass">不通过</div>
  203. <img class="photo" :src="item.fileUrl" alt width="200" />
  204. <div v-show="item.stranger" class="photo-stranger">陌生人</div>
  205. <div
  206. v-show="!item.isFacelivenessPass"
  207. class="photo-facelivenessPass"
  208. >
  209. <i class="el-icon-warning" title="真实性不通过"></i>
  210. </div>
  211. </el-col>
  212. </el-row>
  213. <el-row class="margin-top-20">
  214. <el-col :span="24">
  215. <el-table :data="examProcessRecordData" border>
  216. <el-table-column
  217. sortable
  218. label="考试过程"
  219. prop="processName"
  220. width="200"
  221. ></el-table-column>
  222. <el-table-column
  223. sortable
  224. label="日期"
  225. prop="recordTime"
  226. width="200"
  227. ></el-table-column>
  228. <el-table-column
  229. sortable
  230. label="客户端ip"
  231. prop="sourceIp"
  232. ></el-table-column>
  233. </el-table>
  234. </el-col>
  235. </el-row>
  236. </el-col>
  237. </el-row>
  238. <el-dialog title="审核" :visible.sync="dialogFormVisible">
  239. <el-form ref="auditForm" :model="auditForm">
  240. <el-form-item
  241. label="违纪类型"
  242. prop="illegallyTypeId"
  243. :rules="[
  244. { required: true, message: '请选择违纪类型', trigger: 'change' },
  245. ]"
  246. >
  247. <el-select
  248. v-model="auditForm.illegallyTypeId"
  249. filterable
  250. remote
  251. :remote-method="getDisciplineTypeList"
  252. clearable
  253. placeholder="请选择"
  254. size="small"
  255. @clear="getDisciplineTypeList"
  256. >
  257. <el-option
  258. v-for="item in disciplineTypeList"
  259. :key="item.id"
  260. :label="item.name"
  261. :value="item.id"
  262. ></el-option>
  263. </el-select>
  264. </el-form-item>
  265. <el-form-item label="详情描述" style="margin-top: 15px">
  266. <el-input
  267. v-model="auditForm.disciplineDetail"
  268. type="textarea"
  269. :autosize="{ minRows: 6, maxRows: 10 }"
  270. placeholder="请输入内容"
  271. ></el-input>
  272. </el-form-item>
  273. <div class="dialog-footer margin-top-10 text-center">
  274. <el-button type="primary" @click="doAuditNoPass">确 定</el-button>
  275. <el-button @click="dialogFormVisible = false">取 消</el-button>
  276. </div>
  277. </el-form>
  278. </el-dialog>
  279. </el-main>
  280. </el-container>
  281. </template>
  282. <script>
  283. import { mapState } from "vuex";
  284. import LinkTitlesCustom from "@/components/LinkTitlesCustom.vue";
  285. export default {
  286. components: { LinkTitlesCustom },
  287. data() {
  288. return {
  289. examRecordDataId: "",
  290. examAuditData: [],
  291. examRecordData: [],
  292. capturesList: [],
  293. studentBasePhotoPath: "",
  294. syncCapturePhotoPath: "",
  295. showAudit: false,
  296. dialogFormVisible: false,
  297. auditForm: {
  298. examRecordDataId: null,
  299. illegallyTypeId: null,
  300. disciplineDetail: "",
  301. isPass: null,
  302. },
  303. currentPaths: ["抓拍详情"],
  304. disciplineTypeList: [],
  305. pendingOperate: false,
  306. searchParam: {},
  307. orderDesc: true,
  308. first: false,
  309. last: false,
  310. examProcessRecordData: [],
  311. };
  312. },
  313. computed: {
  314. ...mapState({ user: (state) => state.user }),
  315. },
  316. created() {
  317. this.examRecordDataId = this.$route.params.examRecordDataId;
  318. this.pendingOperate = this.$route.query.pendingOperate;
  319. if (this.$route.query.searchParam) {
  320. this.searchParam = JSON.parse(this.$route.query.searchParam);
  321. }
  322. var fromPage = this.$route.params.from;
  323. var currentPathJson = {
  324. illegalityNameList: ["违纪名单", "抓拍详情"],
  325. awaitingAudit: ["监考待审", "抓拍详情"],
  326. alreadyAudited: ["监考已审", "抓拍详情"],
  327. examDetail: ["考试明细", "抓拍详情"],
  328. };
  329. this.currentPaths = currentPathJson[fromPage];
  330. this.getExamAuditData();
  331. this.listExamCapture();
  332. this.getDisciplineTypeList("");
  333. this.getExamProcessRecordData();
  334. },
  335. methods: {
  336. getDisciplineTypeList(name) {
  337. if (!name) {
  338. name = "";
  339. }
  340. this.$http
  341. .get("/api/ecs_oe_admin/illegallyType/queryByNameLike", {
  342. params: { name, queryScope: "audit" },
  343. })
  344. .then((response) => {
  345. this.disciplineTypeList = response.data;
  346. });
  347. },
  348. getExamAuditData() {
  349. var param = new URLSearchParams({
  350. examRecordDataId: this.examRecordDataId,
  351. });
  352. let isPendingAudit = this.pendingOperate;
  353. this.$http
  354. .post("/api/ecs_oe_admin/exam/capture/audit/detail", param)
  355. .then((response) => {
  356. if (response.data) {
  357. this.showAudit =
  358. isPendingAudit == "false" &&
  359. response.data.isWarn &&
  360. !response.data.isAudit;
  361. // if (response.data.virtualCameraNames === "") {
  362. // response.data.virtualCameraNames = ";";
  363. // }
  364. this.examAuditData = new Array(response.data);
  365. var studentId = response.data.studentId;
  366. this.syncCapturePhotoPath = response.data.syncCaptureFileUrl;
  367. this.getStudentInfo(studentId);
  368. }
  369. });
  370. },
  371. getExamProcessRecordData() {
  372. // var param = new URLSearchParams({
  373. // examRecordDataId: this.examRecordDataId,
  374. // });
  375. let url =
  376. "/api/ecs_oe_admin/exam/capture/getExamProcessRecords?examRecordDataId=" +
  377. this.examRecordDataId;
  378. this.$http.get(url).then((response) => {
  379. if (response.data) {
  380. this.examProcessRecordData = response.data;
  381. }
  382. });
  383. },
  384. listExamCapture() {
  385. var param = new URLSearchParams({
  386. examRecordDataId: this.examRecordDataId,
  387. });
  388. this.$http
  389. .post("/api/ecs_oe_admin/exam/capture/list", param)
  390. .then((response) => {
  391. this.capturesList = response.data;
  392. });
  393. },
  394. getStudentInfo(studentId) {
  395. this.$http
  396. .get("/api/ecs_core/student/getStudentInfo?studentId=" + studentId)
  397. .then((response) => {
  398. if (response.data.photoPath) {
  399. this.studentBasePhotoPath = response.data.photoPath;
  400. }
  401. });
  402. },
  403. /**
  404. * 审核通过
  405. */
  406. auditPass() {
  407. var auditParams = {
  408. examRecordDataId: this.examRecordDataId,
  409. isPass: true,
  410. };
  411. var param = new URLSearchParams(auditParams);
  412. this.$http
  413. .post("/api/ecs_oe_admin/exam/audit/single/audit", param)
  414. .then(() => {
  415. this.$notify({
  416. title: "成功",
  417. message: "操作成功",
  418. type: "success",
  419. });
  420. this.auditNext();
  421. });
  422. },
  423. auditNext() {
  424. var url =
  425. "/api/ecs_oe_admin/exam/record/waiting/audit/next?examRecordDataId=" +
  426. this.examRecordDataId +
  427. "&next=1";
  428. this.$http.post(url, this.searchParam).then((response) => {
  429. if (response.data) {
  430. this.examRecordDataId = response.data;
  431. this.getExamAuditData();
  432. this.listExamCapture();
  433. this.getDisciplineTypeList("");
  434. this.getExamProcessRecordData();
  435. } else {
  436. this.$notify({
  437. title: "成功",
  438. message: "审核完毕",
  439. type: "success",
  440. });
  441. this.back();
  442. }
  443. });
  444. },
  445. /**
  446. * 下一条
  447. */
  448. openNextAuditDetail(next) {
  449. var url =
  450. "/api/ecs_oe_admin/exam/record/waiting/audit/next?examRecordDataId=" +
  451. this.examRecordDataId +
  452. "&next=" +
  453. next;
  454. this.$http.post(url, this.searchParam).then((response) => {
  455. if (response.data) {
  456. this.examRecordDataId = response.data;
  457. this.getExamAuditData();
  458. this.listExamCapture();
  459. this.getDisciplineTypeList("");
  460. this.getExamProcessRecordData();
  461. } else {
  462. var msg = "最后";
  463. if (next === "0") {
  464. msg = "第";
  465. }
  466. this.$notify({
  467. title: "警告",
  468. message: "当前数据为" + msg + "一条",
  469. type: "warning",
  470. });
  471. }
  472. });
  473. },
  474. openAuditDialog() {
  475. this.dialogFormVisible = true;
  476. this.auditForm = {
  477. examRecordDataId: this.examRecordDataId,
  478. illegallyTypeId: null,
  479. disciplineDetail: "",
  480. isPass: false,
  481. };
  482. },
  483. doAuditNoPass() {
  484. this.$refs["auditForm"].validate((valid) => {
  485. if (valid) {
  486. var param = new URLSearchParams(this.auditForm);
  487. this.$http
  488. .post("/api/ecs_oe_admin/exam/audit/single/audit", param)
  489. .then(() => {
  490. this.$notify({
  491. title: "成功",
  492. message: "操作成功",
  493. type: "success",
  494. });
  495. this.dialogFormVisible = false;
  496. this.auditNext();
  497. });
  498. } else {
  499. return false;
  500. }
  501. });
  502. },
  503. back() {
  504. this.$router.back();
  505. },
  506. disciplineTypeFilter: function (value) {
  507. if (value && value.indexOf("&&") > -1) {
  508. var arr = value.split("&&");
  509. var detail = "";
  510. for (var i = 0; i < arr.length; i++) {
  511. detail += arr[i] + "<br>";
  512. }
  513. return detail;
  514. } else {
  515. return value;
  516. }
  517. },
  518. getLevel(level) {
  519. if (level == "ZSB") {
  520. return "专升本";
  521. } else if (level == "GQZ") {
  522. return "高起专";
  523. } else if (level == "GQB") {
  524. return "高起本";
  525. } else {
  526. return "不限";
  527. }
  528. },
  529. },
  530. };
  531. </script>
  532. <style scoped>
  533. #capture-detail-header {
  534. height: 15px !important;
  535. }
  536. .capture-title {
  537. font-size: 20px;
  538. font-weight: bold;
  539. }
  540. .photorow {
  541. border: 1px solid #ebeef5;
  542. text-align: center;
  543. }
  544. .photocol {
  545. position: relative;
  546. margin: 20px 10px 0 10px;
  547. }
  548. .photo {
  549. display: block;
  550. width: 208px;
  551. height: 159px;
  552. object-fit: contain;
  553. }
  554. .photo-pass {
  555. position: absolute;
  556. top: 2px;
  557. width: 208px;
  558. text-align: center;
  559. color: white;
  560. font-size: 12px;
  561. background-color: rgba(10, 10, 10, 0.4);
  562. }
  563. .photo-nopass {
  564. position: absolute;
  565. top: 2px;
  566. width: 208px;
  567. text-align: center;
  568. color: red;
  569. font-size: 12px;
  570. background-color: rgba(10, 10, 10, 0.4);
  571. }
  572. .photo-stranger {
  573. position: absolute;
  574. top: 140px;
  575. width: 208px;
  576. text-align: center;
  577. color: red;
  578. font-size: 12px;
  579. background-color: rgba(10, 10, 10, 0.4);
  580. }
  581. .photo-facelivenessPass {
  582. position: absolute;
  583. top: 160px;
  584. width: 208px;
  585. font-size: 14px;
  586. text-align: center;
  587. color: red;
  588. }
  589. .syncPhto {
  590. margin-top: 30px;
  591. display: block;
  592. width: 180px;
  593. height: 159px;
  594. object-fit: contain;
  595. }
  596. </style>
  597. <style scoped src="../style/common.css"></style>