question-options.vue 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. <template>
  2. <view class="question-options">
  3. <view class="question-option" v-for="option in question.options" :class="getStyleClass(option)" :key="option.id"
  4. @click="handleSelect(option)">
  5. <template v-if="!isReadOnly">
  6. <view v-if="!isOnlySubjective" class="question-option-index">{{ option.no }}</view>
  7. <view v-else>
  8. <uv-icon name="info-circle" :color="option.isSelected ? '#31A0FC' : '#999'" size="18" />
  9. </view>
  10. </template>
  11. <view v-else>
  12. <uv-icon v-if="option.isCorrect" name="checkmark-circle-fill" color="#2CC6A0" size="22" />
  13. <uv-icon v-else-if="!option.isCorrect && option.isSelected" name="close-circle-fill" color="#FF5B5C"
  14. size="22" />
  15. <view v-else class="question-option-index">{{ option.no }}</view>
  16. </view>
  17. <view class="question-option-content">
  18. <uv-parse :content="getOptionContent(option)" containerStyle="display:inline"
  19. contentStyle="word-break:break-word;"></uv-parse>
  20. </view>
  21. </view>
  22. <!-- 添加不会选项 -->
  23. <view v-if="question.options.length && !isReadOnly && !isOnlySubjective" class="question-option"
  24. :class="{ 'question-option-not-know': question.isNotKnow }" @click="handleNotKnow">
  25. <view class="question-option-index">
  26. <uv-icon name="info-circle" :color="question.isNotKnow ? '#31A0FC' : '#999'" size="18" />
  27. </view>
  28. <view class="question-option-content text-fore-light">不会</view>
  29. </view>
  30. <!-- 用于多选题手动提交,背题模式才有 -->
  31. <view class="mt-40" v-if="practiceSettings.reviewMode === EnumReviewMode.DURING_ANSWER && isMultipleChoice">
  32. <ie-button type="primary" size="mini" :round="4" :shadow="false" custom-class="w-160"
  33. @click="handleSubmit">提交</ie-button>
  34. </view>
  35. <view v-if="!isReadOnly && isOnlySubjective" class="mt-40 bg-[#EBF9FF] p-12 rounded-8">
  36. <view class="rounded-8 bg-white px-10 py-20 text-primary text-24 flex gap-x-6 items-center">
  37. <uv-icon name="info-circle" color="#31A0FC" size="16" />
  38. <text>请线下答题,查看解析对比后,选“会”或“不会”</text>
  39. </view>
  40. <view class="mt-30 mb-20 text-24 text-white bg-primary w-fit mx-auto px-20 py-12 rounded-full text-center"
  41. @click="handleShowParse">
  42. 查看解析
  43. </view>
  44. </view>
  45. </view>
  46. </template>
  47. <script lang="ts" setup>
  48. import { EnumQuestionType, EnumReviewMode } from '@/common/enum';
  49. import { useExam } from '@/composables/useExam';
  50. import { Study, Transfer } from '@/types';
  51. import { EXAM_DATA, EXAM_PAGE_OPTIONS, EXAM_AUTO_SUBMIT } from '@/types/injectionSymbols';
  52. const examPageOptions = inject(EXAM_PAGE_OPTIONS) || {} as Transfer.ExamAnalysisPageOptions;
  53. const examData = inject(EXAM_DATA) || {} as ReturnType<typeof useExam>;
  54. const { practiceSettings, nextQuestion, isAllDone } = examData;
  55. const examAutoSubmit = inject(EXAM_AUTO_SUBMIT);
  56. const props = defineProps<{
  57. question: Study.Question;
  58. }>();
  59. const isReadOnly = computed(() => {
  60. const { readonly } = examPageOptions;
  61. if (readonly) {
  62. return true;
  63. }
  64. // 练习模式下,需要背题模式且题目已经做过且解析过
  65. if (practiceSettings.value.reviewMode === EnumReviewMode.DURING_ANSWER && props.question.hasParsed) {
  66. return true;
  67. }
  68. return false;
  69. });
  70. const isOnlySubjective = computed(() => {
  71. return [EnumQuestionType.SUBJECTIVE, EnumQuestionType.SHORT_ANSWER, EnumQuestionType.ESSAY].includes(props.question.typeId);
  72. });
  73. const isMultipleChoice = computed(() => {
  74. return props.question.typeId === EnumQuestionType.MULTIPLE_CHOICE;
  75. });
  76. const getStyleClass = (option: Study.QuestionOption) => {
  77. if (!isReadOnly.value) {
  78. return option.isSelected ? 'question-option-selected' : '';
  79. }
  80. let customClass = '';
  81. let { answers, answer1 } = props.question;
  82. answers = answers?.filter(item => item !== ' ') || [];
  83. answer1 = answer1 || ''
  84. if ([EnumQuestionType.SINGLE_CHOICE, EnumQuestionType.JUDGMENT].includes(props.question.typeId)) {
  85. if (option.isCorrect) {
  86. customClass = 'question-option-correct';
  87. } else if (option.isIncorrect) {
  88. customClass = 'question-option-incorrect';
  89. }
  90. } else if ([EnumQuestionType.MULTIPLE_CHOICE].includes(props.question.typeId)) {
  91. // 我选择的答案
  92. if (option.isSelected) {
  93. if (option.isCorrect) {
  94. customClass = 'question-option-correct';
  95. } else {
  96. customClass = 'question-option-incorrect';
  97. }
  98. } else {
  99. // 漏选的答案
  100. if (option.isMissed) {
  101. customClass = 'question-option-miss';
  102. }
  103. }
  104. }
  105. // console.log(props.question, option)
  106. return customClass;
  107. };
  108. const getOptionContent = (option: Study.QuestionOption) => {
  109. // sb 问题,浪费几个小时
  110. return option.name.replace(/\s/g, ' ');
  111. }
  112. // 多选题要手动提交才能认为是作答结束
  113. const handleSubmit = () => {
  114. props.question.hasParsed = true;
  115. }
  116. const handleNotKnow = () => {
  117. props.question.answers = [];
  118. props.question.isNotKnow = !props.question.isNotKnow;
  119. props.question.hasParsed = true;
  120. handleNext();
  121. }
  122. const handleNext = () => {
  123. // 如果是正常的练习,默认下一题,如果是背题模式,需要根据是否自动下一题来决定
  124. if (practiceSettings.value.reviewMode === EnumReviewMode.DURING_ANSWER) {
  125. if (practiceSettings.value.autoNext) {
  126. nextQuestion();
  127. }
  128. } else {
  129. nextQuestion();
  130. }
  131. if (isAllDone.value) {
  132. examAutoSubmit?.();
  133. }
  134. }
  135. const handleSelect = (option: Study.QuestionOption) => {
  136. if (isReadOnly.value) {
  137. return;
  138. }
  139. if ([
  140. EnumQuestionType.JUDGMENT,
  141. EnumQuestionType.SINGLE_CHOICE,
  142. EnumQuestionType.SUBJECTIVE,
  143. EnumQuestionType.SHORT_ANSWER,
  144. EnumQuestionType.ESSAY,
  145. EnumQuestionType.ANALYSIS
  146. ].includes(props.question.typeId)) {
  147. props.question.answers = [option.no];
  148. } else if (props.question.typeId === EnumQuestionType.MULTIPLE_CHOICE) {
  149. if (props.question.answers.includes(option.no)) {
  150. props.question.answers = props.question.answers.filter(item => item !== option.no);
  151. } else {
  152. props.question.answers.push(option.no);
  153. }
  154. }
  155. props.question.isNotKnow = false;
  156. props.question.hasParsed = true;
  157. // 多选题不自动切换下一题
  158. if (props.question.typeId !== EnumQuestionType.MULTIPLE_CHOICE) {
  159. handleNext();
  160. }
  161. }
  162. const handleShowParse = () => {
  163. props.question.showParse = !props.question.showParse;
  164. }
  165. </script>
  166. <style lang="scss" scoped>
  167. .question-options {
  168. @apply mt-40;
  169. }
  170. .question-option {
  171. @apply flex items-center px-30 py-24 bg-back rounded-8 border border-none border-transparent;
  172. .question-option-index {
  173. @apply w-40 h-40 rounded-full bg-transparent text-30 text-fore-light font-bold flex items-center justify-center flex-shrink-0;
  174. }
  175. .question-option-content {
  176. @apply text-28 text-fore-title ml-20 flex-1 min-w-0;
  177. }
  178. }
  179. .question-option-selected {
  180. @apply bg-[#b5eaff8e];
  181. .question-option-index {
  182. @apply bg-primary text-white;
  183. }
  184. .question-option-content {
  185. @apply text-primary;
  186. }
  187. }
  188. .question-option-not-know {
  189. @apply bg-[#b5eaff8e];
  190. .question-option-content {
  191. @apply text-primary;
  192. }
  193. }
  194. .question-option-correct {
  195. @apply bg-[#E7FCF8] border-[#E7FCF8] text-[#2CC6A0];
  196. .question-option-index {
  197. @apply text-[#2CC6A0];
  198. }
  199. .question-option-content {
  200. @apply text-[#2CC6A0];
  201. }
  202. }
  203. .question-option-miss {
  204. @apply relative overflow-hidden;
  205. &::before {
  206. content: '';
  207. position: absolute;
  208. right: -56rpx;
  209. top: 15rpx;
  210. width: 180rpx;
  211. height: 36rpx;
  212. background: rgba(255, 91, 92, 0.2);
  213. transform: rotate(30deg);
  214. box-shadow: 0 2rpx 4rpx rgba(255, 91, 92, 0.1);
  215. }
  216. &::after {
  217. content: '漏选';
  218. position: absolute;
  219. right: -8rpx;
  220. top: 14rpx;
  221. width: 100rpx;
  222. height: 32rpx;
  223. color: #FF5B5C;
  224. font-size: 20rpx;
  225. // font-weight: bold;
  226. transform: rotate(30deg);
  227. display: flex;
  228. align-items: center;
  229. justify-content: center;
  230. line-height: 1;
  231. }
  232. }
  233. .question-option-incorrect {
  234. @apply bg-[#FEEDE9] border-[#FEEDE9] text-[#FF5B5C];
  235. .question-option-index {
  236. @apply text-[#FF5B5C];
  237. }
  238. .question-option-content {
  239. @apply text-[#FF5B5C];
  240. }
  241. }
  242. .question-option+.question-option {
  243. @apply mt-24;
  244. }
  245. </style>