GradeAction.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. <template>
  2. <div class="grade-action">
  3. <!-- 头部信息 ------ -->
  4. <!-- 试卷状态 -->
  5. <!-- 状态:已评,待评,打回,仲裁 -->
  6. <div class="action-paper-state" v-if="stepType === 'reject'">
  7. <p class="paper-state-cont">
  8. {{ curPaperOrTask.isRejectedByLeader ? "科组长打回" : "自动打回" }}
  9. </p>
  10. <p class="paper-state-intro">
  11. <span v-if="curPaperOrTask.rejectedCount"
  12. >共打回{{ curPaperOrTask.rejectedCount }}次</span
  13. >
  14. </p>
  15. </div>
  16. <div class="action-paper-state" v-else>
  17. <p class="paper-state-cont">{{ stepLabel }}</p>
  18. </div>
  19. <!-- 试卷信息 -->
  20. <div class="action-paper-info">
  21. <p>
  22. <span v-if="IS_MARKER">任务密号:</span>
  23. <span v-else>试卷密号:</span>
  24. <span v-if="curPaperOrTask.sn && !isBatchAction">
  25. NO.{{ curPaperOrTask.sn }}
  26. </span>
  27. <span v-else>--</span>
  28. </p>
  29. </div>
  30. <!-- 选择档位 -->
  31. <h3
  32. class="action-grade-info-title"
  33. v-if="IS_MARK_LEADER && markLeaderOnlyRight && rights.gradeList"
  34. >
  35. 当前操作:{{ markLeaderOnlyRight.name }}
  36. </h3>
  37. <div class="action-grade-list" v-if="rights.gradeList">
  38. <div
  39. class="action-grade-item"
  40. v-for="(level, index) in levels"
  41. :key="index"
  42. >
  43. <div
  44. :class="[
  45. 'action-grade-item-content',
  46. {
  47. 'action-item-content-disabled': btnClicked,
  48. 'is-active': keyInput == level.name
  49. }
  50. ]"
  51. @click="selectLevel(level)"
  52. >
  53. <p>{{ level.name }}</p>
  54. <p>{{ level.minScore }}~{{ level.maxScore }}</p>
  55. </div>
  56. </div>
  57. </div>
  58. <div v-if="ribbonSet.keyboardMark" class="action-grade-keyboard">
  59. <div class="keyboard-input">{{ keyInput }}</div>
  60. <div class="keyboard-clear" @click="clearKeyInput">
  61. <Icon type="md-trash" />
  62. </div>
  63. </div>
  64. <!-- 档位信息 -->
  65. <!-- 已评(已评档位),打回(建议档位) -->
  66. <h3
  67. class="action-grade-info-title"
  68. v-if="IS_MARKER && stepType === 'reject'"
  69. >
  70. <span>原分档档位:{{ curPaperOrTask.originLevel || "" }}</span>
  71. <span
  72. v-if="
  73. paramsSet.autoCallbackShowDeviation &&
  74. curPaperOrTask.deviationDirection &&
  75. curPaperOrTask.deviationDirection !== '0'
  76. "
  77. :class="[
  78. 'grade-info-deviation',
  79. {
  80. 'grade-info-deviation-error':
  81. curPaperOrTask.deviationDirection * 1 > 0
  82. }
  83. ]"
  84. >{{ curPaperOrTask.deviationDirection * 1 > 0 ? "偏高" : "偏低" }}</span
  85. >
  86. </h3>
  87. <h3
  88. class="action-grade-info-title"
  89. v-if="curPaperOrTask.rejected && curPaperOrTask.isRejectedByLeader"
  90. >
  91. 建议档位:
  92. </h3>
  93. <div class="action-grade-info" v-if="rights.gradeInfo && curLevel.name">
  94. <h3 class="grade-info-name">{{ curLevel.name }}</h3>
  95. <div class="grade-info-range">
  96. <p>分数范围</p>
  97. <p>
  98. <span>{{ curLevel.minScore }}</span>
  99. <span>~</span>
  100. <span>{{ curLevel.maxScore }}</span>
  101. </p>
  102. </div>
  103. </div>
  104. <!-- 跳过 -->
  105. <div
  106. class="action-grade-pass"
  107. v-if="rights.gradePass && IS_MARKER"
  108. @click="toPass"
  109. >
  110. 跳过
  111. </div>
  112. <!-- 评卷记录 -->
  113. <div class="action-grade-history" v-if="rights.gradeHis">
  114. <h3>评卷记录</h3>
  115. <div class="grade-history-list">
  116. <div
  117. class="grade-history-item"
  118. v-for="his in gradingHistory"
  119. :key="his.id"
  120. >
  121. <p>{{ his.loginName }}</p>
  122. <p>{{ his.value }}</p>
  123. </div>
  124. </div>
  125. </div>
  126. <!-- 查询 -->
  127. <div class="action-search" v-if="rights.search">
  128. <Select
  129. class="search-select"
  130. v-model="filter.codeType"
  131. placeholder="密号类型"
  132. >
  133. <Option
  134. v-for="item in codeTypes"
  135. :key="item.key"
  136. :value="item.key"
  137. :label="item.val"
  138. ></Option>
  139. </Select>
  140. <Input
  141. class="search-input"
  142. v-model.trim="filter.code"
  143. placeholder="输入密号"
  144. clearable
  145. >
  146. </Input>
  147. <Button size="small" type="primary" class="search-btn" @click="searchCode"
  148. >查询</Button
  149. >
  150. </div>
  151. </div>
  152. </template>
  153. <script>
  154. import { markHistoryList } from "@/api";
  155. import { CODE_TYPE } from "@/constants/enumerate";
  156. import { mapState } from "vuex";
  157. // 三种情况:
  158. // 管理员(ADMIN),科组长(MARK_LEADER),评卷员(MARKER)
  159. // 管理员:查询,头部信息,评卷记录
  160. // 科组长:查询,头部信息,选择档位,评卷记录
  161. // 评卷员:头部信息,选择档位
  162. // MARK_LEADER / ADMIN: curPaperOrTask => paper
  163. // MARKER: curPaperOrTask => task
  164. //
  165. /*
  166. [paper template]
  167. {
  168. "id": 165,
  169. "sn": "029947536",
  170. "examNumber": "1901040084",
  171. "level": "A",
  172. "score": null,
  173. "redoLevel": null,
  174. "updatedOn": 1591767742000,
  175. "imgSrc": "",
  176. "thumbSrc": "",
  177. "markByLeader": false,
  178. "markedLogic": true,
  179. "areaCode": "2",
  180. "inspectScore": null,
  181. "inspectLevel": null,
  182. "inspector": null,
  183. "sheetSrc": null,
  184. "stage": "LEVEL",
  185. "test": 0,
  186. "paperTest": 0,
  187. "markResults": [],
  188. "rejected": false,
  189. "arbitrated": false,
  190. "sample": false,
  191. "tagged": false,
  192. "missing": false,
  193. "manual": false
  194. }
  195. [marktask template]
  196. {
  197. "id": 511,
  198. "sn": "4929446110",
  199. "redoLevel": null,
  200. "level": "A",
  201. "score": null,
  202. "result": "100",
  203. "originLevel": null,
  204. "markerId": 49,
  205. "marker": "pj061001",
  206. "updatedOn": 1594775592000,
  207. "imgSrc": "",
  208. "thumbSrc": "",
  209. "markByLeader": true,
  210. "oldRejected": false,
  211. "paperId": 168,
  212. "randomSeqNew": 9446110,
  213. "randomSeq": null,
  214. "rejected": false,
  215. "sample": true
  216. }
  217. */
  218. const initRights = {
  219. search: false,
  220. gradeInfo: false,
  221. gradeList: false,
  222. gradePass: false,
  223. gradeHis: false
  224. };
  225. export default {
  226. name: "grade-action",
  227. props: {
  228. curPaperOrTask: {
  229. type: Object,
  230. default() {
  231. return {};
  232. }
  233. },
  234. levels: {
  235. type: Array,
  236. default() {
  237. return [];
  238. }
  239. },
  240. paramsSet: {
  241. type: Object,
  242. default() {
  243. return {};
  244. }
  245. },
  246. isBatchAction: {
  247. type: Boolean,
  248. default: false
  249. },
  250. // 是否处于粗分档
  251. isRoughLevel: {
  252. type: Boolean,
  253. default: false
  254. }
  255. },
  256. data() {
  257. return {
  258. curUserRoleType: this.$ls.get("user", { role: "" }).role,
  259. rights: {
  260. ...initRights
  261. },
  262. cacheRights: null,
  263. roleRight: {
  264. ADMIN: {
  265. done: ["search", "gradeHis", "gradeInfo"],
  266. reject: ["search", "gradeInfo", "gradeHis"],
  267. arbitrate: ["search", "gradeHis"]
  268. },
  269. MARK_LEADER: {
  270. undo: ["gradeList", "gradePass", "gradeInfo"],
  271. done: ["gradeList", "gradeHis", "gradeInfo"],
  272. reject: ["gradeList", "gradePass", "gradeInfo", "gradeHis"],
  273. arbitrate: ["gradeList", "gradePass", "gradeHis"],
  274. batch: ["gradeList"]
  275. },
  276. MARKER: {
  277. done: ["gradeList", "gradeInfo"],
  278. undo: ["gradeList", "gradePass"],
  279. reject: ["gradeList", "gradePass", "gradeInfo"],
  280. batch: ["gradeList"]
  281. },
  282. STANDARD: ["gradeInfo"]
  283. },
  284. filter: {
  285. codeType: "examNumber",
  286. code: ""
  287. },
  288. codeTypes: [],
  289. stepDict: {
  290. undo: "待评",
  291. done: "已评",
  292. reject: "打回",
  293. arbitrate: "待仲裁",
  294. sample: "标准卷",
  295. batch: "批量操作"
  296. },
  297. stepType: "",
  298. stepLabel: "",
  299. gradingHistory: [],
  300. curLevel: {},
  301. setT: null,
  302. btnClicked: false,
  303. keyInput: null,
  304. // 科组长权限
  305. markLeaderOnlyRight: null
  306. };
  307. },
  308. computed: {
  309. ...mapState("marker", ["ribbonSet"]),
  310. IS_ADMIN() {
  311. return this.curUserRoleType === "ADMIN";
  312. },
  313. IS_MARKER() {
  314. return this.curUserRoleType === "MARKER";
  315. },
  316. IS_MARK_LEADER() {
  317. return this.curUserRoleType === "MARK_LEADER";
  318. }
  319. },
  320. watch: {
  321. curPaperOrTask() {
  322. this.rebuildRight();
  323. },
  324. isBatchAction(val) {
  325. if (val) {
  326. this.stepType = "batch";
  327. this.stepLabel = this.stepDict[this.stepType];
  328. const roleRights =
  329. this.roleRight[this.curUserRoleType][this.stepType] || [];
  330. this.rights = { ...initRights };
  331. roleRights.map(key => {
  332. this.rights[key] = true;
  333. });
  334. } else {
  335. this.rebuildRight();
  336. }
  337. this.keyInput = null;
  338. },
  339. "ribbonSet.keyboardMark": {
  340. immediate: true,
  341. handler(val) {
  342. if (val) {
  343. document.addEventListener("keydown", this.keyEvent);
  344. } else {
  345. this.keyInput = null;
  346. document.removeEventListener("keydown", this.keyEvent);
  347. }
  348. }
  349. }
  350. },
  351. mounted() {
  352. this.markLeaderOnlyRight = this.$ls.get("user", {
  353. markLeaderOnlyRight: null
  354. }).markLeaderOnlyRight;
  355. this.codeTypes = Object.entries(CODE_TYPE)
  356. .map(([key, val]) => {
  357. return {
  358. key,
  359. val
  360. };
  361. })
  362. .filter(item => item.key !== "examNumber");
  363. // .filter(item => this.IS_ADMIN || item.key !== "examNumber");
  364. this.rebuildRight();
  365. },
  366. methods: {
  367. getStepType() {
  368. const info = this.curPaperOrTask;
  369. if (info.sample) return "sample";
  370. if (info.level) return "done";
  371. if (info.arbitrated) return "arbitrate";
  372. if (info.rejected) return "reject";
  373. if (!info.rejected && !info.arbitrated && !info.level) return "undo";
  374. return;
  375. },
  376. rebuildRight() {
  377. if (this.setT) clearTimeout(this.setT);
  378. let roleRights = [];
  379. this.stepType = this.getStepType();
  380. this.stepLabel = this.stepDict[this.stepType];
  381. if (this.stepType === "sample") {
  382. roleRights = this.roleRight.STANDARD;
  383. } else {
  384. roleRights = this.roleRight[this.curUserRoleType][this.stepType] || [];
  385. }
  386. this.rights = { ...initRights };
  387. roleRights.map(key => {
  388. this.rights[key] = true;
  389. });
  390. this.getCurLevel();
  391. if (this.rights.gradeHis) {
  392. this.getMarkHistory();
  393. }
  394. this.btnClicked = false;
  395. this.keyInput = null;
  396. },
  397. getCurLevel() {
  398. const levelName = this.curPaperOrTask.rejected
  399. ? this.curPaperOrTask.redoLevel
  400. : this.curPaperOrTask.level;
  401. if (levelName) {
  402. this.curLevel = this.levels.find(item => item.name === levelName);
  403. } else {
  404. this.curLevel = {};
  405. }
  406. },
  407. async getMarkHistory() {
  408. // 只有科组长和超管才会展示评卷记录
  409. const data = await markHistoryList(this.curPaperOrTask.id, "LEVEL");
  410. this.gradingHistory = data.map(item => {
  411. let level = item.result || "未评";
  412. if (this.stepType === "reject" && !item.result) {
  413. level = `${level}(${item.originLevel})`;
  414. }
  415. return {
  416. id: item.id,
  417. markerId: item.markerId,
  418. name: item.marker,
  419. loginName: item.loginName,
  420. value: level
  421. };
  422. });
  423. },
  424. selectLevel(level) {
  425. if (this.isBatchAction) {
  426. if (this.btnClicked) return;
  427. this.btnClicked = true;
  428. this.setT = setTimeout(() => {
  429. this.btnClicked = false;
  430. }, 500);
  431. this.$emit("on-batch-level", level);
  432. return;
  433. }
  434. if (this.IS_MARKER && this.curPaperOrTask.level === level.name) return;
  435. if (this.btnClicked) return;
  436. this.btnClicked = true;
  437. if (this.IS_MARK_LEADER) {
  438. this.setT = setTimeout(() => {
  439. this.btnClicked = false;
  440. }, 500);
  441. this.$emit(
  442. "on-leader-level",
  443. {
  444. paperIds: this.curPaperOrTask.id + "",
  445. curLevel: this.curPaperOrTask.level,
  446. selectedLevel: level.name,
  447. markLeaderOnlyRight: this.markLeaderOnlyRight
  448. },
  449. this.gradingHistory.map(item => {
  450. return {
  451. id: item.markerId,
  452. name: item.loginName
  453. };
  454. })
  455. );
  456. return;
  457. }
  458. this.$emit("on-select-level", level);
  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. // keyboard submit
  471. keyEvent(e) {
  472. this.$Message.destroy();
  473. if (this.btnClicked) return;
  474. // if (!this.ribbonSet.keyboardMark) return;
  475. if (!e.altKey && !e.shiftKey && !e.ctrlKey) {
  476. const validKeys = ["F5", "ArrowLeft", "ArrowRight"];
  477. if (validKeys.includes(e.key)) return;
  478. if (e.key === "Enter" && this.ribbonSet.needEnterSubmit) {
  479. e.preventDefault();
  480. this.toKeySubmit();
  481. return;
  482. }
  483. const keyInput = e.key.toUpperCase();
  484. if (this.getKeyInputLevel(keyInput)) {
  485. e.preventDefault();
  486. this.keyInput = keyInput;
  487. if (!this.ribbonSet.needEnterSubmit) {
  488. this.toKeySubmit();
  489. }
  490. } else {
  491. this.$Message.error("按键无效");
  492. }
  493. }
  494. },
  495. getKeyInputLevel(key) {
  496. return this.levels.find(item => item.name === key);
  497. },
  498. toKeySubmit() {
  499. if (!this.keyInput) return;
  500. const level = this.getKeyInputLevel(this.keyInput);
  501. this.selectLevel(level);
  502. },
  503. clearKeyInput() {
  504. this.keyInput = null;
  505. }
  506. },
  507. beforeDestroy() {
  508. if (this.setT) clearTimeout(this.setT);
  509. if (this.ribbonSet.keyboardMark)
  510. document.removeEventListener("click", this.keyEvent);
  511. }
  512. };
  513. </script>