MarkAction.vue 14 KB

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