PreviewPaperDialog.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  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: JSON.parse(question.answer),
  135. score: question.score,
  136. };
  137. } else {
  138. studentAnsMap[k] = {
  139. 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. rDetail.number = dindex + 1;
  189. rDetail.questions = detail.questions.map((question, qindex) => {
  190. // 试题乱序
  191. const rQuestion = { ...rDetail.questions[question.number - 1] };
  192. rQuestion.number = qindex + 1;
  193. if (question.options) {
  194. // 选项乱序
  195. let optionMap = {};
  196. rQuestion.options = question.options.map((opNo, oindex) => {
  197. const rOption = { ...rQuestion.options[opNo - 1] };
  198. rOption.number = oindex + 1;
  199. optionMap[opNo] = oindex + 1;
  200. return rOption;
  201. });
  202. if (rQuestion.answer) {
  203. rQuestion.answer = rQuestion.answer.map(
  204. (ans) => optionMap[ans]
  205. );
  206. }
  207. if (rQuestion.studentAnswer) {
  208. rQuestion.studentAnswer = rQuestion.studentAnswer.map(
  209. (ans) => optionMap[ans]
  210. );
  211. }
  212. }
  213. return rQuestion;
  214. });
  215. return rDetail;
  216. }
  217. );
  218. }
  219. this.paperStruct = studentPaperStructJson;
  220. },
  221. transformRichJson(richJson) {
  222. let contents = [];
  223. let curBlock = [];
  224. const checkNeedSplitSection = (block) => {
  225. if (block.type !== "image") return false;
  226. if (block.param) {
  227. if (block.param.width) return block.param.width > MAX_WIDTH / 2;
  228. if (block.param.height) return block.param.height > 150;
  229. }
  230. return true;
  231. };
  232. richJson.sections.forEach((section) => {
  233. section.blocks.forEach((block) => {
  234. if (checkNeedSplitSection(block) && curBlock.length) {
  235. contents.push({ sections: [{ blocks: [...curBlock] }] });
  236. curBlock = [];
  237. }
  238. curBlock.push(block);
  239. });
  240. if (curBlock.length) {
  241. contents.push({ sections: [{ blocks: [...curBlock] }] });
  242. curBlock = [];
  243. }
  244. });
  245. return contents;
  246. },
  247. parseRenderStructList() {
  248. let renderStructList = [];
  249. renderStructList.push({
  250. cls: "paper-name",
  251. type: "text",
  252. content: this.paperStruct.name,
  253. });
  254. this.paperStruct.details.forEach((detail) => {
  255. renderStructList.push({
  256. cls: "detail-name",
  257. type: "text",
  258. content: `${numberToChinese(detail.number)}、${detail.name}`,
  259. });
  260. const dbodys = this.parseTopicDesc(detail.description);
  261. renderStructList.push(...dbodys);
  262. detail.questions.forEach((question) => {
  263. if (question.subQuestions) {
  264. const bodys = this.parseTopicTitle(
  265. question.body,
  266. `${question.number}、`
  267. );
  268. renderStructList.push(...bodys);
  269. const isMatches = this.checkIsMatches(question.structType);
  270. if (isMatches && question.options && question.options.length) {
  271. question.options.forEach((op) => {
  272. const obodys = this.parseTopicOption(op.body, op.number);
  273. renderStructList.push(...obodys);
  274. });
  275. }
  276. question.subQuestions.forEach((sq) => {
  277. const contents = this.parseSimpleQuestion(sq, false);
  278. renderStructList.push(...contents);
  279. });
  280. } else {
  281. const contents = this.parseSimpleQuestion(question, true);
  282. renderStructList.push(...contents);
  283. }
  284. });
  285. });
  286. this.renderStructList = renderStructList.map((item) => {
  287. item.id = randomCode();
  288. return item;
  289. });
  290. // console.log(renderStructList);
  291. },
  292. parseSimpleQuestion(question, isCommon) {
  293. let contents = [];
  294. const tbodys = this.parseTopicTitle(
  295. question.body,
  296. isCommon ? `${question.number}、` : `${question.number})`
  297. );
  298. contents.push(...tbodys);
  299. if (question.options && question.options.length) {
  300. question.options.forEach((op) => {
  301. const obodys = this.parseTopicOption(op.body, op.number);
  302. contents.push(...obodys);
  303. });
  304. }
  305. if (question.answer !== null) {
  306. // console.log(question.answer);
  307. const conts = this.parseTopicAnswer(
  308. question.answer,
  309. question.structType,
  310. false
  311. );
  312. contents.push(...conts);
  313. }
  314. if (question.studentAnswer !== null) {
  315. const conts = this.parseTopicAnswer(
  316. question.studentAnswer,
  317. question.structType,
  318. true
  319. );
  320. contents.push(...conts);
  321. }
  322. contents.push({
  323. cls: "topic-score",
  324. type: "text",
  325. content: "得分:" + (question.studentScore || 0),
  326. });
  327. return contents;
  328. },
  329. parseTopicDesc(richJson) {
  330. const bodys = this.transformRichJson(richJson);
  331. return bodys.map((body) => {
  332. return {
  333. cls: "detail-desc",
  334. type: "json",
  335. content: body,
  336. };
  337. });
  338. },
  339. parseTopicTitle(richJson, numberVal) {
  340. if (!richJson) {
  341. return {
  342. cls: "topic-title",
  343. type: "json",
  344. content: {
  345. sections: [
  346. {
  347. blocks: [
  348. {
  349. type: "text",
  350. value: numberVal,
  351. param: { bold: true },
  352. },
  353. ],
  354. },
  355. ],
  356. },
  357. };
  358. }
  359. const bodys = this.transformRichJson(richJson);
  360. return bodys.map((body, index) => {
  361. if (index === 0) {
  362. body.sections[0].blocks.unshift({
  363. type: "text",
  364. value: numberVal,
  365. param: { bold: true },
  366. });
  367. }
  368. return {
  369. cls: "topic-title",
  370. type: "json",
  371. content: body,
  372. };
  373. });
  374. },
  375. parseTopicOption(richJson, number) {
  376. const bodys = this.transformRichJson(richJson);
  377. return bodys.map((body, index) => {
  378. if (index === 0) {
  379. body.sections[0].blocks.unshift({
  380. type: "text",
  381. value: `${numberToUpperCase(number)}、`,
  382. });
  383. }
  384. return {
  385. cls: "topic-option",
  386. type: "json",
  387. content: body,
  388. };
  389. });
  390. },
  391. parseTopicAnswer(answer, structType, isStdAns) {
  392. // console.log(answer, structType, isStdAns);
  393. const answerTitleBlock = {
  394. type: "text",
  395. value: isStdAns ? "学生答案:" : "标准答案:",
  396. param: {
  397. bold: true,
  398. },
  399. };
  400. let contents = [];
  401. if (
  402. structType === STRUCT_TYPES.FILL_BLANK ||
  403. structType === STRUCT_TYPES.TEXT
  404. ) {
  405. contents.push({
  406. cls: "topic-answer",
  407. type: "json",
  408. content: {
  409. sections: [
  410. {
  411. blocks: [{ ...answerTitleBlock }],
  412. },
  413. ],
  414. },
  415. });
  416. answer.forEach((item) => {
  417. const bodys = this.transformRichJson(item);
  418. const conts = bodys.map((body, index) => {
  419. if (index === 0) {
  420. // body.sections[0].blocks.unshift({
  421. // type: "text",
  422. // value: "",
  423. // });
  424. }
  425. return {
  426. cls: "topic-answer",
  427. type: "json",
  428. content: body,
  429. };
  430. });
  431. contents.push(...conts);
  432. });
  433. } else {
  434. let ansCont = "";
  435. if (structType === STRUCT_TYPES.BOOLEAN_CHOICE) {
  436. ansCont = answer ? "对" : "错";
  437. } else {
  438. ansCont = answer.map((item) => OPTION_NAME[item - 1]).join("");
  439. }
  440. contents.push({
  441. cls: "topic-answer",
  442. type: "json",
  443. content: {
  444. sections: [
  445. {
  446. blocks: [
  447. { ...answerTitleBlock },
  448. { type: "text", value: ansCont },
  449. ],
  450. },
  451. ],
  452. },
  453. });
  454. }
  455. return contents;
  456. },
  457. loadImg(url) {
  458. return new Promise((resolve, reject) => {
  459. const img = new Image();
  460. img.onload = function () {
  461. resolve(true);
  462. };
  463. img.onerror = function () {
  464. reject();
  465. };
  466. img.src = url;
  467. });
  468. },
  469. getRichJsonImgUrls(richJson) {
  470. let urls = [];
  471. if (!richJson) return urls;
  472. richJson.sections.forEach((section) => {
  473. section.blocks.forEach((elem) => {
  474. if (elem.type === "image") {
  475. urls.push(elem.value);
  476. }
  477. });
  478. });
  479. return urls;
  480. },
  481. async waitAllImgLoaded() {
  482. let imgUrls = [];
  483. this.renderStructList.forEach((item) => {
  484. if (item.type === "text") return;
  485. imgUrls.push(...this.getRichJsonImgUrls(item.content));
  486. });
  487. // console.log(imgUrls);
  488. if (!imgUrls.length) return Promise.resolve(true);
  489. const imgLoads = imgUrls.map((item) => this.loadImg(item));
  490. const imgLoadResult = await Promise.all(imgLoads).catch(() => {});
  491. if (imgLoadResult && imgLoadResult.length) {
  492. return Promise.resolve(true);
  493. } else {
  494. return Promise.reject();
  495. }
  496. },
  497. buildRenderGroupStructList() {
  498. let curGroup = [],
  499. curGroupHeight = 0;
  500. let renderGroupStructList = [];
  501. this.renderStructList.forEach((item) => {
  502. const itemHeight = document.getElementById(item.id).clientHeight;
  503. if (curGroupHeight + itemHeight > MAX_HEIGHT) {
  504. if (curGroup.length) renderGroupStructList.push(curGroup);
  505. curGroup = [];
  506. curGroupHeight = 0;
  507. }
  508. curGroup.push(item);
  509. curGroupHeight += itemHeight;
  510. });
  511. if (curGroup.length) renderGroupStructList.push(curGroup);
  512. this.renderGroupStructList = renderGroupStructList;
  513. this.renderStructList = [];
  514. },
  515. getPreviewHtml() {
  516. const html = previewTem(this.$refs.PaperBody.innerHTML);
  517. console.log(html);
  518. },
  519. },
  520. };
  521. </script>
  522. <style src="@/styles/paper-preview.css"></style>