ModifyMarkParams.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. <template>
  2. <el-dialog
  3. class="modify-mark-params"
  4. :visible.sync="modalIsShow"
  5. title="评卷参数设置"
  6. top="0"
  7. :close-on-click-modal="false"
  8. :close-on-press-escape="false"
  9. append-to-body
  10. fullscreen
  11. :before-close="beforeClose"
  12. @open="visibleChange"
  13. >
  14. <el-steps
  15. class="mark-step"
  16. :active="current"
  17. align-center
  18. finish-status="success"
  19. >
  20. <el-step
  21. v-for="step in steps"
  22. :key="step.name"
  23. :title="step.title"
  24. :description="step.desc"
  25. >
  26. </el-step>
  27. </el-steps>
  28. <div class="mark-body" v-if="dataReady">
  29. <component
  30. :is="currentComponent"
  31. :ref="currentComponent"
  32. :datas="infos"
  33. @next-step="toNext"
  34. @on-ready="compReady"
  35. @data-change="dataChange"
  36. ></component>
  37. </div>
  38. <div class="text-center">
  39. <el-button
  40. v-if="!isFirstStep"
  41. type="primary"
  42. :disabled="loading"
  43. @click="prevStep"
  44. >上一步</el-button
  45. >
  46. <el-button
  47. v-if="isLastStep"
  48. type="success"
  49. :disabled="loading"
  50. @click="nextStep"
  51. >提交</el-button
  52. >
  53. <el-button v-else type="primary" @click="nextStep" :disabled="loading"
  54. >下一步</el-button
  55. >
  56. <el-button @click="cancel">取消</el-button>
  57. </div>
  58. <div slot="footer"></div>
  59. </el-dialog>
  60. </template>
  61. <script>
  62. import MarkPaperMarker from "./MarkPaperMarker.vue";
  63. import MarkPaperStructure from "./MarkPaperStructure.vue";
  64. // import paramData from "./paramData";
  65. import { examStructureSubmit } from "../../api";
  66. import { cardDetail } from "../../../card/api";
  67. import { calcSum } from "@/plugins/utils";
  68. const STEPS_LIST = [
  69. {
  70. name: "structure",
  71. title: "试卷结构",
  72. desc: "请按试卷填写相应试卷结构信息",
  73. },
  74. {
  75. name: "marker",
  76. title: "评卷员",
  77. desc: "请选择评卷老师及设置对应评卷题目",
  78. },
  79. ];
  80. export default {
  81. name: "modify-mark-params",
  82. components: { MarkPaperMarker, MarkPaperStructure },
  83. props: {
  84. instance: {
  85. type: Object,
  86. default() {
  87. return {};
  88. },
  89. },
  90. },
  91. data() {
  92. return {
  93. modalIsShow: false,
  94. infos: {
  95. basicPaperInfo: {},
  96. paperStructureInfo: [],
  97. groupInfo: [],
  98. paperStat: {},
  99. },
  100. // step
  101. steps: STEPS_LIST,
  102. current: 0,
  103. loading: false,
  104. dataReady: false,
  105. };
  106. },
  107. computed: {
  108. currentComponent() {
  109. return `mark-paper-${this.steps[this.current].name}`;
  110. },
  111. isFirstStep() {
  112. return this.current === 0;
  113. },
  114. isLastStep() {
  115. return this.current === this.lastStep;
  116. },
  117. lastStep() {
  118. return this.steps.length - 1;
  119. },
  120. },
  121. methods: {
  122. async visibleChange() {
  123. this.current = 0;
  124. this.loading = false;
  125. if (this.instance.paperInfoJson) {
  126. const { paperStructureInfo, groupInfo } = JSON.parse(
  127. this.instance.paperInfoJson
  128. );
  129. this.infos = {
  130. paperStructureInfo: [
  131. ...paperStructureInfo.objectiveQuestionList,
  132. ...paperStructureInfo.subjectiveQuestionList,
  133. ],
  134. groupInfo,
  135. basicPaperInfo: { ...this.instance },
  136. };
  137. this.infos.paperStat = this.statPaperStructure(
  138. this.infos.paperStructureInfo
  139. );
  140. this.dataReady = true;
  141. return;
  142. }
  143. const detData = await cardDetail(this.instance.cardId);
  144. const cardContent = JSON.parse(detData.content);
  145. this.infos = {
  146. paperStructureInfo: this.parsePaperStructureFromCard(cardContent.pages),
  147. groupInfo: [],
  148. basicPaperInfo: { ...this.instance },
  149. paperStat: this.statPaperStructure([]),
  150. };
  151. this.dataReady = true;
  152. },
  153. parsePaperStructureFromCard(pages) {
  154. let structData = [];
  155. let curTopicId = 0;
  156. pages.forEach((page) => {
  157. page.columns.forEach((column) => {
  158. column.elements.forEach((topic) => {
  159. if (!topic.parent) return;
  160. if (curTopicId === topic.parent.id) return;
  161. curTopicId = topic.parent.id;
  162. let questionsCount = 1,
  163. startNumber = 1;
  164. if (topic.type === "COMPOSITION") {
  165. // 相同大题号的作文题合并处理
  166. const compositionData = structData.find(
  167. (struct) =>
  168. struct.cardTopicType === "COMPOSITION" &&
  169. struct.mainNumber === topic.parent.topicNo * 1
  170. );
  171. if (compositionData) return;
  172. } else {
  173. questionsCount = topic.parent.questionsCount;
  174. startNumber = topic.parent.startNumber || 1;
  175. }
  176. const typeInfo = this.getQuestionType(topic);
  177. if (!typeInfo) return;
  178. let data = {
  179. mainNumber: topic.parent.topicNo * 1,
  180. mainTitle: topic.parent.topicName,
  181. questionsCount,
  182. startNumber,
  183. cardTopicType: topic.type,
  184. ...typeInfo,
  185. };
  186. if (topic.type === "FILL_QUESTION") {
  187. data.optionCount = topic.optionCount;
  188. }
  189. structData.push(data);
  190. });
  191. });
  192. });
  193. let structure = [];
  194. let mainIds = {};
  195. structData.forEach((struct) => {
  196. const { startNumber, questionsCount, qType, mainNumber, mainTitle } =
  197. struct;
  198. if (!mainIds[mainNumber]) {
  199. mainIds[mainNumber] = this.$randomCode();
  200. }
  201. for (let i = 0; i < questionsCount; i++) {
  202. structure.push({
  203. id: this.$randomCode(),
  204. qType,
  205. mainId: mainIds[mainNumber],
  206. mainTitle,
  207. mainNumber,
  208. subNumber: startNumber + i,
  209. type: struct.type,
  210. typeName: struct.typeName,
  211. optionCount: struct.optionCount || null,
  212. totalScore: undefined,
  213. mainFirstSub: false,
  214. expandSub: true,
  215. });
  216. }
  217. });
  218. structure.sort(
  219. (a, b) => a.mainNumber - b.mainNumber || a.subNumber - b.subNumber
  220. );
  221. let curMainId = null;
  222. structure.forEach((item) => {
  223. if (curMainId !== item.mainId) {
  224. curMainId = item.mainId;
  225. item.mainFirstSub = true;
  226. }
  227. });
  228. return structure;
  229. },
  230. getQuestionType(topic) {
  231. // const questionType = {
  232. // 1: "SINGLE_ANSWER_QUESTION",
  233. // 2: "MULTIPLE_ANSWER_QUESTION",
  234. // 3: "BOOL_ANSWER_QUESTION",
  235. // 4: "FILL_BLANK_QUESTION",
  236. // 5: "TEXT_ANSWER_QUESTION",
  237. // };
  238. if (topic.type === "COMPOSITION" || topic.type === "EXPLAIN") {
  239. return {
  240. type: 5,
  241. typeName: "解答题",
  242. qType: "subjective",
  243. };
  244. }
  245. if (topic.type === "FILL_LINE") {
  246. return {
  247. type: 4,
  248. typeName: "填空题",
  249. qType: "subjective",
  250. };
  251. }
  252. if (topic.type === "FILL_QUESTION") {
  253. if (topic.isBoolean)
  254. return {
  255. type: 3,
  256. typeName: "判断题",
  257. qType: "objective",
  258. };
  259. if (topic.isMultiply)
  260. return {
  261. type: 2,
  262. typeName: "多选题",
  263. qType: "objective",
  264. };
  265. return {
  266. type: 1,
  267. typeName: "单选题",
  268. qType: "objective",
  269. };
  270. }
  271. return;
  272. },
  273. statPaperStructure(paperStructureInfo) {
  274. const questionCount = paperStructureInfo.length;
  275. const subjectiveQuestionCount = paperStructureInfo.filter(
  276. (item) => item.qType === "subjective"
  277. ).length;
  278. return {
  279. questionCount,
  280. paperTotalScore: calcSum(
  281. paperStructureInfo.map((item) => item.totalScore || 0)
  282. ),
  283. subjectiveQuestionCount,
  284. objectiveQuestionCount: questionCount - subjectiveQuestionCount,
  285. structChanged: false,
  286. };
  287. },
  288. async cancel() {
  289. const res = await this.$confirm("确定要退出阅卷参数编辑吗?", "提示", {
  290. type: "warning",
  291. }).catch(() => {});
  292. if (res !== "confirm") return;
  293. this.modalIsShow = false;
  294. this.dataReady = false;
  295. },
  296. open() {
  297. this.modalIsShow = true;
  298. },
  299. async beforeClose(done) {
  300. const res = await this.$confirm("确定要退出阅卷参数编辑吗?", "提示", {
  301. type: "warning",
  302. }).catch(() => {});
  303. if (res !== "confirm") return;
  304. this.dataReady = false;
  305. done();
  306. },
  307. prevStep() {
  308. if (this.isFirstStep) return;
  309. this.$refs[this.currentComponent].updateData();
  310. this.current -= 1;
  311. if (this.steps[this.current].name === "structure") {
  312. this.$notify({
  313. title: "警告",
  314. message: "修改试卷结构会清空已经设置的评卷员,需要重新设置!",
  315. type: "warning",
  316. duration: 5000,
  317. });
  318. }
  319. },
  320. nextStep() {
  321. this.$refs[this.currentComponent].checkData();
  322. },
  323. toNext() {
  324. if (this.isLastStep) {
  325. this.submit();
  326. } else {
  327. const paperStat = this.infos.paperStat;
  328. let tipsContent = `本试卷共${paperStat.questionCount}道小题,客观题${paperStat.objectiveQuestionCount}道,主观题${paperStat.subjectiveQuestionCount}道,总分为${paperStat.paperTotalScore}分。`;
  329. if (paperStat.structChanged) {
  330. tipsContent += "试卷结构有变动,评卷员将被清空。";
  331. }
  332. if (paperStat.subjectiveQuestionCount) {
  333. this.$confirm(`${tipsContent}确定要下一步吗?`, "提示", {
  334. type: "warning",
  335. })
  336. .then(() => {
  337. this.current += 1;
  338. })
  339. .catch(() => {});
  340. } else {
  341. // 没有主观题,可以直接提交,不需要设置评卷员分组
  342. this.$confirm(`${tipsContent}, 是否立即提交?`, "提示", {
  343. type: "warning",
  344. })
  345. .then(() => {
  346. this.submit();
  347. })
  348. .catch(() => {});
  349. }
  350. }
  351. },
  352. getPaperStructData(paperStructureInfo) {
  353. let originStruct = [];
  354. let group = [];
  355. let curMainId = null;
  356. let curQType = null;
  357. paperStructureInfo.forEach((item) => {
  358. if (curMainId !== item.mainId) {
  359. curMainId = item.mainId;
  360. curQType = item.qType[0];
  361. if (group.length) originStruct.push(`${curQType}${group.length}`);
  362. group = [];
  363. }
  364. group.push(item);
  365. });
  366. if (group.length) originStruct.push(`${curQType}${group.length}`);
  367. return originStruct;
  368. },
  369. dataChange(data) {
  370. if (data.paperStructureInfo) {
  371. data.paperStat = this.statPaperStructure(data.paperStructureInfo);
  372. }
  373. if (data.paperStructureInfo && this.infos.paperStructureInfo.length) {
  374. // 检验试卷结构是否有变化
  375. const originStruct = this.getPaperStructData(
  376. this.infos.paperStructureInfo
  377. );
  378. const curStruct = this.getPaperStructData(data.paperStructureInfo);
  379. if (curStruct.join("") !== originStruct.join("")) {
  380. data.groupInfo = [];
  381. data.paperStat.structChanged = true;
  382. } else {
  383. // 更新分组中的试题信息
  384. const paperMap = {};
  385. data.paperStructureInfo.forEach((item) => {
  386. paperMap[`${item.mainNumber}-${item.subNumber}`] = item;
  387. });
  388. const groupInfo = this.infos.groupInfo.map((group) => {
  389. let ngroup = { ...group };
  390. ngroup.questions = ngroup.questions.map((q) => {
  391. return { ...paperMap[`${q.mainNumber}-${q.subNumber}`] };
  392. });
  393. return ngroup;
  394. });
  395. data.groupInfo = groupInfo;
  396. }
  397. }
  398. Object.entries(data).forEach(([key, val]) => {
  399. this.infos[key] = val;
  400. });
  401. },
  402. compReady(type = false) {
  403. this.loading = type;
  404. },
  405. getBaseInfo(baseInfo) {
  406. const data = this.$objAssign(
  407. {
  408. examPaperStructureId: null,
  409. examId: null,
  410. thirdRelateId: null,
  411. thirdRelateName: "",
  412. courseName: "",
  413. courseCode: "",
  414. paperNumber: null,
  415. paperType: "",
  416. sequence: null,
  417. status: "",
  418. },
  419. baseInfo
  420. );
  421. data.examPaperStructureId = baseInfo.id;
  422. return data;
  423. },
  424. async submit() {
  425. if (this.loading) return;
  426. const hasGroupNoPic = this.infos.groupInfo.some(
  427. (item) => !item.pictureConfigList.length
  428. );
  429. if (hasGroupNoPic) {
  430. const confirm = await this.$confirm(
  431. `当前评卷员分组中存在没有设置评卷区的分组, 确定要提交吗?`,
  432. "提示",
  433. {
  434. type: "warning",
  435. }
  436. ).catch(() => {});
  437. if (confirm !== "confirm") return;
  438. }
  439. this.loading = true;
  440. const datas = {
  441. basicPaperInfo: this.getBaseInfo(this.infos.basicPaperInfo),
  442. paperStructureInfo: {
  443. objectiveQuestionList: this.infos.paperStructureInfo.filter(
  444. (item) => item.qType === "objective"
  445. ),
  446. subjectiveQuestionList: this.infos.paperStructureInfo.filter(
  447. (item) => item.qType === "subjective"
  448. ),
  449. },
  450. groupInfo: this.infos.groupInfo,
  451. };
  452. const res = await examStructureSubmit(datas).catch(() => false);
  453. this.loading = false;
  454. if (!res) return;
  455. this.$message.success("提交成功!");
  456. this.$emit("modified");
  457. this.modalIsShow = false;
  458. this.dataReady = false;
  459. },
  460. },
  461. };
  462. </script>