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