GradingLevelSet.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. <template>
  2. <div class="grading-level-set">
  3. <Button
  4. class="level-add-btn"
  5. type="success"
  6. icon="recode-white icon"
  7. shape="circle"
  8. @click="toAdd"
  9. >新增档位</Button
  10. >
  11. <table class="table table-noborder grading-table">
  12. <tr>
  13. <th>档位</th>
  14. <th>最低分</th>
  15. <th>最高分</th>
  16. <th>给分间隔</th>
  17. <th>典型值</th>
  18. <th>类型</th>
  19. <th>给分项</th>
  20. <th>考区阈值%</th>
  21. <th>占比阈值%</th>
  22. <th>操作</th>
  23. </tr>
  24. <template v-for="(level, index) in levels">
  25. <tr :key="index">
  26. <td>
  27. <Input
  28. v-model="level.code"
  29. style="width: 60px"
  30. @on-blur="codeChange(level)"
  31. v-if="level.canEdit && workDetail.modifyOtherVal"
  32. ></Input>
  33. <p v-else>{{ level.code }}</p>
  34. </td>
  35. <td>
  36. <InputNumber
  37. v-model="level.minScore"
  38. :min="0"
  39. :max="1000"
  40. @on-blur="checkLevelValidate(level)"
  41. v-if="level.canEdit && workDetail.modifyOtherVal"
  42. ></InputNumber>
  43. <p v-else>{{ level.minScore }}</p>
  44. </td>
  45. <td>
  46. <InputNumber
  47. v-model="level.maxScore"
  48. :min="1"
  49. :max="1000"
  50. @on-blur="checkLevelValidate(level)"
  51. v-if="level.canEdit && workDetail.modifyOtherVal"
  52. ></InputNumber>
  53. <p v-else>{{ level.maxScore }}</p>
  54. </td>
  55. <td style="min-width: 100px">
  56. <InputNumber
  57. v-model="level.intervalScore"
  58. :min="1"
  59. :max="100"
  60. :precision="0"
  61. :disabled="!level.canEdit"
  62. @on-blur="checkLevelValidate(level)"
  63. v-if="
  64. level.levelType === 'ADMITED' &&
  65. level.canEdit &&
  66. workDetail.modifyOtherVal
  67. "
  68. ></InputNumber>
  69. <p v-else>{{ level.intervalScore }}</p>
  70. </td>
  71. <td>
  72. <InputNumber
  73. v-model="level.weight"
  74. :min="1"
  75. :max="1000"
  76. @on-blur="checkLevelValidate(level)"
  77. v-if="level.canEdit && workDetail.modifyOtherVal"
  78. ></InputNumber>
  79. <p v-else>{{ level.weight }}</p>
  80. </td>
  81. <td>
  82. <Select
  83. v-model="level.levelType"
  84. @on-change="levelTypeChange(level)"
  85. style="width: 120px"
  86. v-if="level.canEdit && workDetail.modifyOtherVal"
  87. >
  88. <Option
  89. v-for="(val, key) in LEVEL_TYPE"
  90. :key="key"
  91. :value="key"
  92. >{{ val }}</Option
  93. >
  94. </Select>
  95. <p v-else>{{ LEVEL_TYPE[level.levelType] }}</p>
  96. </td>
  97. <td style="min-width: 140px">
  98. <Input
  99. v-model="level.scoreList"
  100. @on-blur="checkLevelValidate(level)"
  101. v-if="
  102. level.levelType === 'UNADMIT' &&
  103. level.canEdit &&
  104. workDetail.modifyOtherVal
  105. "
  106. ></Input>
  107. <p v-else>{{ level.scoreList }}</p>
  108. </td>
  109. <td>
  110. <InputNumber
  111. v-model="level.kdpt"
  112. :min="1"
  113. :max="100"
  114. @on-blur="checkLevelValidate(level)"
  115. v-if="level.canEdit"
  116. ></InputNumber>
  117. <p v-else>{{ level.kdpt }}</p>
  118. </td>
  119. <td>
  120. <InputNumber
  121. v-model="level.pt"
  122. :min="1"
  123. :max="100"
  124. @on-blur="checkLevelValidate(level)"
  125. v-if="level.canEdit"
  126. ></InputNumber>
  127. <p v-else>{{ level.pt }}</p>
  128. </td>
  129. <td class="table-action">
  130. <div style="width: 60px">
  131. <Icon type="md-create" title="编辑" @click="toEdit(index)" />
  132. <Icon
  133. class="icon-danger"
  134. type="md-trash"
  135. title="删除"
  136. @click="toDelete(index)"
  137. v-if="workDetail.modifyOtherVal"
  138. />
  139. </div>
  140. </td>
  141. </tr>
  142. <tr class="tr-tips-error" v-if="level.errors" :key="index + '1'">
  143. <td>
  144. {{ level.errors.code }}
  145. </td>
  146. <td>
  147. {{ level.errors.minScore }}
  148. </td>
  149. <td>
  150. {{ level.errors.maxScore }}
  151. </td>
  152. <td>
  153. {{ level.errors.intervalScore }}
  154. </td>
  155. <td>
  156. {{ level.errors.weight }}
  157. </td>
  158. <td></td>
  159. <td>
  160. {{ level.errors.scoreList }}
  161. </td>
  162. <td>
  163. {{ level.errors.kdpt }}
  164. </td>
  165. <td>
  166. {{ level.errors.pt }}
  167. </td>
  168. <td></td>
  169. </tr>
  170. </template>
  171. </table>
  172. <div class="text-center">
  173. <Button
  174. type="primary"
  175. shape="circle"
  176. @click="toSubmit"
  177. style="width: 80px"
  178. :disabled="isSubmit"
  179. >确定</Button
  180. >
  181. </div>
  182. </div>
  183. </template>
  184. <script>
  185. import { workDetail, updateWork } from "@/api";
  186. import { LEVEL_TYPE } from "@/constants/enumerate";
  187. import schema from "async-validator";
  188. schema.warning = function () {};
  189. const initLevel = {
  190. id: null,
  191. workId: null,
  192. code: null,
  193. levelValue: 0,
  194. maxScore: null,
  195. minScore: null,
  196. intervalScore: null,
  197. weight: null,
  198. levelType: "ADMITED",
  199. scoreList: null,
  200. pt: null,
  201. kdpt: null,
  202. };
  203. export default {
  204. name: "grading-level-set",
  205. data() {
  206. return {
  207. LEVEL_TYPE,
  208. workId: this.$route.params.workId,
  209. letterRelateNumber: {},
  210. levels: [],
  211. workDetail: {},
  212. isSubmit: false,
  213. };
  214. },
  215. mounted() {
  216. this.initData();
  217. },
  218. methods: {
  219. initData() {
  220. const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  221. letters.split("").map((item, index) => {
  222. this.letterRelateNumber[item] = index + 1;
  223. });
  224. this.getData();
  225. },
  226. async getData() {
  227. const data = await workDetail(this.workId);
  228. this.workDetail = data;
  229. this.levels = data.levels.map((item) => {
  230. item.canEdit = false;
  231. return item;
  232. });
  233. },
  234. checkLevelCodeIsContinuous() {
  235. let levelIsContinuous = true;
  236. for (var i = 0, num = this.levels.length; i < num; i++) {
  237. if (i > 0) {
  238. const beforeCodeNum =
  239. this.letterRelateNumber[this.levels[i - 1].code];
  240. const curCodeNum = this.letterRelateNumber[this.levels[i].code];
  241. levelIsContinuous =
  242. levelIsContinuous && curCodeNum - beforeCodeNum === 1;
  243. if (!levelIsContinuous) {
  244. return false;
  245. }
  246. }
  247. }
  248. return true;
  249. },
  250. levelTypeChange(level) {
  251. if (level.levelType === "ADMITED") {
  252. level.scoreList = null;
  253. } else {
  254. level.intervalScore = null;
  255. }
  256. },
  257. codeChange(level) {
  258. level.code = level.code.toUpperCase();
  259. this.levels.sort((a, b) => {
  260. if (
  261. !a.code ||
  262. !b.code ||
  263. !this.letterRelateNumber[a.code] ||
  264. !this.letterRelateNumber[b.code]
  265. )
  266. return 0;
  267. return (
  268. this.letterRelateNumber[a.code] - this.letterRelateNumber[b.code]
  269. );
  270. });
  271. this.checkLevelValidate(level);
  272. },
  273. getNextLevelCode() {
  274. const codeNumbers = this.levels.map(
  275. (level) => this.letterRelateNumber[level.code] || 0
  276. );
  277. const maxCodeNumber = Math.max.apply(null, codeNumbers);
  278. const nextLevel = Object.entries(this.letterRelateNumber).find(
  279. ([key, val]) => {
  280. return maxCodeNumber < val;
  281. }
  282. );
  283. return nextLevel ? nextLevel[0] : "";
  284. },
  285. toAdd() {
  286. let level = { ...initLevel };
  287. level.workId = this.workId;
  288. level.code = this.getNextLevelCode();
  289. this.levels.push(level);
  290. },
  291. toEdit(index) {
  292. this.levels[index].canEdit = true;
  293. this.$forceUpdate();
  294. },
  295. toDelete(index) {
  296. this.levels.splice(index, 1);
  297. },
  298. checkLevelValidate(level) {
  299. const descriptor = {
  300. code: {
  301. type: "string",
  302. required: true,
  303. pattern: /^[A-Z]{1}$/,
  304. message: "请输入单个大写字母",
  305. },
  306. minScore: {
  307. type: "number",
  308. required: true,
  309. message: "请输入最低分",
  310. },
  311. maxScore: {
  312. type: "number",
  313. required: true,
  314. validator: (rule, value, callback) => {
  315. if (!value || value < level.minScore) {
  316. callback(new Error("最高分不得小于最低分"));
  317. } else {
  318. callback();
  319. }
  320. },
  321. },
  322. intervalScore: {
  323. type: "number",
  324. validator: (rule, value, callback) => {
  325. if (level.levelType === "ADMITED" && !value) {
  326. callback(new Error("请输入给分间隔"));
  327. } else {
  328. callback();
  329. }
  330. },
  331. },
  332. weight: {
  333. type: "number",
  334. required: true,
  335. message: "请输入典型值",
  336. },
  337. scoreList: {
  338. type: "string",
  339. validator: (rule, value, callback) => {
  340. if (level.levelType !== "UNADMIT") return callback();
  341. if (level.levelType === "UNADMIT" && !value) {
  342. return callback(new Error("请输入给分项"));
  343. }
  344. if (!value.match(/^[0-9,]+$/)) {
  345. return callback(new Error("给分项只能包含数字和英文逗号"));
  346. }
  347. const unvalid = value
  348. .split(",")
  349. .filter((item) => item)
  350. .some((item) => {
  351. const num = item * 1;
  352. return num < level.minScore || num > level.maxScore;
  353. });
  354. if (unvalid) {
  355. return callback(
  356. new Error("给分项包含的分值只能介于最低分和最高分之间")
  357. );
  358. }
  359. callback();
  360. },
  361. },
  362. pt: {
  363. type: "number",
  364. required: true,
  365. message: "请输入占比阀值",
  366. },
  367. kdpt: {
  368. type: "number",
  369. required: true,
  370. message: "请输入考区阀值",
  371. },
  372. };
  373. return new schema(descriptor)
  374. .validate(level)
  375. .then(() => {
  376. if (level.errors) level.errors = null;
  377. })
  378. .catch(({ errors, fields }) => {
  379. let errorMsgs = {};
  380. errors.map((error) => {
  381. errorMsgs[error.field] = error.message;
  382. });
  383. this.$set(level, "errors", errorMsgs);
  384. return { errors };
  385. });
  386. },
  387. async toSubmit() {
  388. const validatorAll = this.levels.map((level) =>
  389. this.checkLevelValidate(level)
  390. );
  391. const result = await Promise.all(validatorAll);
  392. const hasUnvalidate = result.some((item) => !!item);
  393. if (hasUnvalidate) return;
  394. if (!this.checkLevelCodeIsContinuous()) {
  395. this.$Message.error("请保持档位连续!");
  396. return;
  397. }
  398. if (this.isSubmit) return;
  399. this.isSubmit = true;
  400. this.workDetail.levels = this.levels;
  401. const data = await updateWork(this.workDetail).catch(() => {
  402. this.isSubmit = false;
  403. });
  404. if (!data) return;
  405. this.isSubmit = false;
  406. this.getData();
  407. this.$Message.success("保存成功!");
  408. },
  409. },
  410. };
  411. </script>