MarkAction.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. <template>
  2. <div class="mark-action grade-action">
  3. <!-- 头部信息 ------ -->
  4. <!-- 试卷状态 -->
  5. <!-- 状态:已评,待评,改档,改档打分 -->
  6. <div class="action-paper-state">
  7. <p class="paper-state-cont">{{ stepLabel }}</p>
  8. </div>
  9. <!-- 试卷信息 -->
  10. <div class="action-paper-info">
  11. <!-- <p v-if="IS_ADMIN">
  12. <span>试卷考号:</span><span>{{ curPaperOrTask.examNumber }}</span>
  13. </p> -->
  14. <p>
  15. <span v-if="IS_MARKER">任务密号:</span>
  16. <span v-else>试卷密号:</span>
  17. <span>NO.{{ curPaperOrTask.sn }}</span>
  18. </p>
  19. </div>
  20. <!-- 改档信息 -->
  21. <div class="action-grade-change" v-if="rights.gradeChange">
  22. <p>
  23. <span>原始档位:</span><span>{{ curPaperOrTask.originLevel }}</span>
  24. </p>
  25. <p>
  26. <span v-if="IS_MARKER">建议档位:</span>
  27. <span v-else>申请档位:</span>
  28. <span>{{ curPaperOrTask.redoLevel }}</span>
  29. </p>
  30. </div>
  31. <div
  32. :class="[
  33. 'action-grade-change-status',
  34. { 'action-grade-change-status-error': curPaperOrTask.auditStatus === 0 }
  35. ]"
  36. v-if="rights.gradeChange && IS_ADMIN"
  37. >
  38. <p>{{ curPaperOrTask.auditStatus === 1 ? "同意改档" : "不同意改档" }}</p>
  39. </div>
  40. <!-- 档位信息 -->
  41. <!-- 已评/待评(已评档位),改档打分(已评档位) -->
  42. <div class="action-grade-info" v-if="rights.gradeInfo">
  43. <h3 class="grade-info-name">
  44. {{ curPaperLevel }}
  45. </h3>
  46. <!-- <div class="grade-info-range">
  47. <p>分数范围</p>
  48. <p>
  49. <span>{{ curLevel.minScore }}</span>
  50. <span>~</span>
  51. <span>{{ curLevel.maxScore }}</span>
  52. </p>
  53. </div> -->
  54. </div>
  55. <!-- 打分信息 -->
  56. <div class="action-grade-info action-mark-info" v-if="rights.markInfo">
  57. <p class="grade-info-name" v-if="curPaperScore">
  58. {{ curPaperScore }}
  59. </p>
  60. <p class="grade-info-name grade-info-none" v-else>未打分</p>
  61. </div>
  62. <!-- 选择档位 -->
  63. <div class="action-grade-list" v-if="rights.gradeList">
  64. <div
  65. class="action-grade-item"
  66. v-for="(level, index) in levels"
  67. :key="index"
  68. >
  69. <div
  70. :class="[
  71. 'action-grade-item-content',
  72. {
  73. 'action-item-content-disabled': btnClicked,
  74. 'is-active': keyInput == level.name
  75. }
  76. ]"
  77. @click="selectLevel(level)"
  78. >
  79. <p>{{ level.name }}</p>
  80. <p>{{ level.minScore }}~{{ level.maxScore }}</p>
  81. </div>
  82. </div>
  83. </div>
  84. <!-- 选择分数 -->
  85. <div class="action-mark-list" v-if="rights.levelList">
  86. <div
  87. class="action-mark-item"
  88. v-for="(score, index) in scores"
  89. :key="index"
  90. >
  91. <div
  92. :class="[
  93. 'action-mark-item-content',
  94. {
  95. 'action-item-content-disabled': btnClicked,
  96. 'is-active': keyInput == score
  97. }
  98. ]"
  99. @click="selectScore(score)"
  100. >
  101. <p>{{ score }}</p>
  102. </div>
  103. </div>
  104. </div>
  105. <div class="action-grade-pass" v-if="rights.levelPass" @click="toPass">
  106. 跳过
  107. </div>
  108. <!-- mark confirm grade change -->
  109. <div
  110. class="action-grade-change-confirm"
  111. v-if="IS_MARKER && stepType === 'shift'"
  112. >
  113. <Button type="primary" @click="gradeChangeConfirm">确认改档</Button>
  114. </div>
  115. <!-- 评卷记录 -->
  116. <div class="action-grade-history" v-if="rights.markHis">
  117. <h3>评卷记录</h3>
  118. <div class="grade-history-list">
  119. <div
  120. class="grade-history-item"
  121. v-for="(his, hindex) in gradingHistory"
  122. :key="hindex"
  123. >
  124. <p>{{ his.loginName }}</p>
  125. <p>{{ his.value }}</p>
  126. </div>
  127. </div>
  128. </div>
  129. <!-- 查询 -->
  130. <div class="action-search" v-if="rights.search">
  131. <Select
  132. class="search-select"
  133. v-model="filter.codeType"
  134. placeholder="密号类型"
  135. >
  136. <Option
  137. v-for="item in codeTypes"
  138. :key="item.key"
  139. :value="item.key"
  140. :label="item.val"
  141. ></Option>
  142. </Select>
  143. <Input
  144. class="search-input"
  145. v-model.trim="filter.code"
  146. placeholder="输入密号"
  147. clearable
  148. >
  149. </Input>
  150. <Button size="small" type="primary" class="search-btn" @click="searchCode"
  151. >查询</Button
  152. >
  153. </div>
  154. <!-- 改档处理状态查询 -->
  155. <div class="action-search" v-if="rights.gradeChangeSearch">
  156. <Select
  157. class="search-input"
  158. v-model="applyChangeLevelStatus"
  159. placeholder="类型"
  160. >
  161. <Option
  162. v-for="(val, key) in CHANGE_LEVEL_STATUS"
  163. :key="key"
  164. :value="key * 1"
  165. :label="val"
  166. ></Option>
  167. </Select>
  168. <Button
  169. size="small"
  170. type="primary"
  171. class="search-btn"
  172. @click="searchGradeChange"
  173. >查询</Button
  174. >
  175. </div>
  176. </div>
  177. </template>
  178. <script>
  179. import { markHistoryList } from "@/api";
  180. import { CODE_TYPE, CHANGE_LEVEL_STATUS } from "@/constants/enumerate";
  181. import { mapState, mapMutations } from "vuex";
  182. // 三种情况:
  183. // 管理员(ADMIN),科组长(MARK_LEADER),评卷员(MARKER)
  184. // 管理员:查询,头部信息,评卷记录
  185. // 科组长:查询,头部信息,选择档位,评卷记录
  186. // 评卷员:头部信息,选择分数
  187. /*
  188. [paper template]
  189. {
  190. "id": 42,
  191. "sn": "5314987744",
  192. "redoLevel": null,
  193. "level": "B",
  194. "score": null,
  195. "result": "88",
  196. "originLevel": null,
  197. "markerId": 52,
  198. "marker": "zj-pj-01",
  199. "updatedOn": 1595812145000,
  200. "imgSrc": "http://192.168.10.145:9000/images/34/SC/1/1901130046.jpg?random=f5549bb0-ba58-4cd3-a88e-0559be4e06e5",
  201. "thumbSrc": "http://192.168.10.145:9000/thumbs/34/SC/1/1901130046.jpg?random=e7f880b8-4cb6-4963-8e66-532f0f8bdeb0",
  202. "markByLeader": false,
  203. "oldRejected": false,
  204. "paperId": 116,
  205. "randomSeqNew": 4987744,
  206. "randomSeq": null,
  207. "serialNumber": "B1",
  208. "displayNumber": true,
  209. "shift": false,
  210. "shiftScore": false,
  211. "rejected": false,
  212. "sample": false
  213. }
  214. [marktask template]
  215. {
  216. "id": 38,
  217. "sn": "5314266469",
  218. "redoLevel": null,
  219. "level": "A",
  220. "score": null,
  221. "result": "96",
  222. "originLevel": null,
  223. "markerId": 52,
  224. "marker": "zj-pj-01",
  225. "updatedOn": 1595812012000,
  226. "imgSrc": "http://192.168.10.145:9000/images/34/SC/1/1901130043.jpg?random=bffc061c-7a80-42a4-ad56-36ec6eba0d45",
  227. "thumbSrc": "http://192.168.10.145:9000/thumbs/34/SC/1/1901130043.jpg?random=da743f9d-8d46-4499-bb6f-b9c26e003ba2",
  228. "markByLeader": true,
  229. "oldRejected": false,
  230. "paperId": 113,
  231. "randomSeqNew": 4266469,
  232. "randomSeq": null,
  233. "serialNumber": "A1",
  234. "displayNumber": true,
  235. "shift": false,
  236. "shiftScore": false,
  237. "rejected": false,
  238. "sample": true
  239. }
  240. */
  241. const initRights = {
  242. search: false,
  243. gradeChangeSearch: false,
  244. gradeChange: false,
  245. gradeInfo: false,
  246. markInfo: false,
  247. gradeList: false,
  248. levelList: false,
  249. levelPass: false,
  250. markHis: false
  251. };
  252. export default {
  253. name: "mark-action",
  254. props: {
  255. curPaperOrTask: {
  256. type: Object,
  257. default() {
  258. return {};
  259. }
  260. },
  261. levels: {
  262. type: Array,
  263. default() {
  264. return [];
  265. }
  266. }
  267. },
  268. data() {
  269. return {
  270. curUserRoleType: this.$ls.get("user", { role: "" }).role,
  271. rights: {
  272. ...initRights
  273. },
  274. roleRight: {
  275. ADMIN: {
  276. done: ["search", "markHis", "gradeInfo", "markInfo"],
  277. shift: ["search", "gradeChangeSearch", "gradeChange"]
  278. },
  279. MARK_LEADER: {
  280. undo: ["gradeList", "gradeInfo", "markInfo"],
  281. done: ["gradeList", "markHis", "gradeInfo", "markInfo"],
  282. shift: ["gradeList", "gradeChange"]
  283. },
  284. MARKER: {
  285. done: ["levelList", "gradeInfo", "markInfo"],
  286. undo: ["levelList", "levelPass", "gradeInfo"],
  287. shift: ["gradeChange"],
  288. shiftScore: ["levelList", "levelPass", "gradeInfo"]
  289. }
  290. },
  291. filter: {
  292. codeType: "examNumber",
  293. code: ""
  294. },
  295. codeTypes: [],
  296. CHANGE_LEVEL_STATUS,
  297. applyChangeLevelStatus: null,
  298. stepDict: {
  299. undo: "待评",
  300. done: "已评",
  301. shift: "改档",
  302. shiftScore: "改档打分"
  303. },
  304. stepType: "",
  305. stepLabel: "",
  306. curPaperLevel: "",
  307. curPaperScore: "",
  308. scores: [],
  309. gradingHistory: [],
  310. curLevel: {},
  311. setT: null,
  312. btnClicked: false,
  313. keyInput: null
  314. };
  315. },
  316. computed: {
  317. ...mapState("marker", ["ribbonSet", "shortcutKeyStatus"]),
  318. IS_ADMIN() {
  319. return this.curUserRoleType === "ADMIN";
  320. },
  321. IS_MARKER() {
  322. return this.curUserRoleType === "MARKER";
  323. },
  324. IS_MARK_LEADER() {
  325. return this.curUserRoleType === "MARK_LEADER";
  326. }
  327. },
  328. watch: {
  329. curPaperOrTask(val) {
  330. this.rebuildRight();
  331. },
  332. "ribbonSet.keyboardMark": {
  333. immediate: true,
  334. handler(val) {
  335. this.setShortcutStatus({ action: val });
  336. }
  337. },
  338. "shortcutKeyStatus.action": {
  339. immediate: true,
  340. handler(val, oldval) {
  341. // console.log(val, oldval);
  342. if (val === oldval) return;
  343. if (val) {
  344. document.addEventListener("keydown", this.keyEvent);
  345. } else {
  346. document.removeEventListener("keydown", this.keyEvent);
  347. }
  348. }
  349. }
  350. },
  351. mounted() {
  352. this.codeTypes = Object.entries(CODE_TYPE)
  353. .map(([key, val]) => {
  354. return {
  355. key,
  356. val
  357. };
  358. })
  359. .filter(item => item.key !== "examNumber");
  360. // .filter(item => this.IS_ADMIN || item.key !== "examNumber");
  361. this.rebuildRight();
  362. },
  363. methods: {
  364. ...mapMutations("marker", ["setShortcutStatus"]),
  365. getStepType() {
  366. const paper = this.curPaperOrTask;
  367. if (paper.shift && paper.shiftScore && !paper.level && !paper.result)
  368. return "shift";
  369. if (!paper.shift && paper.shiftScore && !paper.result)
  370. return "shiftScore";
  371. if (this.IS_MARKER) {
  372. if (!paper.result) return "undo";
  373. if (paper.result) return "done";
  374. } else {
  375. if (paper.score !== null) return "done";
  376. if (paper.score === null) return "undo";
  377. }
  378. },
  379. rebuildRight() {
  380. const curPaperScore = this.IS_MARKER
  381. ? this.curPaperOrTask.result
  382. : this.curPaperOrTask.score;
  383. this.curPaperScore = curPaperScore !== null ? curPaperScore + "" : "";
  384. this.stepType = this.getStepType();
  385. this.stepLabel = this.stepDict[this.stepType];
  386. this.rights = { ...initRights };
  387. const roleRights =
  388. this.roleRight[this.curUserRoleType][this.stepType] || [];
  389. roleRights.map(key => {
  390. this.rights[key] = true;
  391. });
  392. if (this.curPaperOrTask.level) {
  393. this.curLevel = this.levels.find(
  394. level => level.name === this.curPaperOrTask.level
  395. );
  396. this.curPaperLevel =
  397. this.curPaperOrTask["displayNumber"] && this.stepType !== "shiftScore"
  398. ? this.curPaperOrTask["serialNumber"]
  399. : this.curLevel.name;
  400. this.updateScoreList();
  401. }
  402. if (this.rights.markHis) {
  403. this.getMarkHistory();
  404. }
  405. this.btnClicked = false;
  406. this.keyInput = null;
  407. },
  408. updateScoreList() {
  409. let scores = [];
  410. if (this.curLevel.levelType === "ADMITED") {
  411. const { minScore, maxScore, intervalScore } = this.curLevel;
  412. for (let i = minScore; i <= maxScore; i += intervalScore) {
  413. scores.push(i);
  414. }
  415. } else {
  416. scores = this.curLevel.scoreList.split(",").map(item => item * 1);
  417. }
  418. this.scores = scores;
  419. },
  420. async getMarkHistory() {
  421. const data = await markHistoryList(this.curPaperOrTask.id, "SCORE");
  422. this.gradingHistory = data.map(item => {
  423. return {
  424. id: item.markerId,
  425. name: item.marker,
  426. loginName: item.loginName,
  427. value: item.result || "未评"
  428. };
  429. });
  430. },
  431. selectLevel(level) {
  432. if (this.curPaperOrTask.level === level.name) return;
  433. if (this.btnClicked) return;
  434. this.btnClicked = true;
  435. this.setT = setTimeout(() => {
  436. this.btnClicked = false;
  437. }, 500);
  438. // 科组长改档 / 评卷同意改档:只使用selectedLevel
  439. this.$emit("on-leader-level", {
  440. paperId: this.curPaperOrTask.paperId,
  441. curLevel:
  442. this.stepType === "shift"
  443. ? this.curPaperOrTask.redoLevel
  444. : this.curPaperOrTask.level,
  445. selectedLevel: level.name
  446. });
  447. },
  448. gradeChangeConfirm() {
  449. this.selectLevel({ name: this.curPaperOrTask.redoLevel });
  450. },
  451. selectScore(score) {
  452. if (this.btnClicked) return;
  453. this.btnClicked = true;
  454. this.setT = setTimeout(() => {
  455. this.btnClicked = false;
  456. }, 500);
  457. // 评卷员打分
  458. this.$emit("on-select-score", score * 1);
  459. },
  460. toPass() {
  461. this.$emit("on-pass");
  462. },
  463. searchCode() {
  464. if (!this.filter.code || !this.filter.codeType) {
  465. this.$Message.error("请设置密号类型和密号!");
  466. return;
  467. }
  468. this.$emit("on-code-search", this.filter);
  469. },
  470. searchGradeChange() {
  471. this.$emit("on-grade-change-search", this.applyChangeLevelStatus);
  472. },
  473. // keyboard submit
  474. keyEvent(e) {
  475. if (this.btnClicked) return;
  476. this.$Message.destroy();
  477. // if (!this.ribbonSet.keyboardMark) return;
  478. if (!e.altKey && !e.shiftKey && !e.ctrlKey) {
  479. // 打分
  480. if (this.rights.levelList) {
  481. if (e.key === "Enter" && this.ribbonSet.needEnterSubmit) {
  482. e.preventDefault();
  483. this.toKeySubmitScore();
  484. return;
  485. }
  486. if (!/^[a-z0-9]$/.test(e.key)) return;
  487. if (this.checkKeyCodeValid(e.keyCode)) {
  488. e.preventDefault();
  489. this.keyInput += e.key;
  490. this.keyInput = this.keyInput.slice(-3);
  491. if (!this.ribbonSet.needEnterSubmit) {
  492. this.toKeySubmitScore();
  493. }
  494. } else {
  495. this.$Message.error("按键无效");
  496. }
  497. return;
  498. }
  499. // 改档
  500. if (this.rights.gradeList) {
  501. if (e.key === "Enter" && this.ribbonSet.needEnterSubmit) {
  502. e.preventDefault();
  503. this.toKeySubmitLevel();
  504. return;
  505. }
  506. if (!/^[a-z0-9]$/.test(e.key)) return;
  507. const keyInput = e.key.toUpperCase();
  508. if (this.getKeyInputLevel(keyInput)) {
  509. e.preventDefault();
  510. this.keyInput = keyInput;
  511. if (!this.ribbonSet.needEnterSubmit) {
  512. this.toKeySubmitLevel();
  513. }
  514. } else {
  515. this.$Message.error("按键无效");
  516. }
  517. }
  518. }
  519. },
  520. checkKeyCodeValid(keyCode) {
  521. return keyCode >= 49 && keyCode <= 58;
  522. },
  523. checkScoreValid(score) {
  524. const minScore = this.scores[0];
  525. const maxScore = this.scores.slice(-1)[0];
  526. return score >= minScore && score <= maxScore;
  527. },
  528. toKeySubmitScore() {
  529. if (!this.keyInput) return;
  530. if (!this.checkScoreValid(this.keyInput * 1)) {
  531. this.$Message.destroy();
  532. this.$Message.error("输入分数无效");
  533. return;
  534. }
  535. this.selectScore(this.keyInput);
  536. },
  537. getKeyInputLevel(key) {
  538. return this.levels.find(item => item.name === key);
  539. },
  540. toKeySubmitLevel() {
  541. if (!this.keyInput) return;
  542. const level = this.getKeyInputLevel(this.keyInput);
  543. this.selectLevel(level);
  544. },
  545. clearKeyInput() {
  546. this.keyInput = null;
  547. }
  548. },
  549. beforeDestroy() {
  550. if (this.setT) clearTimeout(this.setT);
  551. if (this.ribbonSet.keyboardMark)
  552. document.removeEventListener("click", this.keyEvent);
  553. }
  554. };
  555. </script>