ScoreCheckDetail.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. <template>
  2. <el-dialog
  3. class="mark-detail"
  4. :visible.sync="modalIsShow"
  5. top="0"
  6. :close-on-click-modal="false"
  7. :close-on-press-escape="false"
  8. :show-close="false"
  9. append-to-body
  10. fullscreen
  11. @open="initData"
  12. >
  13. <div slot="title">
  14. <h2 class="el-dialog__title">成绩详情</h2>
  15. <span
  16. >课程名称:{{ instance.courseName }}({{ instance.courseCode }})</span
  17. >
  18. <span>试卷编号:{{ instance.paperNumber }}</span>
  19. <button class="el-dialog__headerbtn" @click="cancel"></button>
  20. </div>
  21. <div class="part-box part-box-filter part-box-flex">
  22. <el-form ref="FilterForm" label-position="left" label-width="85px" inline>
  23. <el-form-item label="学院">
  24. <el-input v-model.trim="filter.college" placeholder="学院" clearable>
  25. </el-input>
  26. </el-form-item>
  27. <el-form-item label="专业">
  28. <el-input
  29. v-model.trim="filter.majorName"
  30. placeholder="专业"
  31. clearable
  32. >
  33. </el-input>
  34. </el-form-item>
  35. <el-form-item label="教学班">
  36. <el-input
  37. v-model.trim="filter.teachClassName"
  38. placeholder="教学班"
  39. clearable
  40. >
  41. </el-input>
  42. </el-form-item>
  43. <el-form-item label="行政班">
  44. <el-input
  45. v-model.trim="filter.className"
  46. placeholder="行政班"
  47. clearable
  48. >
  49. </el-input>
  50. </el-form-item>
  51. <el-form-item label="任课老师">
  52. <el-input
  53. v-model.trim="filter.teacher"
  54. placeholder="任课老师"
  55. clearable
  56. >
  57. </el-input>
  58. </el-form-item>
  59. <el-form-item label="复核条件">
  60. <el-select v-model="filter.filter" placeholder="复核条件">
  61. <el-option :value="0" label="无"></el-option>
  62. <el-option :value="1" label="客观题0分"></el-option>
  63. <el-option :value="2" label="客观题0分,主观题有分"></el-option>
  64. <el-option :value="3" label="主观题0分,客观题有分"></el-option>
  65. </el-select>
  66. </el-form-item>
  67. <el-form-item label="状态">
  68. <el-select v-model="filter.status" placeholder="状态" clearable>
  69. <el-option value="ABSENT" label="缺考"></el-option>
  70. <el-option value="NORMAL" label="正常"></el-option>
  71. <el-option value="UNEXISTS" label="未扫描"></el-option>
  72. </el-select>
  73. </el-form-item>
  74. <el-form-item label="是否违纪">
  75. <el-select v-model="filter.breach" placeholder="是否违纪" clearable>
  76. <el-option :value="1" label="违纪"></el-option>
  77. <el-option :value="0" label="正常"></el-option>
  78. </el-select>
  79. </el-form-item>
  80. <el-form-item label="试卷总分">
  81. <el-input-number
  82. v-model="filter.startScore"
  83. placeholder="试卷总分"
  84. :min="0"
  85. :max="9999"
  86. :step="0.01"
  87. step-strictly
  88. :controls="false"
  89. clearable
  90. >
  91. </el-input-number>
  92. <span class="mlr-1">-</span>
  93. <el-input-number
  94. v-model="filter.endScore"
  95. placeholder="试卷总分"
  96. :min="0"
  97. :max="9999"
  98. :step="0.01"
  99. step-strictly
  100. :controls="false"
  101. clearable
  102. >
  103. </el-input-number>
  104. </el-form-item>
  105. <el-form-item label="客观题总分">
  106. <el-input-number
  107. v-model="filter.objectiveStartScore"
  108. placeholder="客观题总分"
  109. :min="0"
  110. :max="9999"
  111. :step="0.01"
  112. step-strictly
  113. :controls="false"
  114. clearable
  115. >
  116. </el-input-number>
  117. <span class="mlr-1">-</span>
  118. <el-input-number
  119. v-model="filter.objectiveEndScore"
  120. placeholder="客观题总分"
  121. :min="0"
  122. :max="9999"
  123. :step="0.01"
  124. step-strictly
  125. :controls="false"
  126. clearable
  127. >
  128. </el-input-number>
  129. </el-form-item>
  130. <el-form-item label="主观题总分">
  131. <el-input-number
  132. v-model="filter.subjectiveStartScore"
  133. placeholder="主观题总分"
  134. :min="0"
  135. :max="9999"
  136. :step="0.01"
  137. step-strictly
  138. :controls="false"
  139. clearable
  140. >
  141. </el-input-number>
  142. <span class="mlr-1">-</span>
  143. <el-input-number
  144. v-model="filter.subjectiveEndScore"
  145. placeholder="主观题总分"
  146. :min="0"
  147. :max="9999"
  148. :step="0.01"
  149. step-strictly
  150. :controls="false"
  151. clearable
  152. >
  153. </el-input-number>
  154. </el-form-item>
  155. <el-form-item label="小题得分">
  156. <el-input-number
  157. v-model="filter.subScore"
  158. placeholder="小题得分"
  159. :min="0"
  160. :max="9999"
  161. :step="0.01"
  162. step-strictly
  163. :controls="false"
  164. clearable
  165. >
  166. </el-input-number>
  167. </el-form-item>
  168. <el-form-item label="客观分">
  169. <el-input-number
  170. v-model="filter.objectiveScoreRateLt"
  171. placeholder="客观分小于X%"
  172. :min="1"
  173. :max="100"
  174. :step="1"
  175. step-strictly
  176. :controls="false"
  177. clearable
  178. >
  179. </el-input-number>
  180. </el-form-item>
  181. <el-form-item label="姓名">
  182. <el-input
  183. v-model.trim="filter.studentName"
  184. placeholder="姓名"
  185. clearable
  186. >
  187. </el-input>
  188. </el-form-item>
  189. <el-form-item label="学号">
  190. <el-input
  191. v-model.trim="filter.studentCode"
  192. placeholder="学号"
  193. clearable
  194. >
  195. </el-input>
  196. </el-form-item>
  197. <el-form-item label="密号">
  198. <el-input
  199. v-model.trim="filter.secretNumber"
  200. placeholder="密号"
  201. clearable
  202. >
  203. </el-input>
  204. </el-form-item>
  205. <el-form-item label-width="0px">
  206. <el-button type="primary" @click="search">查询</el-button>
  207. </el-form-item>
  208. </el-form>
  209. <div class="part-box-action">
  210. <el-button
  211. class="mr-2"
  212. type="primary"
  213. :loading="loading"
  214. @click="toCalcObjective"
  215. >客观题统分</el-button
  216. >
  217. <el-dropdown
  218. v-if="
  219. checkPrivilege('button', 'BatchInspectedSubjective') ||
  220. checkPrivilege('button', 'BatchObjectiveSubjective')
  221. "
  222. :disabled="!multipleSelection.length"
  223. @command="toBatchCheck"
  224. >
  225. <el-button type="primary" :disabled="!multipleSelection.length">
  226. 批量复核<i class="el-icon-arrow-down el-icon--right"></i>
  227. </el-button>
  228. <el-dropdown-menu slot="dropdown">
  229. <el-dropdown-item
  230. v-if="checkPrivilege('button', 'BatchInspectedSubjective')"
  231. command="subjective"
  232. >主观题复核</el-dropdown-item
  233. >
  234. <el-dropdown-item
  235. v-if="checkPrivilege('button', 'BatchObjectiveSubjective')"
  236. command="objective"
  237. >客观题复核</el-dropdown-item
  238. >
  239. </el-dropdown-menu>
  240. </el-dropdown>
  241. <el-button
  242. v-if="checkPrivilege('button', 'ScoreExport')"
  243. class="ml-2"
  244. type="success"
  245. :loading="downloading"
  246. :disabled="!canExport"
  247. @click="toExportScore"
  248. >成绩导出</el-button
  249. >
  250. </div>
  251. </div>
  252. <div class="part-box part-box-pad">
  253. <el-table
  254. ref="TableList"
  255. :data="dataList"
  256. @selection-change="handleSelectionChange"
  257. @sort-change="sortChange"
  258. >
  259. <el-table-column
  260. type="selection"
  261. fixed="left"
  262. width="55"
  263. align="center"
  264. ></el-table-column>
  265. <el-table-column prop="studentName" label="姓名" min-width="150">
  266. </el-table-column>
  267. <el-table-column
  268. prop="studentCode"
  269. label="学号"
  270. width="150"
  271. sortable
  272. ></el-table-column>
  273. <el-table-column
  274. prop="secretNumber"
  275. label="密号"
  276. width="120"
  277. ></el-table-column>
  278. <el-table-column
  279. prop="studentStatusDisplay"
  280. label="状态"
  281. width="80"
  282. ></el-table-column>
  283. <el-table-column
  284. prop="breachDisplay"
  285. label="违纪状态"
  286. width="80"
  287. ></el-table-column>
  288. <el-table-column
  289. prop="paperType"
  290. label="卷型"
  291. width="80"
  292. ></el-table-column>
  293. <el-table-column
  294. prop="objectiveScore"
  295. label="客观分"
  296. width="85"
  297. sortable
  298. ></el-table-column>
  299. <el-table-column
  300. prop="subjectiveScore"
  301. label="主观分"
  302. width="85"
  303. sortable
  304. ></el-table-column>
  305. <el-table-column
  306. prop="totalScore"
  307. label="总分"
  308. width="80"
  309. sortable
  310. ></el-table-column>
  311. <el-table-column
  312. prop="subjectiveScoreList"
  313. label="主观题明细"
  314. min-width="300"
  315. ></el-table-column>
  316. <el-table-column
  317. prop="college"
  318. label="学院"
  319. min-width="200"
  320. sortable
  321. ></el-table-column>
  322. <el-table-column
  323. prop="majorName"
  324. label="专业"
  325. min-width="200"
  326. sortable
  327. ></el-table-column>
  328. <el-table-column
  329. prop="teachClassName"
  330. label="教学班"
  331. min-width="160"
  332. sortable
  333. ></el-table-column>
  334. <el-table-column
  335. prop="className"
  336. label="行政班"
  337. min-width="200"
  338. sortable
  339. ></el-table-column>
  340. <el-table-column prop="checkUserName" label="复核人" min-width="140">
  341. <span slot-scope="scope"
  342. >{{ scope.row.checkUserName | defaultFieldFilter }}({{
  343. scope.row.checkUserLoginName | defaultFieldFilter
  344. }})</span
  345. ></el-table-column
  346. >
  347. <el-table-column prop="checkTime" label="复核时间" width="170">
  348. <span slot-scope="scope">{{
  349. scope.row.checkTime | timestampFilter
  350. }}</span>
  351. </el-table-column>
  352. <el-table-column
  353. class-name="action-column"
  354. label="操作"
  355. width="260"
  356. fixed="right"
  357. >
  358. <template v-if="checkActionValid(scope.row)" slot-scope="scope">
  359. <el-button
  360. class="btn-primary"
  361. type="text"
  362. @click="toViewSheetPaper(scope.row)"
  363. >原图</el-button
  364. >
  365. <el-button
  366. class="btn-primary"
  367. type="text"
  368. @click="toViewTrack(scope.row)"
  369. >轨迹图</el-button
  370. >
  371. <el-button
  372. v-if="checkPrivilege('link', 'InspectedSubjective')"
  373. class="btn-primary"
  374. type="text"
  375. :disabled="!scope.row.subjectiveCheckFlag"
  376. @click="toCheckQuestion(scope.row, 'subjective')"
  377. >主观题复核</el-button
  378. >
  379. <el-button
  380. v-if="checkPrivilege('link', 'ObjectiveSubjective')"
  381. class="btn-primary"
  382. type="text"
  383. :disabled="!scope.row.objectiveCheckFlag"
  384. @click="toCheckQuestion(scope.row, 'objective')"
  385. >客观题复核</el-button
  386. >
  387. </template>
  388. </el-table-column>
  389. </el-table>
  390. <div class="part-page">
  391. <el-pagination
  392. background
  393. layout="total, sizes, prev, pager, next, jumper"
  394. :pager-count="5"
  395. :current-page="current"
  396. :total="total"
  397. :page-size="size"
  398. @current-change="toPage"
  399. @size-change="pageSizeChange"
  400. >
  401. </el-pagination>
  402. </div>
  403. </div>
  404. <!-- image-preview -->
  405. <simple-image-preview
  406. :cur-image="curImage"
  407. @on-prev="toPrevImage"
  408. @on-next="toNextImage"
  409. ref="SimpleImagePreview"
  410. ></simple-image-preview>
  411. <div slot="footer"></div>
  412. </el-dialog>
  413. </template>
  414. <script>
  415. import {
  416. scoreDetailListPage,
  417. objectiveScoreCalculate,
  418. scoreDetailListExport,
  419. } from "../api";
  420. import SimpleImagePreview from "@/components/SimpleImagePreview";
  421. import markMinxin from "../markMinxin";
  422. import { downloadByApi } from "@/plugins/download";
  423. export default {
  424. name: "score-check-detail",
  425. components: { SimpleImagePreview },
  426. mixins: [markMinxin],
  427. props: {
  428. instance: {
  429. type: Object,
  430. default() {
  431. return {};
  432. },
  433. },
  434. },
  435. data() {
  436. return {
  437. modalIsShow: false,
  438. filter: {
  439. college: "",
  440. majorName: "",
  441. className: "",
  442. teacher: "",
  443. filter: 0,
  444. status: "",
  445. breach: "",
  446. startScore: undefined,
  447. endScore: undefined,
  448. objectiveStartScore: undefined,
  449. objectiveEndScore: undefined,
  450. subjectiveStartScore: undefined,
  451. subjectiveEndScore: undefined,
  452. subScore: undefined,
  453. objectiveScoreRateLt: undefined,
  454. studentName: "",
  455. studentCode: "",
  456. secretNumber: "",
  457. orderType: undefined,
  458. orderField: undefined,
  459. },
  460. searchfilter: {},
  461. current: 1,
  462. size: this.GLOBAL.pageSize,
  463. total: 0,
  464. dataList: [],
  465. loading: false,
  466. multipleSelection: [],
  467. downloading: false,
  468. // img view
  469. curImage: {},
  470. curImageIndex: 0,
  471. imageList: [],
  472. };
  473. },
  474. computed: {
  475. canExport() {
  476. return (
  477. JSON.stringify(this.filter) === JSON.stringify(this.searchfilter) &&
  478. this.total > 0
  479. );
  480. },
  481. },
  482. methods: {
  483. cancel() {
  484. this.modalIsShow = false;
  485. },
  486. open() {
  487. this.modalIsShow = true;
  488. },
  489. initData() {
  490. this.search();
  491. },
  492. async getList() {
  493. const datas = {
  494. ...this.filter,
  495. examId: this.instance.examId,
  496. paperNumber: this.instance.paperNumber,
  497. pageNumber: this.current,
  498. pageSize: this.size,
  499. };
  500. if (datas.absent !== null && datas.absent !== "")
  501. datas.absent = !!datas.absent;
  502. if (datas.breach !== null && datas.breach !== "")
  503. datas.breach = !!datas.breach;
  504. const data = await scoreDetailListPage(datas);
  505. this.dataList = data.records;
  506. this.total = data.total;
  507. this.searchfilter = { ...this.filter };
  508. },
  509. toPage(page) {
  510. this.current = page;
  511. this.getList();
  512. },
  513. search() {
  514. this.toPage(1);
  515. },
  516. checkActionValid(row) {
  517. return !["UNEXIST", "MANUAL_ABSENT"].includes(row.scanStatus);
  518. },
  519. collegeChange(val) {
  520. this.filter.college = val?.name;
  521. },
  522. handleSelectionChange(val) {
  523. this.multipleSelection = val;
  524. },
  525. sortChange({ prop, order }) {
  526. if (!order) {
  527. this.filter.orderField = undefined;
  528. this.filter.orderType = undefined;
  529. } else {
  530. this.filter.orderField = prop;
  531. this.filter.orderType = order === "ascending" ? "ASC" : "DESC";
  532. }
  533. this.getList();
  534. },
  535. toBatchCheck(type) {
  536. if (!this.multipleSelection.length) return;
  537. if (type === "objective") {
  538. const studentIds = this.multipleSelection
  539. .filter((item) => item.objectiveCheckFlag)
  540. .map((item) => item.studentId);
  541. if (!studentIds.length) {
  542. this.$message.error("没有可复核数据!");
  543. return;
  544. }
  545. this.toMarkObjectiveAnswer(studentIds);
  546. } else {
  547. const studentIds = this.multipleSelection
  548. .filter((item) => item.subjectiveCheckFlag)
  549. .map((item) => item.studentId);
  550. if (!studentIds.length) {
  551. this.$message.error("没有可复核数据!");
  552. return;
  553. }
  554. this.toMarkSubjectiveAnswer({
  555. examId: this.instance.examId,
  556. paperNumber: this.instance.paperNumber,
  557. studentIds: studentIds,
  558. });
  559. }
  560. },
  561. toCheckQuestion(row, type) {
  562. if (type === "objective") {
  563. this.toMarkObjectiveAnswer([row.studentId]);
  564. } else {
  565. this.toMarkSubjectiveAnswer({
  566. examId: row.examId,
  567. paperNumber: row.paperNumber,
  568. studentIds: [row.studentId],
  569. });
  570. }
  571. },
  572. async toCalcObjective() {
  573. if (this.loading) return;
  574. this.loading = true;
  575. const res = await objectiveScoreCalculate({
  576. examId: this.instance.examId,
  577. paperNumber: this.instance.paperNumber,
  578. }).catch(() => {});
  579. this.loading = false;
  580. if (!res) return;
  581. this.$message.success("操作成功!");
  582. this.getList();
  583. },
  584. async toExportScore() {
  585. if (this.downloading) return;
  586. this.downloading = true;
  587. const res = await downloadByApi(() => {
  588. return scoreDetailListExport({
  589. ...this.searchfilter,
  590. examId: this.instance.examId,
  591. paperNumber: this.instance.paperNumber,
  592. });
  593. }).catch((e) => {
  594. this.$message.error(e || "导出失败,请重新尝试!");
  595. });
  596. this.downloading = false;
  597. if (!res) return;
  598. this.$message.success("导出成功!");
  599. },
  600. // img view
  601. async toViewTrack(row) {
  602. this.toMarkTrack(row.studentId);
  603. },
  604. toViewSheetPaper(row) {
  605. this.curImageIndex = 0;
  606. this.imageList = row.sheetUrls || [];
  607. this.selectImage(this.curImageIndex);
  608. this.$refs.SimpleImagePreview.open();
  609. },
  610. selectImage(index) {
  611. this.curImage = this.imageList[index];
  612. },
  613. toPrevImage() {
  614. if (this.curImageIndex === 0) {
  615. this.curImageIndex = this.imageList.length - 1;
  616. } else {
  617. this.curImageIndex--;
  618. }
  619. this.selectImage(this.curImageIndex);
  620. },
  621. toNextImage() {
  622. if (this.curImageIndex === this.imageList.length - 1) {
  623. this.curImageIndex = 0;
  624. } else {
  625. this.curImageIndex++;
  626. }
  627. this.selectImage(this.curImageIndex);
  628. },
  629. },
  630. };
  631. </script>