JudgePreview.vue 10 KB

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