exam-stat.vue 5.6 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">{{ rightRate }}%</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 filteredQuestionList" :key="item.id"
  42. :class="[item.isNotAnswer ? 'is-unanswered' : (item.isCorrect ? 'is-correct' : 'is-incorrect')]"
  43. @click="handleDetail(item)">
  44. <view class="text-36 font-bold">{{ item.virtualIndex + 1 }}</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 { useExam } from '@/composables/useExam';
  53. import { Study } from '@/types';
  54. const { transferTo } = useTransferPage();
  55. const { setQuestionList, flatQuestionList } = useExam();
  56. const emit = defineEmits<{
  57. (e: 'detail', item: Study.Question): void;
  58. }>();
  59. const props = defineProps<{
  60. data: Study.Examinee;
  61. showStats: boolean;
  62. }>();
  63. interface QuestionItem extends Study.Question {
  64. originalIndex?: number;
  65. isRight?: boolean;
  66. isNotAnswer?: boolean;
  67. }
  68. const paperInfo = computed(() => {
  69. return props.data.paperInfo || {};
  70. });
  71. const stats = computed(() => {
  72. return props.data.stats || {};
  73. });
  74. const totalQuestions = computed(() => {
  75. return (flatQuestionList.value || []).length;
  76. });
  77. const rightRate = computed(() => {
  78. const { totalCount = 0, wrongCount = 0 } = props.data || {};
  79. return Math.max(Math.round((totalCount - wrongCount) / totalCount * 100), 1);
  80. });
  81. const filterTypes = ref<('correct' | 'incorrect' | 'unanswered')[]>(['correct', 'incorrect', 'unanswered']);
  82. const filteredQuestionList = computed(() => {
  83. const list: QuestionItem[] = [];
  84. let offset = 0;
  85. (flatQuestionList.value || []).forEach((item, index) => {
  86. if (item.subQuestions && item.subQuestions.length > 0) {
  87. item.subQuestions.forEach((subItem, subIndex) => {
  88. offset++;
  89. list.push({
  90. ...subItem,
  91. originalIndex: index + subIndex + 1, // 保存原始序号
  92. // isRight: isQuestionCorrect(subItem),
  93. // isNotAnswer: !subItem.answers.filter(item => item !== ' ').length
  94. });
  95. });
  96. } else {
  97. list.push({
  98. ...item,
  99. originalIndex: index + offset + 1, // 保存原始序号
  100. // isRight: isQuestionCorrect(item),
  101. // isNotAnswer: !item.answers.filter(item => item !== ' ').length
  102. });
  103. }
  104. });
  105. return list.filter((item: QuestionItem) => {
  106. // 判断是否答对
  107. if (item.isCorrect && filterTypes.value.includes('correct')) {
  108. return true;
  109. }
  110. // 判断是否答错(答了但是错了)
  111. if (!item.isCorrect && !item.isNotAnswer && filterTypes.value.includes('incorrect')) {
  112. return true;
  113. }
  114. // 判断是否未答
  115. if (item.isNotAnswer && filterTypes.value.includes('unanswered')) {
  116. return true;
  117. }
  118. return false;
  119. });
  120. });
  121. const handleFilter = (type: 'correct' | 'incorrect' | 'unanswered') => {
  122. if (filterTypes.value.includes(type)) {
  123. filterTypes.value = filterTypes.value.filter(item => item !== type);
  124. } else {
  125. filterTypes.value.push(type);
  126. }
  127. }
  128. const handleDetail = (item: Study.Question) => {
  129. emit('detail', item);
  130. }
  131. setQuestionList(props.data.questions);
  132. console.log(flatQuestionList.value, 111)
  133. </script>
  134. <style lang="scss" scoped>
  135. .question-item {
  136. @apply w-80 h-80 rounded-full overflow-hidden flex items-center justify-center text-20 text-fore-subtitle;
  137. }
  138. .is-unanswered {
  139. background: #EEF4FA;
  140. color: #B3B3B3;
  141. }
  142. .is-correct {
  143. background: #E7FCF8;
  144. color: #2CC6A0;
  145. }
  146. .is-incorrect {
  147. background: #FEEDE9;
  148. color: #FF5B5C;
  149. }
  150. .is-filter {
  151. .icon {
  152. background: #bbbbbb;
  153. }
  154. .legend {
  155. color: #bbbbbb;
  156. }
  157. }
  158. </style>