exam-stat.vue 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. <template>
  2. <view class="px-16 py-26 bg-white rounded-15 mt-20">
  3. <view class="text-30 text-fore-title font-bold">答题情况</view>
  4. <view v-if="showStats" class="mt-20 flex items-center">
  5. <view class="flex-1 text-center">
  6. <view class="text-40 text-fore-title font-bold">{{ totalQuestions }}</view>
  7. <view class="mt-5 text-28 text-fore-light">题量</view>
  8. </view>
  9. <view class="flex-1 text-center">
  10. <view class="text-40 text-fore-title font-bold">{{ paperInfo.score }}</view>
  11. <view class="mt-5 text-28 text-fore-light">总分</view>
  12. </view>
  13. <view class="flex-1 text-center">
  14. <view class="text-40 text-fore-title font-bold">{{ stats.score }}</view>
  15. <view class="mt-5 text-28 text-fore-light">得分</view>
  16. </view>
  17. <view class="flex-1 text-center">
  18. <view class="text-40 text-fore-title font-bold">{{ stats.rate }}%</view>
  19. <view class="mt-5 text-28 text-fore-light">正确率</view>
  20. </view>
  21. </view>
  22. <view v-if="showStats" class="h-1 bg-[#E6E6E6] my-20 mb-40 "></view>
  23. <view class="flex items-center justify-end gap-x-30">
  24. <view class="flex items-center gap-x-12" :class="{ 'is-filter': !filterTypes.includes('correct') }"
  25. @click="handleFilter('correct')">
  26. <view class="icon w-18 h-18 rounded-full bg-[#2CC6A0]"></view>
  27. <view class="legend text-20 text-fore-subtitle">答对</view>
  28. </view>
  29. <view class="flex items-center gap-x-12" :class="{ 'is-filter': !filterTypes.includes('incorrect') }"
  30. @click="handleFilter('incorrect')">
  31. <view class="icon w-18 h-18 rounded-full bg-[#FF5B5C]"></view>
  32. <view class="legend text-20 text-fore-subtitle">答错</view>
  33. </view>
  34. <view class="flex items-center gap-x-12" :class="{ 'is-filter': !filterTypes.includes('unanswered') }"
  35. @click="handleFilter('unanswered')">
  36. <view class="icon w-18 h-18 rounded-full is-unanswered"></view>
  37. <view class="legend text-20 text-fore-subtitle">未答</view>
  38. </view>
  39. </view>
  40. <view class="mt-30 grid grid-cols-5 gap-x-10 gap-y-30 place-items-center">
  41. <view class="question-item" v-for="item in questionList" :key="item.id"
  42. :class="[item.isNotAnswer ? 'is-unanswered' : (item.isRight ? 'is-correct' : 'is-incorrect')]"
  43. @click="handleDetail(item)">
  44. <view class="text-36 font-bold">{{ item.originalIndex }}</view>
  45. </view>
  46. </view>
  47. </view>
  48. </template>
  49. <script lang="ts" setup>
  50. import { EnumPaperType, EnumQuestionType } from '@/common/enum';
  51. import { useTransferPage } from '@/hooks/useTransferPage';
  52. import { Study } from '@/types';
  53. const { transferTo } = useTransferPage();
  54. const emit = defineEmits<{
  55. (e: 'detail', item: Study.ExamineeQuestion): void;
  56. }>();
  57. const props = defineProps<{
  58. data: Study.Examinee;
  59. showStats: boolean;
  60. }>();
  61. interface QuestionItem extends Study.ExamineeQuestion {
  62. originalIndex?: number;
  63. isRight?: boolean;
  64. isNotAnswer?: boolean;
  65. }
  66. const paperInfo = computed(() => {
  67. return props.data.paperInfo || {};
  68. });
  69. const stats = computed(() => {
  70. return props.data.stats || {};
  71. });
  72. const totalQuestions = computed(() => {
  73. return (props.data.questions || []).length;
  74. });
  75. const filterTypes = ref<('correct' | 'incorrect' | 'unanswered')[]>(['correct', 'incorrect', 'unanswered']);
  76. const questionList = computed(() => {
  77. const list: QuestionItem[] = [];
  78. let offset = 0 ;
  79. (props.data.questions || []).forEach((item, index) => {
  80. if (item.subQuestions && item.subQuestions.length > 0) {
  81. item.subQuestions.forEach((subItem, subIndex) => {
  82. offset++;
  83. list.push({
  84. ...subItem,
  85. originalIndex: index + subIndex + 1, // 保存原始序号
  86. isRight: judgeCorrect(subItem),
  87. isNotAnswer: !subItem.answers.filter(item => item !== ' ').length
  88. });
  89. });
  90. } else {
  91. list.push({
  92. ...item,
  93. originalIndex: index + offset + 1, // 保存原始序号
  94. isRight: judgeCorrect(item),
  95. isNotAnswer: !item.answers.filter(item => item !== ' ').length
  96. });
  97. }
  98. });
  99. return list.filter((item: QuestionItem) => {
  100. // 判断是否答对
  101. if (item.isRight && filterTypes.value.includes('correct')) {
  102. return true;
  103. }
  104. // 判断是否答错(答了但是错了)
  105. if (!item.isRight && !item.isNotAnswer && filterTypes.value.includes('incorrect')) {
  106. return true;
  107. }
  108. // 判断是否未答
  109. if (item.isNotAnswer && filterTypes.value.includes('unanswered')) {
  110. return true;
  111. }
  112. return false;
  113. });
  114. });
  115. const judgeCorrect = (qs: Study.ExamineeQuestion) => {
  116. if (qs.typeId === EnumQuestionType.SINGLE_CHOICE
  117. || qs.typeId === EnumQuestionType.JUDGMENT) {
  118. return qs.answers.includes(qs.answer1);
  119. } else if (qs.typeId === EnumQuestionType.MULTIPLE_CHOICE) {
  120. const rightAnswers = qs.answer1.split('').filter(item => item !== ' ');
  121. return rightAnswers.length === qs.answers.length && rightAnswers.every(item => qs.answers.includes(item));
  122. }
  123. }
  124. const handleFilter = (type: 'correct' | 'incorrect' | 'unanswered') => {
  125. if (filterTypes.value.includes(type)) {
  126. filterTypes.value = filterTypes.value.filter(item => item !== type);
  127. } else {
  128. filterTypes.value.push(type);
  129. }
  130. console.log(filterTypes.value)
  131. }
  132. const handleDetail = (item: Study.ExamineeQuestion) => {
  133. emit('detail', item);
  134. }
  135. </script>
  136. <style lang="scss" scoped>
  137. .question-item {
  138. @apply w-80 h-80 rounded-full overflow-hidden flex items-center justify-center text-20 text-fore-subtitle;
  139. }
  140. .is-unanswered {
  141. background: #EEF4FA;
  142. color: #B3B3B3;
  143. }
  144. .is-correct {
  145. background: #E7FCF8;
  146. color: #2CC6A0;
  147. }
  148. .is-incorrect {
  149. background: #FEEDE9;
  150. color: #FF5B5C;
  151. }
  152. .is-filter {
  153. .icon {
  154. background: #bbbbbb;
  155. }
  156. .legend {
  157. color: #bbbbbb;
  158. }
  159. }
  160. </style>