JudgePreview.vue 9.1 KB

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