CardBuildDialog.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. <template>
  2. <el-dialog
  3. class="card-build-dialog opacity-dialog"
  4. :visible.sync="modalIsShow"
  5. :close-on-click-modal="false"
  6. :close-on-press-escape="false"
  7. :show-close="false"
  8. append-to-body
  9. fullscreen
  10. @opened="initData"
  11. >
  12. <div slot="title"></div>
  13. <div slot="footer"></div>
  14. <div
  15. class="loading-tips"
  16. v-loading="loading"
  17. element-loading-text="生成题卡中"
  18. ></div>
  19. <div
  20. v-if="modalIsShow"
  21. id="card-build"
  22. class="card-build card-preview card-print"
  23. >
  24. <card-view
  25. v-if="pages.length"
  26. ref="CardView"
  27. class="preview-body"
  28. :pages="pages"
  29. :card-config="cardConfig"
  30. ></card-view>
  31. <!-- all topics -->
  32. <div v-else class="topic-list">
  33. <div :class="['page-box', `page-box-${cardConfig.pageSize}`]">
  34. <div class="page-main-inner">
  35. <div
  36. :class="['page-main', `page-main-${cardConfig.columnNumber}`]"
  37. :style="{ margin: `0 -${cardConfig.columnGap / 2}px` }"
  38. >
  39. <div
  40. class="page-column"
  41. :style="{ padding: `0 ${cardConfig.columnGap / 2}px` }"
  42. >
  43. <div class="page-column-main" id="topic-column">
  44. <div class="page-column-body">
  45. <!-- card-head-sample -->
  46. <card-head-sample
  47. :data="cardHeadSampleData"
  48. id="simple-card-head"
  49. v-if="topics.length && cardHeadSampleData"
  50. ></card-head-sample>
  51. <!-- topic element -->
  52. <topic-element-preview
  53. class="page-column-element"
  54. v-for="element in topics"
  55. :key="element.id"
  56. :data="element"
  57. ></topic-element-preview>
  58. </div>
  59. </div>
  60. </div>
  61. </div>
  62. </div>
  63. </div>
  64. </div>
  65. </div>
  66. </el-dialog>
  67. </template>
  68. <script>
  69. import json5 from "json5";
  70. import { mapState, mapMutations, mapActions } from "vuex";
  71. import { saveCard, cardConfigInfos } from "../api";
  72. import { tikuPaperDetail } from "../../exam/api";
  73. import { getPaperJsonSimpleStructInfo } from "../autoBuild/paperStruct";
  74. import { buildCardFromPaperSimpleStruct } from "../autoBuild/simplePaperCard";
  75. // card components
  76. import { getCardHeadModel } from "../../../../card/elementModel";
  77. import TopicElementPreview from "../../../../card/components/TopicElementPreview";
  78. import CardView from "../../../../card/components/CardView.vue";
  79. import CardHeadSample from "../../../../card/elements/card-head/CardHead";
  80. import { deepCopy, objTypeOf } from "@/plugins/utils";
  81. // ceshi
  82. // import paperData from "./paper.json";
  83. export default {
  84. name: "CardBuild",
  85. components: { CardView, TopicElementPreview, CardHeadSample },
  86. props: {
  87. presetData: {
  88. type: Object,
  89. default() {
  90. return {
  91. paperId: "",
  92. };
  93. },
  94. },
  95. },
  96. data() {
  97. return {
  98. cardId: "",
  99. loading: false,
  100. modalIsShow: false,
  101. paperInfo: {},
  102. answers: {},
  103. cachePages: [],
  104. };
  105. },
  106. computed: {
  107. ...mapState("card", ["cardConfig", "topics", "pages"]),
  108. cardHeadSampleData() {
  109. if (!this.cardConfig["pageSize"]) return;
  110. const data = getCardHeadModel(this.cardConfig);
  111. data.isSimple = true;
  112. return data;
  113. },
  114. },
  115. methods: {
  116. ...mapMutations("card", [
  117. "setCardConfig",
  118. "setTopics",
  119. "setPages",
  120. "initState",
  121. ]),
  122. ...mapActions("card", ["resetTopicSeries", "rebuildPages"]),
  123. async initData() {
  124. this.loading = true;
  125. this.initState();
  126. // 题卡规则
  127. const cardConfig = await this.getCardConfig(
  128. this.presetData.cardRuleId
  129. ).catch(() => {});
  130. if (!cardConfig) {
  131. this.emitResult({ success: false, message: "题卡规则获取失败" });
  132. return;
  133. }
  134. this.setCardConfig(cardConfig);
  135. // 试卷信息
  136. const res = await tikuPaperDetail({
  137. examId: this.presetData.examId,
  138. paperId: this.presetData.paperId,
  139. uuid: this.presetData.uuid,
  140. }).catch(() => {});
  141. if (!res) {
  142. this.emitResult({ success: false, message: "试卷内容获取失败" });
  143. return;
  144. }
  145. // const res = paperData;
  146. // 构建题卡
  147. try {
  148. this.paperInfo = {
  149. uuid: this.presetData.uuid,
  150. attachmentId: res.attachmentId,
  151. };
  152. const answerJson = res.answerJson
  153. ? json5.parse(res.answerJson)
  154. : { details: [] };
  155. const paperJson = res.paperJson
  156. ? json5.parse(res.paperJson)
  157. : { details: [] };
  158. this.rebuildPaperQuestionNumber(answerJson);
  159. this.rebuildPaperQuestionNumber(paperJson);
  160. this.parsePaperAnswers(paperJson, answerJson);
  161. const paperSimpleStruct = getPaperJsonSimpleStructInfo(paperJson);
  162. const elementTypePreSetInfo = {
  163. FILL_QUESTION: {
  164. pageSize: "A3",
  165. columnNumber: 2,
  166. },
  167. };
  168. const elements = buildCardFromPaperSimpleStruct(
  169. paperSimpleStruct,
  170. elementTypePreSetInfo
  171. );
  172. this.setTopics([getCardHeadModel(this.cardConfig), ...elements]);
  173. this.resetTopicSeries();
  174. } catch (error) {
  175. console.dir(error);
  176. this.emitResult({
  177. success: false,
  178. message: error.message || "数据构建错误",
  179. });
  180. return;
  181. }
  182. this.$nextTick(() => {
  183. this.rebuildPages();
  184. this.cachePages = deepCopy(this.pages);
  185. console.log(this.cachePages);
  186. this.updatePageField();
  187. this.$nextTick(() => {
  188. this.buildData();
  189. });
  190. });
  191. },
  192. rebuildPaperQuestionNumber(paperJson) {
  193. // 如果一个大题有多个套题,那么套题的小题需要将强制转成为小题号连续
  194. paperJson.details.forEach((detail) => {
  195. let qno = 1;
  196. detail.questions.forEach((question) => {
  197. if (question.subQuestions && question.subQuestions.length) {
  198. question.subQuestions.forEach((subq) => {
  199. subq.number = qno++;
  200. });
  201. } else {
  202. question.number = qno++;
  203. }
  204. });
  205. });
  206. },
  207. getObjectiveQuestionAnswer(answer) {
  208. if (objTypeOf(answer) === "boolean") {
  209. return answer ? "A" : "B";
  210. }
  211. if (objTypeOf(answer) === "array") {
  212. const abc = "abcdefghijklmnopqrstuvwxyz".toUpperCase();
  213. return answer.map((item) => abc[item - 1]).join();
  214. }
  215. },
  216. parsePaperAnswers(paperJson, answerJson) {
  217. const infos = {};
  218. const objectiveQuestionTypes = [1, 2, 3];
  219. paperJson.details.forEach((detail) => {
  220. detail.questions.forEach((question) => {
  221. if (question.subQuestions && question.subQuestions.length) {
  222. question.subQuestions.forEach((subq) => {
  223. const qno = `${detail.number}-${subq.number}`;
  224. if (!infos[qno]) {
  225. infos[qno] = {
  226. answer: null,
  227. score: null,
  228. objective: false,
  229. };
  230. }
  231. infos[qno].score = subq.score;
  232. infos[qno].objective = objectiveQuestionTypes.includes(
  233. subq.structType
  234. );
  235. });
  236. } else {
  237. const qno = `${detail.number}-${question.number}`;
  238. if (!infos[qno]) {
  239. infos[qno] = { answer: null, score: null, objective: false };
  240. }
  241. infos[qno].score = question.score;
  242. infos[qno].objective = objectiveQuestionTypes.includes(
  243. question.structType
  244. );
  245. }
  246. });
  247. });
  248. answerJson.details.forEach((detail) => {
  249. detail.questions.forEach((question) => {
  250. if (question.subQuestions && question.subQuestions.length) {
  251. question.subQuestions.forEach((subq) => {
  252. const qno = `${detail.number}-${subq.number}`;
  253. if (infos[qno].objective) {
  254. infos[qno].answer = this.getObjectiveQuestionAnswer(
  255. subq.answer
  256. );
  257. }
  258. });
  259. } else {
  260. const qno = `${detail.number}-${question.number}`;
  261. if (infos[qno].objective) {
  262. infos[qno].answer = this.getObjectiveQuestionAnswer(
  263. question.answer
  264. );
  265. }
  266. }
  267. });
  268. });
  269. this.answers = infos;
  270. },
  271. async getCardConfig(cardRuleId) {
  272. const data = await cardConfigInfos(cardRuleId);
  273. if (!data) {
  274. this.$message.error("找不到题卡规则!");
  275. return Promise.reject();
  276. }
  277. let config = {
  278. ...data,
  279. ...{
  280. pageSize: "A3",
  281. columnNumber: 2,
  282. columnGap: 20,
  283. showForbidArea: false,
  284. makeMethod: "SELF",
  285. },
  286. };
  287. config.fillNumber = data.examNumberDigit;
  288. config.aOrB = false;
  289. config.requiredFields = JSON.parse(config.requiredFields);
  290. config.extendFields = JSON.parse(config.extendFields);
  291. config.cardTitle = this.getCardTitle(config.titleRule);
  292. return config;
  293. },
  294. getCardTitle(titleRule) {
  295. const fieldMap = {
  296. courseCode: this.presetData.courseCode,
  297. courseName: this.presetData.courseName,
  298. schoolName: this.presetData.schoolName,
  299. };
  300. Object.entries(fieldMap).forEach(([key, val]) => {
  301. titleRule = titleRule.replace("${" + key + "}", val);
  302. });
  303. return titleRule;
  304. },
  305. updatePageField() {
  306. const config = this.cardConfig;
  307. // 变量
  308. let fieldInfos = {};
  309. [...config.requiredFields, ...config.extendFields]
  310. .filter((item) => item.enable)
  311. .map((item) => {
  312. fieldInfos[item.code] = "${" + item.code + "}";
  313. });
  314. const isPrintExamNumber = config.examNumberStyle === "PRINT";
  315. if (isPrintExamNumber) {
  316. fieldInfos.examNumber = "data:image/png;base64,${examNumber}";
  317. fieldInfos.examNumberStr = "${examNumberStr}";
  318. }
  319. if (config.aOrB && config.paperType === "PRINT") {
  320. fieldInfos.paperType = "data:image/png;base64,${paperType}";
  321. fieldInfos.paperTypeName = "${paperTypeName}";
  322. }
  323. const pages = this.pages.map((page, pageNo) => {
  324. if (pageNo % 2) return page;
  325. const cardHeadElement = page.columns[0].elements[0];
  326. if (cardHeadElement.type === "CARD_HEAD") {
  327. cardHeadElement.fieldInfos = fieldInfos;
  328. }
  329. return page;
  330. });
  331. this.setPages(pages);
  332. },
  333. async buildData() {
  334. const model = this.$refs.CardView.getPageModel({
  335. cardConfig: this.cardConfig,
  336. pages: this.cachePages,
  337. answers: this.answers,
  338. });
  339. const htmlContent = this.$refs.CardView.getPreviewTemp(
  340. document.getElementById("card-build").outerHTML
  341. );
  342. const datas = {
  343. content: model,
  344. htmlContent,
  345. title: this.presetData.paperName,
  346. status: "SUBMIT",
  347. ...this.presetData,
  348. };
  349. const result = await saveCard(datas).catch((error) => {
  350. this.emitResult({
  351. success: false,
  352. message: error.message || "保存题卡错误",
  353. });
  354. });
  355. if (!result) return;
  356. this.emitResult({
  357. success: true,
  358. data: { ...result, ...this.paperInfo },
  359. });
  360. },
  361. emitResult(data) {
  362. // console.log(data);
  363. this.initState();
  364. this.loading = false;
  365. this.$emit("confirm", data);
  366. this.cancel();
  367. },
  368. cancel() {
  369. this.modalIsShow = false;
  370. },
  371. open() {
  372. this.modalIsShow = true;
  373. },
  374. },
  375. };
  376. </script>