PreviewPaperDialog.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. <template>
  2. <el-dialog
  3. class="preview-paper-dialog"
  4. :visible.sync="visible"
  5. title="查看原卷"
  6. top="0"
  7. :close-on-click-modal="false"
  8. :close-on-press-escape="false"
  9. append-to-body
  10. fullscreen
  11. destroy-on-close
  12. @open="visibleChange"
  13. >
  14. <div ref="PaperBody" class="paper-body">
  15. <div
  16. v-if="renderStructList.length"
  17. class="paper-content paper-content-long"
  18. >
  19. <cont-item
  20. v-for="(item, index) in renderStructList"
  21. :key="index"
  22. :data="item"
  23. ></cont-item>
  24. </div>
  25. <div
  26. v-for="(group, gindex) in renderGroupStructList"
  27. :key="gindex"
  28. class="paper-content"
  29. >
  30. <cont-item
  31. v-for="(item, index) in group"
  32. :key="index"
  33. :data="item"
  34. ></cont-item>
  35. </div>
  36. </div>
  37. <div slot="footer"></div>
  38. </el-dialog>
  39. </template>
  40. <script>
  41. import { studentExamDetailInfo } from "@/api/examwork-student";
  42. import { numberToChinese, numberToUpperCase } from "./spins/renderJSON";
  43. import ContItem from "./spins/ContItem.vue";
  44. import previewTem from "./spins/previewTem";
  45. import { randomCode } from "@/utils/utils";
  46. import { STRUCT_TYPES, MAX_WIDTH, MAX_HEIGHT } from "./spins/paperSetting";
  47. const OPTION_NAME = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  48. export default {
  49. name: "preview-paper-dialog",
  50. components: { ContItem },
  51. props: {
  52. examRecordId: {
  53. type: String,
  54. },
  55. },
  56. data() {
  57. return {
  58. visible: false,
  59. paperStruct: {},
  60. renderStructList: [],
  61. renderGroupStructList: [],
  62. };
  63. },
  64. methods: {
  65. visibleChange() {
  66. this.paperStruct = {};
  67. this.renderStructList = [];
  68. this.renderGroupStructList = [];
  69. this.getRecordDetail();
  70. },
  71. openDialog() {
  72. this.visible = true;
  73. },
  74. closeDialog() {
  75. this.visible = false;
  76. },
  77. async getRecordDetail() {
  78. const res = await studentExamDetailInfo(this.examRecordId);
  79. this.parsePaperStruct(res.data.data);
  80. this.parseRenderStructList();
  81. const loadres = await this.waitAllImgLoaded().catch(() => {});
  82. if (!loadres) {
  83. this.$message.error("图片加载有误!");
  84. return;
  85. }
  86. this.$nextTick(() => {
  87. this.buildRenderGroupStructList();
  88. });
  89. },
  90. checkIsMatches(structType) {
  91. const matchesTypes = [8, 9, 10];
  92. return matchesTypes.includes(structType);
  93. },
  94. parsePaperStruct(data) {
  95. const paperStructJson = data.paperStructJson
  96. ? JSON.parse(data.paperStructJson)
  97. : { details: [] };
  98. const answerJson = data.answerJson
  99. ? JSON.parse(data.answerJson)
  100. : { details: [] };
  101. const examStudentAnswerJson = data.examStudentAnswerJson
  102. ? JSON.parse(data.examStudentAnswerJson)
  103. : [];
  104. const subjectiveScoreDetailJson = data.subjectiveScoreDetailJson
  105. ? JSON.parse(data.subjectiveScoreDetailJson)
  106. : [];
  107. const randomPaperStructPath = data.randomPaperStructPath
  108. ? JSON.parse(data.randomPaperStructPath)
  109. : { details: [] };
  110. // console.log(JSON.stringify(randomPaperStructPath));
  111. // console.log(paperStructJson);
  112. // console.log(answerJson);
  113. // console.log(examStudentAnswerJson);
  114. const isEmpty = (cont) =>
  115. cont === undefined || cont === null || cont === "";
  116. const answerMap = {};
  117. answerJson.details.forEach((detail) => {
  118. detail.questions.forEach((question) => {
  119. const k = `${detail.number}-${question.number}`;
  120. if (question.subQuestions) {
  121. question.subQuestions.forEach((sq) => {
  122. answerMap[`${k}-${sq.number}`] = sq.answer;
  123. });
  124. } else {
  125. answerMap[k] = question.answer;
  126. }
  127. });
  128. });
  129. const studentAnsMap = {};
  130. examStudentAnswerJson.forEach((question) => {
  131. const k = `${question.mainNumber}-${question.subNumber}`;
  132. if (question.subIndex !== null) {
  133. studentAnsMap[`${k}-${question.subIndex}`] = {
  134. answer: question.answer ? JSON.parse(question.answer) : "",
  135. score: question.score,
  136. };
  137. } else {
  138. studentAnsMap[k] = {
  139. answer: question.answer ? JSON.parse(question.answer) : "",
  140. score: question.score,
  141. };
  142. }
  143. });
  144. subjectiveScoreDetailJson.forEach((question) => {
  145. let k = `${question.mainNumber}-${question.subNumber}`;
  146. if (question.subIndex) {
  147. k = `${k}-${question.subIndex}`;
  148. }
  149. if (studentAnsMap[k]) {
  150. studentAnsMap[k].score = question.score;
  151. } else {
  152. studentAnsMap[k] = {
  153. score: question.score,
  154. answer: null,
  155. };
  156. }
  157. });
  158. // detail
  159. paperStructJson.details.forEach((detail) => {
  160. detail.questions.forEach((question) => {
  161. let k = `${detail.number}-${question.number}`;
  162. if (question.subQuestions) {
  163. const isMatches = this.checkIsMatches(question.structType);
  164. question.subQuestions.forEach((sq) => {
  165. if (isMatches) sq.options = [];
  166. const sqk = `${k}-${sq.number}`;
  167. sq.answer = isEmpty(answerMap[sqk]) ? null : answerMap[sqk];
  168. const stdAns = studentAnsMap[sqk] || {
  169. answer: null,
  170. score: null,
  171. };
  172. sq.studentAnswer = stdAns.answer;
  173. sq.studentScore = stdAns.score;
  174. });
  175. } else {
  176. question.answer = isEmpty(answerMap[k]) ? null : answerMap[k];
  177. const stdAns = studentAnsMap[k] || { answer: null, score: null };
  178. question.studentAnswer = stdAns.answer;
  179. question.studentScore = stdAns.score;
  180. }
  181. });
  182. });
  183. let studentPaperStructJson = { ...paperStructJson };
  184. if (randomPaperStructPath.details.length) {
  185. studentPaperStructJson.details = randomPaperStructPath.details.map(
  186. (detail, dindex) => {
  187. // const rDetail = { ...paperStructJson.details[detail.number - 1] };
  188. const rDetail = paperStructJson.details.find(
  189. (item) => item.number === detail.number
  190. );
  191. if (!rDetail) return;
  192. rDetail.number = dindex + 1;
  193. rDetail.questions = detail.questions.map((question, qindex) => {
  194. // 试题乱序
  195. const rQuestion = { ...rDetail.questions[question.number - 1] };
  196. rQuestion.number = qindex + 1;
  197. if (question.options) {
  198. // 选项乱序
  199. let optionMap = {};
  200. rQuestion.options = question.options.map((opNo, oindex) => {
  201. const rOption = { ...rQuestion.options[opNo - 1] };
  202. rOption.number = oindex + 1;
  203. optionMap[opNo] = oindex + 1;
  204. return rOption;
  205. });
  206. if (rQuestion.answer) {
  207. rQuestion.answer = rQuestion.answer.map(
  208. (ans) => optionMap[ans]
  209. );
  210. rQuestion.answer.sort((a, b) => a - b);
  211. }
  212. if (rQuestion.studentAnswer) {
  213. rQuestion.studentAnswer = rQuestion.studentAnswer.map(
  214. (ans) => optionMap[ans]
  215. );
  216. rQuestion.studentAnswer.sort((a, b) => a - b);
  217. }
  218. }
  219. return rQuestion;
  220. });
  221. return rDetail;
  222. }
  223. );
  224. }
  225. this.paperStruct = studentPaperStructJson;
  226. },
  227. transformRichJson(richJson) {
  228. if (!richJson || !richJson.sections) return [];
  229. let contents = [];
  230. let curBlock = [];
  231. const checkNeedSplitSection = (block) => {
  232. if (block.type !== "image") return false;
  233. if (block.param) {
  234. if (block.param.width) return block.param.width > MAX_WIDTH / 2;
  235. if (block.param.height) return block.param.height > 150;
  236. }
  237. return true;
  238. };
  239. richJson.sections.forEach((section) => {
  240. section.blocks.forEach((block) => {
  241. if (checkNeedSplitSection(block) && curBlock.length) {
  242. contents.push({ sections: [{ blocks: [...curBlock] }] });
  243. curBlock = [];
  244. }
  245. curBlock.push(block);
  246. });
  247. if (curBlock.length) {
  248. contents.push({ sections: [{ blocks: [...curBlock] }] });
  249. curBlock = [];
  250. }
  251. });
  252. return contents;
  253. },
  254. parseRenderStructList() {
  255. let renderStructList = [];
  256. renderStructList.push({
  257. cls: "paper-name",
  258. type: "text",
  259. content: this.paperStruct.name,
  260. });
  261. this.paperStruct.details.forEach((detail) => {
  262. renderStructList.push({
  263. cls: "detail-name",
  264. type: "text",
  265. content: `${numberToChinese(detail.number)}、${detail.name}`,
  266. });
  267. const dbodys = this.parseTopicDesc(detail.description);
  268. renderStructList.push(...dbodys);
  269. detail.questions.forEach((question) => {
  270. if (question.subQuestions) {
  271. const bodys = this.parseTopicTitle(
  272. question.body,
  273. `${question.number}、`
  274. );
  275. renderStructList.push(...bodys);
  276. const isMatches = this.checkIsMatches(question.structType);
  277. if (isMatches && question.options && question.options.length) {
  278. question.options.forEach((op) => {
  279. const obodys = this.parseTopicOption(op.body, op.number);
  280. renderStructList.push(...obodys);
  281. });
  282. }
  283. question.subQuestions.forEach((sq) => {
  284. const contents = this.parseSimpleQuestion(sq, false);
  285. renderStructList.push(...contents);
  286. });
  287. } else {
  288. const contents = this.parseSimpleQuestion(question, true);
  289. renderStructList.push(...contents);
  290. }
  291. });
  292. });
  293. this.renderStructList = renderStructList.map((item) => {
  294. item.id = randomCode();
  295. return item;
  296. });
  297. // console.log(renderStructList);
  298. },
  299. parseSimpleQuestion(question, isCommon) {
  300. let contents = [];
  301. const tbodys = this.parseTopicTitle(
  302. question.body,
  303. isCommon ? `${question.number}、` : `${question.number})`
  304. );
  305. contents.push(...tbodys);
  306. if (question.options && question.options.length) {
  307. question.options.forEach((op) => {
  308. const obodys = this.parseTopicOption(op.body, op.number);
  309. contents.push(...obodys);
  310. });
  311. }
  312. if (question.answer !== null) {
  313. // console.log(question.answer);
  314. const conts = this.parseTopicAnswer(
  315. question.answer,
  316. question.structType,
  317. false
  318. );
  319. contents.push(...conts);
  320. }
  321. if (question.studentAnswer !== null) {
  322. const conts = this.parseTopicAnswer(
  323. question.studentAnswer,
  324. question.structType,
  325. true
  326. );
  327. contents.push(...conts);
  328. }
  329. contents.push({
  330. cls: "topic-score",
  331. type: "text",
  332. content: "得分:" + (question.studentScore || 0),
  333. });
  334. return contents;
  335. },
  336. parseTopicDesc(richJson) {
  337. if (!richJson) return [];
  338. const bodys = this.transformRichJson(richJson);
  339. return bodys.map((body) => {
  340. return {
  341. cls: "detail-desc",
  342. type: "json",
  343. content: body,
  344. };
  345. });
  346. },
  347. parseTopicTitle(richJson, numberVal) {
  348. if (!richJson) {
  349. return {
  350. cls: "topic-title",
  351. type: "json",
  352. content: {
  353. sections: [
  354. {
  355. blocks: [
  356. {
  357. type: "text",
  358. value: numberVal,
  359. param: { bold: true },
  360. },
  361. ],
  362. },
  363. ],
  364. },
  365. };
  366. }
  367. const bodys = this.transformRichJson(richJson);
  368. return bodys.map((body, index) => {
  369. if (index === 0) {
  370. body.sections[0].blocks.unshift({
  371. type: "text",
  372. value: numberVal,
  373. param: { bold: true },
  374. });
  375. }
  376. return {
  377. cls: "topic-title",
  378. type: "json",
  379. content: body,
  380. };
  381. });
  382. },
  383. parseTopicOption(richJson, number) {
  384. const bodys = this.transformRichJson(richJson);
  385. return bodys.map((body, index) => {
  386. if (index === 0) {
  387. body.sections[0].blocks.unshift({
  388. type: "text",
  389. value: `${numberToUpperCase(number)}、`,
  390. });
  391. }
  392. return {
  393. cls: "topic-option",
  394. type: "json",
  395. content: body,
  396. };
  397. });
  398. },
  399. parseTopicAnswer(answer, structType, isStdAns) {
  400. // console.log(answer, structType, isStdAns);
  401. const answerTitleBlock = {
  402. type: "text",
  403. value: isStdAns ? "学生答案:" : "标准答案:",
  404. param: {
  405. bold: true,
  406. },
  407. };
  408. let contents = [];
  409. if (
  410. structType === STRUCT_TYPES.FILL_BLANK ||
  411. structType === STRUCT_TYPES.TEXT
  412. ) {
  413. contents.push({
  414. cls: "topic-answer",
  415. type: "json",
  416. content: {
  417. sections: [
  418. {
  419. blocks: [{ ...answerTitleBlock }],
  420. },
  421. ],
  422. },
  423. });
  424. answer.forEach((item) => {
  425. const bodys = this.transformRichJson(item);
  426. const conts = bodys.map((body, index) => {
  427. if (index === 0) {
  428. // body.sections[0].blocks.unshift({
  429. // type: "text",
  430. // value: "",
  431. // });
  432. }
  433. return {
  434. cls: "topic-answer",
  435. type: "json",
  436. content: body,
  437. };
  438. });
  439. contents.push(...conts);
  440. });
  441. } else {
  442. let ansCont = "";
  443. if (structType === STRUCT_TYPES.BOOLEAN_CHOICE) {
  444. ansCont = answer ? "对" : "错";
  445. } else {
  446. ansCont = answer.map((item) => OPTION_NAME[item - 1]).join("");
  447. }
  448. contents.push({
  449. cls: "topic-answer",
  450. type: "json",
  451. content: {
  452. sections: [
  453. {
  454. blocks: [
  455. { ...answerTitleBlock },
  456. { type: "text", value: ansCont },
  457. ],
  458. },
  459. ],
  460. },
  461. });
  462. }
  463. return contents;
  464. },
  465. loadImg(url) {
  466. return new Promise((resolve, reject) => {
  467. const img = new Image();
  468. img.onload = function () {
  469. resolve(true);
  470. };
  471. img.onerror = function () {
  472. reject();
  473. };
  474. img.src = url;
  475. });
  476. },
  477. getRichJsonImgUrls(richJson) {
  478. let urls = [];
  479. if (!richJson) return urls;
  480. richJson.sections.forEach((section) => {
  481. section.blocks.forEach((elem) => {
  482. if (elem.type === "image") {
  483. urls.push(elem.value);
  484. }
  485. });
  486. });
  487. return urls;
  488. },
  489. async waitAllImgLoaded() {
  490. let imgUrls = [];
  491. this.renderStructList.forEach((item) => {
  492. if (item.type === "text") return;
  493. imgUrls.push(...this.getRichJsonImgUrls(item.content));
  494. });
  495. // console.log(imgUrls);
  496. if (!imgUrls.length) return Promise.resolve(true);
  497. const imgLoads = imgUrls.map((item) => this.loadImg(item));
  498. const imgLoadResult = await Promise.all(imgLoads).catch(() => {});
  499. if (imgLoadResult && imgLoadResult.length) {
  500. return Promise.resolve(true);
  501. } else {
  502. return Promise.reject();
  503. }
  504. },
  505. buildRenderGroupStructList() {
  506. let curGroup = [],
  507. curGroupHeight = 0;
  508. let renderGroupStructList = [];
  509. this.renderStructList.forEach((item) => {
  510. const itemHeight = document.getElementById(item.id).clientHeight;
  511. if (curGroupHeight + itemHeight > MAX_HEIGHT) {
  512. if (curGroup.length) renderGroupStructList.push(curGroup);
  513. curGroup = [];
  514. curGroupHeight = 0;
  515. }
  516. curGroup.push(item);
  517. curGroupHeight += itemHeight;
  518. });
  519. if (curGroup.length) renderGroupStructList.push(curGroup);
  520. this.renderGroupStructList = renderGroupStructList;
  521. this.renderStructList = [];
  522. },
  523. getPreviewHtml() {
  524. const html = previewTem(this.$refs.PaperBody.innerHTML);
  525. console.log(html);
  526. },
  527. },
  528. };
  529. </script>
  530. <style src="@/styles/paper-preview.css"></style>