JudgePreview.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. <!-- eslint-disable vue/no-v-html -->
  2. <template>
  3. <div class="judge-preview" :style="[getAreaStyle(), getComponentStyle()]">
  4. <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
  5. <div class="main">
  6. <ul class="option-list">
  7. <li
  8. v-for="({ content, mark, paragraph_list, rich_text_list }, i) in data.option_list"
  9. :key="mark"
  10. :style="{ cursor: disabled ? 'not-allowed' : 'pointer' }"
  11. :class="['option-item', { active: isAnswer(mark) }]"
  12. >
  13. <div
  14. :style="{ borderColor: data.unified_attrib?.topic_color }"
  15. :class="['option-content', computedIsJudgeRight(mark)]"
  16. >
  17. <span
  18. v-if="!('enable_serial' in data.property) || isEnable(data.property.enable_serial)"
  19. class="serial-number"
  20. >
  21. {{ computeOptionNumber(i, data.property.option_serial_type) }}
  22. </span>
  23. <PinyinText
  24. v-if="isEnable(data.property.view_pinyin)"
  25. class="content"
  26. :paragraph-list="paragraph_list"
  27. :rich-text-list="rich_text_list"
  28. :pinyin-position="data.property.pinyin_position"
  29. :body-styles="getBodyStyles()"
  30. :is-preview="true"
  31. />
  32. <div
  33. v-else
  34. class="rich-text"
  35. :style="{ fontSize: type === typeList[0] ? '12pt' : '', ...getBodyStyles() }"
  36. v-html="convertText(sanitizeHTML(content))"
  37. ></div>
  38. </div>
  39. <div class="option-type">
  40. <div
  41. v-for="option_type in incertitudeList"
  42. :key="option_type"
  43. :style="{ cursor: disabled ? 'not-allowed' : 'pointer' }"
  44. :class="[
  45. 'option-type-item',
  46. {
  47. active: isAnswer(mark, option_type),
  48. },
  49. ]"
  50. @click="selectAnswer(mark, option_type)"
  51. >
  52. <SvgIcon
  53. v-if="option_type === option_type_list[0].value"
  54. icon-class="check-mark"
  55. width="17"
  56. height="12"
  57. />
  58. <SvgIcon v-if="option_type === option_type_list[1].value" icon-class="cross" size="12" />
  59. <SvgIcon v-if="option_type === option_type_list[2].value" icon-class="circle" size="16" />
  60. </div>
  61. </div>
  62. </li>
  63. </ul>
  64. <PreviewOperation @showAnswerAnalysis="showAnswerAnalysis" @judgeCorrect="judgeCorrect" @retry="retry" />
  65. </div>
  66. <AnswerCorrect
  67. :answer-correct="data?.answer_correct"
  68. :visible.sync="visibleAnswerCorrect"
  69. :is-check-correct="isCheckCorrect"
  70. @closeAnswerCorrect="closeAnswerCorrect"
  71. />
  72. <AnswerAnalysis
  73. :visible.sync="visibleAnswerAnalysis"
  74. :answer-list="data.answer_list"
  75. :analysis-list="data.analysis_list"
  76. @closeAnswerAnalysis="closeAnswerAnalysis"
  77. >
  78. <ul slot="right-answer" class="option-list">
  79. <li
  80. v-for="({ content, mark, paragraph_list, rich_text_list }, i) in data.option_list"
  81. :key="mark"
  82. :style="{ cursor: disabled ? 'not-allowed' : 'pointer' }"
  83. :class="['option-item', { active: isAnswer(mark) }]"
  84. >
  85. <div
  86. :style="{ borderColor: data.unified_attrib?.topic_color }"
  87. :class="['option-content', computedIsJudgeRight(mark)]"
  88. >
  89. <span
  90. v-if="!('enable_serial' in data.property) || isEnable(data.property.enable_serial)"
  91. class="serial-number"
  92. >
  93. {{ computeOptionNumber(i, data.property.option_serial_type) }}
  94. </span>
  95. <PinyinText
  96. v-if="isEnable(data.property.view_pinyin)"
  97. class="content"
  98. :paragraph-list="paragraph_list"
  99. :rich-text-list="rich_text_list"
  100. :pinyin-position="data.property.pinyin_position"
  101. :body-styles="getBodyStyles()"
  102. :is-preview="true"
  103. />
  104. <div
  105. v-else
  106. class="rich-text"
  107. :style="{ fontSize: type === typeList[0] ? '12pt' : '', ...getBodyStyles() }"
  108. v-html="convertText(sanitizeHTML(content))"
  109. ></div>
  110. </div>
  111. <div class="option-type">
  112. <div
  113. v-for="option_type in incertitudeList"
  114. :key="option_type"
  115. :style="{ cursor: disabled ? 'not-allowed' : 'pointer' }"
  116. :class="['option-type-item', computedIsShowRightAnswer(mark, option_type)]"
  117. @click="selectAnswer(mark, option_type)"
  118. >
  119. <SvgIcon
  120. v-if="option_type === option_type_list[0].value"
  121. icon-class="check-mark"
  122. width="17"
  123. height="12"
  124. />
  125. <SvgIcon v-if="option_type === option_type_list[1].value" icon-class="cross" size="12" />
  126. <SvgIcon v-if="option_type === option_type_list[2].value" icon-class="circle" size="16" />
  127. </div>
  128. </div>
  129. </li>
  130. </ul>
  131. </AnswerAnalysis>
  132. </div>
  133. </template>
  134. <script>
  135. import PreviewMixin from '../common/PreviewMixin';
  136. import { getJudgeData, option_type_list, isEnable } from '@/views/book/courseware/data/judge';
  137. import { computeOptionNumber } from '@/views/book/courseware/data/common';
  138. export default {
  139. name: 'JudgePreview',
  140. mixins: [PreviewMixin],
  141. data() {
  142. return {
  143. data: getJudgeData(),
  144. option_type_list,
  145. isEnable,
  146. computeOptionNumber,
  147. };
  148. },
  149. computed: {
  150. incertitudeList() {
  151. let _option_type_list = this.data.property.option_type_list;
  152. if (isEnable(this.data.property.is_view_incertitude)) {
  153. return _option_type_list;
  154. }
  155. // 返回不包含第三个元素的新数组
  156. return [..._option_type_list.slice(0, 2), ..._option_type_list.slice(3)];
  157. },
  158. },
  159. watch: {
  160. 'answer.answer_list': {
  161. handler(val) {
  162. if (val.length === 0) {
  163. this.answer.is_right = false;
  164. return;
  165. }
  166. if (val.length !== this.data.answer.answer_list.length) {
  167. this.answer.is_right = false;
  168. return;
  169. }
  170. this.answer.is_right = val.every((item) =>
  171. this.data.answer.answer_list.some(
  172. (answerItem) => answerItem.mark === item.mark && answerItem.option_type === item.option_type,
  173. ),
  174. );
  175. },
  176. deep: true,
  177. },
  178. },
  179. methods: {
  180. isAnswer(mark, option_type) {
  181. return this.answer.answer_list.some((li) => li.mark === mark && li.option_type === option_type);
  182. },
  183. // 选择答案
  184. selectAnswer(mark, option_type) {
  185. if (this.disabled) return;
  186. const index = this.answer.answer_list.findIndex((li) => li.mark === mark);
  187. if (index === -1) {
  188. this.answer.answer_list.push({ mark, option_type });
  189. } else {
  190. this.answer.answer_list[index].option_type = option_type;
  191. }
  192. },
  193. // 计算判断题小题题目样式
  194. computedIsJudgeRight(mark) {
  195. if (!this.isJudgingRightWrong) return '';
  196. let selectOption = this.answer.answer_list.find((item) => item.mark === mark); // 查找是否已选中的选项
  197. if (!selectOption) return '';
  198. // 正确选项的类型
  199. const correctOption = this.data.answer.answer_list.find((item) => item.mark === mark)?.option_type;
  200. if (!correctOption) return '';
  201. return correctOption === selectOption.option_type ? 'right' : 'wrong';
  202. },
  203. // 计算是否显示正确答案的样式
  204. computedIsShowRightAnswer(mark, option_type) {
  205. if (!this.isShowRightAnswer) return '';
  206. // 是否是正确的选项类型
  207. let isCorrectType = this.data.answer.answer_list.find((item) => item.mark === mark)?.option_type === option_type;
  208. return isCorrectType ? 'answer-right' : '';
  209. },
  210. retry() {
  211. this.isJudgingRightWrong = false;
  212. this.isShowRightAnswer = false;
  213. this.answer.answer_list = [];
  214. },
  215. /**
  216. * 获取无文本内容的数据结构,用于保存为个人模板时的样式模板
  217. */
  218. getNoTextContentData() {
  219. let noTextContentData = JSON.parse(JSON.stringify(this.data));
  220. const resetFieldMap = {
  221. analysis_list: [],
  222. answer_list: [],
  223. };
  224. Object.assign(noTextContentData, resetFieldMap);
  225. noTextContentData.option_list.forEach((item) => {
  226. item.content = '';
  227. item.paragraph_list = [];
  228. item.paragraph_list_parameter = {
  229. text: '',
  230. pinyin_proofread_word_list: [],
  231. };
  232. });
  233. if (noTextContentData.answer) {
  234. noTextContentData.answer.answer_list = [];
  235. noTextContentData.answer.reference_answer = '';
  236. }
  237. return noTextContentData;
  238. },
  239. },
  240. };
  241. </script>
  242. <style lang="scss" scoped>
  243. @use '@/styles/mixin.scss' as *;
  244. .judge-preview {
  245. @include preview-base;
  246. .option-list {
  247. display: flex;
  248. flex-direction: column;
  249. row-gap: 16px;
  250. .option-item {
  251. display: flex;
  252. column-gap: 16px;
  253. .option-content {
  254. display: flex;
  255. flex: 1;
  256. column-gap: 8px;
  257. align-items: center;
  258. padding: 12px 24px;
  259. background-color: $content-color;
  260. border-style: solid;
  261. border-width: 1px;
  262. border-radius: 4px;
  263. &.right {
  264. background-color: $right-bc-color;
  265. }
  266. &.wrong {
  267. background-color: #ffd4d9;
  268. }
  269. .serial-number {
  270. font-size: 16pt;
  271. color: #000;
  272. }
  273. }
  274. .option-type {
  275. display: flex;
  276. column-gap: 8px;
  277. align-items: center;
  278. &-item {
  279. display: flex;
  280. align-items: center;
  281. justify-content: center;
  282. width: 36px;
  283. height: 36px;
  284. color: #000;
  285. cursor: pointer;
  286. background-color: $content-color;
  287. border-radius: 50%;
  288. &.active {
  289. color: #fff;
  290. background-color: $light-main-color;
  291. }
  292. &.answer-right {
  293. color: $right-color;
  294. border: 1px solid $right-color;
  295. }
  296. }
  297. }
  298. }
  299. }
  300. }
  301. </style>