Browse Source

完成题目解析

shmily1213 3 weeks ago
parent
commit
86bb0d8bef

+ 1 - 1
src/api/modules/study.ts

@@ -1,6 +1,6 @@
 import { ApiResponse, ApiResponseList } from "@/types";
 import { ApiResponse, ApiResponseList } from "@/types";
 import flyio from "../flyio";
 import flyio from "../flyio";
-import { ApiQuestion, DirectedSchool, Examinee, ExamPaper, ExamPaperSubmit, GetExamPaperRequestDTO, Knowledge, KnowledgeListRequestDTO, KnowledgeRecord, OpenExamineeRequestDTO, PracticeRecord, SimulatedRecord, SimulationExamSubject, SimulationTestInfo, StudyPlan, Subject, SubjectListRequestDTO, VideoStudyRecord } from "@/types/study";
+import { DirectedSchool, Examinee, ExamPaper, ExamPaperSubmit, GetExamPaperRequestDTO, Knowledge, KnowledgeListRequestDTO, KnowledgeRecord, OpenExamineeRequestDTO, PracticeRecord, SimulatedRecord, SimulationExamSubject, SimulationTestInfo, StudyPlan, Subject, SubjectListRequestDTO, VideoStudyRecord } from "@/types/study";
 
 
 /**
 /**
  * 获取学习计划
  * 获取学习计划

+ 13 - 7
src/composables/useExam.ts

@@ -122,6 +122,15 @@ export const useExam = () => {
     }
     }
     return qs.answers && qs.answers.filter(item => !!item).length > 0 || !!qs.isNotKnow;
     return qs.answers && qs.answers.filter(item => !!item).length > 0 || !!qs.isNotKnow;
   }
   }
+  const isCorrect = (qs: Study.ExamineeQuestion): boolean => {
+    const { answers, answer1, answer2, typeId } = qs;
+    if ([EnumQuestionType.SINGLE_CHOICE, EnumQuestionType.JUDGMENT].includes(typeId)) {
+      return answer1.includes(answers[0]);
+    } else if ([EnumQuestionType.MULTIPLE_CHOICE].includes(typeId)) {
+      return answers.length === answer1.length && answers.every(item => answer1.includes(item));
+    }
+    return false;
+  };
   const nextQuestion = () => {
   const nextQuestion = () => {
     if (currentIndex.value >= questionList.value.length - 1) {
     if (currentIndex.value >= questionList.value.length - 1) {
       return;
       return;
@@ -208,16 +217,15 @@ export const useExam = () => {
     examDuration.value = duration;
     examDuration.value = duration;
     practiceDuration.value = duration;
     practiceDuration.value = duration;
   }
   }
-  const setQuestionList = (list: Study.ApiQuestion[]) => {
+  const setQuestionList = (list: Study.ExamineeQuestion[]) => {
     const orders = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
     const orders = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
     // 数据预处理
     // 数据预处理
     // 1、给每个项目补充额外字段
     // 1、给每个项目补充额外字段
-    const parseQuestion = (item: Study.ApiQuestion): Study.Question => {
+    const parseQuestion = (item: Study.ExamineeQuestion): Study.Question => {
       return {
       return {
-        title: item.title,
+        ...item,
         // 处理没有题型的大题,统一作为阅读题
         // 处理没有题型的大题,统一作为阅读题
         typeId: (item.typeId === null || item.typeId === undefined) ? EnumQuestionType.OTHER : item.typeId,
         typeId: (item.typeId === null || item.typeId === undefined) ? EnumQuestionType.OTHER : item.typeId,
-        id: item.id,
         answers: item.answers || [],
         answers: item.answers || [],
         subQuestions: item.subQuestions?.map(parseQuestion) || [],
         subQuestions: item.subQuestions?.map(parseQuestion) || [],
         options: item.options?.map((option, index) => {
         options: item.options?.map((option, index) => {
@@ -229,9 +237,7 @@ export const useExam = () => {
           } as Study.QuestionOption
           } as Study.QuestionOption
         }) || [],
         }) || [],
         isDone: false,
         isDone: false,
-        isMark: item.isMark,
-        isNotKnow: item.isNotKnow,
-        isFavorite: item.isFavorite
+        isCorrect: isCorrect(item)
       } as Study.Question
       } as Study.Question
     }
     }
     const arr: Study.Question[] = list.map(item => {
     const arr: Study.Question[] = list.map(item => {

+ 1 - 1
src/pages.json

@@ -578,7 +578,7 @@
           }
           }
         },
         },
         {
         {
-          "path": "pages/start-exam/start-exam",
+          "path": "pages/exam-start/exam-start",
           "style": {
           "style": {
             "navigationBarTitleText": ""
             "navigationBarTitleText": ""
           }
           }

+ 3 - 3
src/pagesStudy/components/exam-record-item.vue

@@ -18,7 +18,7 @@
 <script lang="ts" setup>
 <script lang="ts" setup>
 import { Study } from '@/types';
 import { Study } from '@/types';
 import { useTransferPage } from '@/hooks/useTransferPage';
 import { useTransferPage } from '@/hooks/useTransferPage';
-import { EnumSimulatedRecordStatus } from '@/common/enum';
+import { EnumPaperType, EnumSimulatedRecordStatus } from '@/common/enum';
 import { beginExaminee } from '@/api/modules/study';
 import { beginExaminee } from '@/api/modules/study';
 const { transferTo } = useTransferPage();
 const { transferTo } = useTransferPage();
 const props = defineProps<{
 const props = defineProps<{
@@ -35,10 +35,10 @@ const handleDetail = () => {
       }
       }
     });
     });
   } else {
   } else {
-    transferTo('/pagesStudy/pages/start-exam/start-exam', {
+    transferTo('/pagesStudy/pages/exam-start/exam-start', {
       data: {
       data: {
         name: '模拟考试-' + props.data.subjectName,
         name: '模拟考试-' + props.data.subjectName,
-        paperType: 'Simulated',
+        paperType: EnumPaperType.SIMULATED,
         simulationInfo: {
         simulationInfo: {
           examineeId: props.data.id,
           examineeId: props.data.id,
         }
         }

+ 1 - 16
src/pagesStudy/components/practice-table.vue

@@ -15,7 +15,7 @@
             title="选择月份" :fontSize="26" icon="arrow-down" key-label="label" key-value="value"
             title="选择月份" :fontSize="26" icon="arrow-down" key-label="label" key-value="value"
             @change="handleMonthChange">
             @change="handleMonthChange">
             <template #default="{ label }">
             <template #default="{ label }">
-              <text>{{ label }}</text>
+              <text>{{ label }}</text>
             </template>
             </template>
           </ie-picker>
           </ie-picker>
         </view>
         </view>
@@ -149,21 +149,6 @@ const form = ref({
   year: '',
   year: '',
   month: '',
   month: '',
 })
 })
-
-// 年份列表(当前年份前后各2年,但不能超过当前年份)
-// const yearList = computed(() => {
-//   return [
-//     {
-//       label: 2024,
-//       value: 2024
-//     },
-//     {
-//       label: 2025,
-//       value: 2025
-//     }
-//   ];
-// });
-
 // 月份列表(不能超过当前月份)
 // 月份列表(不能超过当前月份)
 const monthList = computed(() => {
 const monthList = computed(() => {
   const today = new Date();
   const today = new Date();

+ 0 - 0
src/pagesStudy/pages/start-exam/components/question-correct-popup.vue → src/pagesStudy/pages/exam-start/components/question-correct-popup.vue


+ 152 - 11
src/pagesStudy/pages/start-exam/components/question-item.vue → src/pagesStudy/pages/exam-start/components/question-item.vue

@@ -8,14 +8,19 @@
         <uv-parse :content="getQuestionTitle()"></uv-parse>
         <uv-parse :content="getQuestionTitle()"></uv-parse>
       </view>
       </view>
       <view class="question-options">
       <view class="question-options">
-        <view class="question-option" v-for="option in question.options"
-          :class="{ 'question-option-selected': isSelected(option) }" :key="option.id" @click="handleSelect(option)">
-          <view class="question-option-index">{{ option.no }}</view>
+        <view class="question-option" v-for="option in question.options" :class="getStyleClass(option)" :key="option.id"
+          @click="handleSelect(option)">
+          <view v-if="!readonly" class="question-option-index">{{ option.no }}</view>
+          <view v-else>
+            <uv-icon v-if="isOptionCorrect(option)" name="checkmark-circle-fill" color="#2CC6A0" size="22" />
+            <uv-icon v-else-if="isOptionIncorrect(option)" name="close-circle-fill" color="#FF5B5C" size="22" />
+            <view v-else class="question-option-index">{{ option.no }}</view>
+          </view>
           <view class="question-option-content">
           <view class="question-option-content">
             <uv-parse :content="option.name"></uv-parse>
             <uv-parse :content="option.name"></uv-parse>
           </view>
           </view>
         </view>
         </view>
-        <view v-if="question.options.length" class="question-option"
+        <view v-if="question.options.length && !readonly" class="question-option"
           :class="{ 'question-option-not-know': question.isNotKnow }" @click="handleNotKnow">
           :class="{ 'question-option-not-know': question.isNotKnow }" @click="handleNotKnow">
           <view class="question-option-index">
           <view class="question-option-index">
             <uv-icon name="info-circle" :color="question.isNotKnow ? '#31A0FC' : '#999'" size="18" />
             <uv-icon name="info-circle" :color="question.isNotKnow ? '#31A0FC' : '#999'" size="18" />
@@ -23,10 +28,33 @@
           <view class="question-option-content text-fore-light">不会</view>
           <view class="question-option-content text-fore-light">不会</view>
         </view>
         </view>
       </view>
       </view>
+      <view class="answer-wrap mt-40 rounded-8 pt-60 pb-40 flex items-center text-center relative">
+        <ie-image v-if="question.isCorrect" src="/pagesStudy/static/image/icon-answer-correct.png"
+          class="absolute top-0 left-1/2 -translate-x-1/2 w-222 h-64" />
+        <ie-image v-else src="/pagesStudy/static/image/icon-answer-incorrect.png"
+          class="absolute top-0 left-1/2 -translate-x-1/2 w-240 h-64" />
+        <view class="flex-1">
+          <view class="text-34 text-[#2CC6A0] font-bold">{{ question.answer1 }}</view>
+          <view class="mt-4 text-26">正确答案</view>
+        </view>
+        <view class="h-40 w-1 bg-back"></view>
+        <view class="flex-1">
+          <view class="text-34 font-bold" :class="[question.isCorrect ? 'text-[#2CC6A0]' : 'text-[#FF5B5C]']">
+            {{ question.answers.join('') || (question.isNotKnow ? '不会' : '未答') }}
+          </view>
+          <view class="mt-4 text-26">我的答案</view>
+        </view>
+      </view>
+      <view v-if="question.parse" class="mt-40">
+        <view class="text-30 text-fore-title font-bold">解析</view>
+        <view class="mt-10 text-26 text-fore-light">
+          <uv-parse :content="question.parse || '暂无解析'"></uv-parse>
+        </view>
+      </view>
     </view>
     </view>
     <view v-if="question.subQuestions.length" class="is-sub-question">
     <view v-if="question.subQuestions.length" class="is-sub-question">
-      <question-item :question="subQuestion" v-for="(subQuestion, index) in question.subQuestions" :key="subQuestion.id"
-        :is-sub-question="true" :index="index" :total="question.subQuestions.length"
+      <question-item :question="subQuestion" :readonly="readonly" v-for="(subQuestion, index) in question.subQuestions"
+        :key="subQuestion.id" :is-sub-question="true" :index="index" :total="question.subQuestions.length"
         @update:question="handleSubQuestionUpdate" @select="handleSelectOption" @notKnow="handleSelectNotKnow" />
         @update:question="handleSubQuestionUpdate" @select="handleSelectOption" @notKnow="handleSelectNotKnow" />
     </view>
     </view>
   </view>
   </view>
@@ -40,6 +68,7 @@ import { NEXT_QUESTION, PREV_QUESTION, NEXT_QUESTION_QUICKLY, PREV_QUESTION_QUIC
 const { questionTypeDesc } = useExam();
 const { questionTypeDesc } = useExam();
 const props = defineProps<{
 const props = defineProps<{
   question: Study.Question;
   question: Study.Question;
+  readonly?: boolean;
   isSubQuestion?: boolean;
   isSubQuestion?: boolean;
   index?: number;
   index?: number;
   total?: number;
   total?: number;
@@ -54,7 +83,49 @@ const emit = defineEmits<{
   (e: 'notKnow', question: Study.Question): void;
   (e: 'notKnow', question: Study.Question): void;
   (e: 'scrollTo', selector: string): void;
   (e: 'scrollTo', selector: string): void;
 }>();
 }>();
-
+const getStyleClass = (option: Study.QuestionOption) => {
+  if (!props.readonly) {
+    return isSelected(option) ? 'question-option-selected' : '';
+  }
+  let customClass = '';
+  const { answers, answer1, answer2 } = props.question;
+  if ([EnumQuestionType.SINGLE_CHOICE, EnumQuestionType.JUDGMENT].includes(props.question.typeId)) {
+    if (answer1.includes(option.no)) {
+      customClass = 'question-option-correct';
+    } else if (answers.includes(option.no)) {
+      customClass = 'question-option-incorrect';
+    }
+  } else if ([EnumQuestionType.MULTIPLE_CHOICE].includes(props.question.typeId)) {
+    // 我选择的答案
+    if (answers.includes(option.no)) {
+      if (answer1.includes(option.no)) {
+        customClass = 'question-option-correct';
+      } else {
+        customClass = 'question-option-incorrect';
+      }
+    } else {
+      // 漏选的答案
+      if (answer1.includes(option.no)) {
+        customClass = 'question-option-miss';
+      }
+    }
+  }
+  // console.log(props.question, option)
+  return customClass;
+};
+const isOptionCorrect = (option: Study.QuestionOption) => {
+  const { answers, answer1, answer2 } = props.question;
+  if ([EnumQuestionType.SINGLE_CHOICE, EnumQuestionType.JUDGMENT].includes(props.question.typeId)) {
+    return answer1.includes(option.no);
+  } else if ([EnumQuestionType.MULTIPLE_CHOICE].includes(props.question.typeId)) {
+    return answer1.includes(option.no);
+  }
+  return false;
+}
+const isOptionIncorrect = (option: Study.QuestionOption) => {
+  const { answers, answer1 } = props.question;
+  return answers.includes(option.no) && !answer1.includes(option.no);
+}
 const getQuestionTitle = () => {
 const getQuestionTitle = () => {
   if (props.isSubQuestion) {
   if (props.isSubQuestion) {
     const prefix = questionTypeDesc[props.question.typeId as EnumQuestionType].slice(0, 2);
     const prefix = questionTypeDesc[props.question.typeId as EnumQuestionType].slice(0, 2);
@@ -65,7 +136,6 @@ const getQuestionTitle = () => {
 };
 };
 
 
 const handleNotKnow = () => {
 const handleNotKnow = () => {
-  // console.log('handleNotKnow')
   props.question.answers = [];
   props.question.answers = [];
   props.question.isNotKnow = !props.question.isNotKnow;
   props.question.isNotKnow = !props.question.isNotKnow;
   checkIsDone();
   checkIsDone();
@@ -80,6 +150,10 @@ const handleNotKnow = () => {
   }
   }
 }
 }
 const handleSelect = (option: Study.QuestionOption) => {
 const handleSelect = (option: Study.QuestionOption) => {
+  console.log(props.question)
+  if (props.readonly) {
+    return;
+  }
   if ([
   if ([
     EnumQuestionType.JUDGMENT,
     EnumQuestionType.JUDGMENT,
     EnumQuestionType.SINGLE_CHOICE,
     EnumQuestionType.SINGLE_CHOICE,
@@ -177,6 +251,14 @@ const isSelected = (option: Study.QuestionOption) => {
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>
+.answer-wrap {
+  box-shadow: 0 0 10px 0px rgba(0, 0, 0, 0.06);
+}
+
+.prefix {
+  @apply relative pl-20 before:content-[''] before:absolute before:left-0 before:top-1/2 before:-translate-y-1/2 before:w-[3px] before:h-[15px] before:bg-primary before:rounded-full;
+}
+
 .is-sub-question {
 .is-sub-question {
   @apply px-0;
   @apply px-0;
 
 
@@ -209,14 +291,14 @@ const isSelected = (option: Study.QuestionOption) => {
   .question-options {
   .question-options {
 
 
     .question-option {
     .question-option {
-      @apply flex items-center px-30 py-30 bg-back rounded-10;
+      @apply flex items-center px-30 py-24 bg-back rounded-8 border border-none border-transparent;
 
 
       .question-option-index {
       .question-option-index {
         @apply w-40 h-40 rounded-full bg-transparent text-30 text-fore-light font-bold flex items-center justify-center flex-shrink-0;
         @apply w-40 h-40 rounded-full bg-transparent text-30 text-fore-light font-bold flex items-center justify-center flex-shrink-0;
       }
       }
 
 
       .question-option-content {
       .question-option-content {
-        @apply text-30 text-fore-title ml-20;
+        @apply text-28 text-fore-title ml-20;
       }
       }
     }
     }
 
 
@@ -240,8 +322,67 @@ const isSelected = (option: Study.QuestionOption) => {
       }
       }
     }
     }
 
 
+    .question-option-correct,
+    .question-option-miss {
+      @apply bg-[#E7FCF8] border-[#E7FCF8] text-[#2CC6A0];
+
+      .question-option-index {
+        @apply text-[#2CC6A0];
+      }
+
+      .question-option-content {
+        @apply text-[#2CC6A0];
+      }
+    }
+
+    .question-option-miss {
+      @apply relative overflow-hidden;
+
+      &::before {
+        content: '';
+        position: absolute;
+        right: -56rpx;
+        top: 15rpx;
+        width: 180rpx;
+        height: 36rpx;
+        background: rgba(255, 91, 92, 0.2);
+        transform: rotate(30deg);
+        box-shadow: 0 2rpx 4rpx rgba(255, 91, 92, 0.1);
+      }
+
+      &::after {
+        content: '漏选';
+        position: absolute;
+        right: -8rpx;
+        top: 14rpx;
+        width: 100rpx;
+        height: 32rpx;
+        color: #FF5B5C;
+        font-size: 20rpx;
+        // font-weight: bold;
+        transform: rotate(30deg);
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        line-height: 1;
+      }
+    }
+
+    .question-option-incorrect {
+      @apply bg-[#FEEDE9] border-[#FEEDE9] text-[#FF5B5C];
+
+      .question-option-index {
+        @apply text-[#FF5B5C];
+      }
+
+      .question-option-content {
+        @apply text-[#FF5B5C];
+      }
+
+    }
+
     .question-option+.question-option {
     .question-option+.question-option {
-      @apply mt-20;
+      @apply mt-24;
     }
     }
   }
   }
 }
 }

+ 0 - 0
src/pagesStudy/pages/start-exam/components/question-progress.vue → src/pagesStudy/pages/exam-start/components/question-progress.vue


+ 25 - 4
src/pagesStudy/pages/start-exam/components/question-stats-popup.vue → src/pagesStudy/pages/exam-start/components/question-stats-popup.vue

@@ -16,9 +16,16 @@
               </view>
               </view>
             </view>
             </view>
             <view class="popup-header-right">
             <view class="popup-header-right">
-              <view class="stats-dot stats-dot-done">已答</view>
-              <view class="stats-dot stats-dot-not-done">未答</view>
-              <view class="stats-dot stats-dot-not-know">不会</view>
+              <block v-if="!readonly">
+                <view class="stats-dot stats-dot-done">已答</view>
+                <view class="stats-dot stats-dot-not-done">未答</view>
+                <view class="stats-dot stats-dot-not-know">不会</view>
+              </block>
+              <block v-else>
+                <view class="stats-dot stats-dot-correct">答对</view>
+                <view class="stats-dot stats-dot-incorrect">答错</view>
+                <view class="stats-dot stats-dot-not-done">未答</view>
+              </block>
             </view>
             </view>
           </view>
           </view>
           <slot />
           <slot />
@@ -33,6 +40,9 @@
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
+const props = defineProps<{
+  readonly?: boolean;
+}>();
 const popupRef = ref();
 const popupRef = ref();
 const open = () => {
 const open = () => {
   popupRef.value.open();
   popupRef.value.open();
@@ -87,6 +97,17 @@ defineExpose({
       @apply bg-back;
       @apply bg-back;
     }
     }
   }
   }
-}
 
 
+  &.stats-dot-correct {
+    &::before {
+      @apply bg-[#19a237];
+    }
+  }
+
+  &.stats-dot-incorrect {
+    &::before {
+      @apply bg-error;
+    }
+  }
+}
 </style>
 </style>

+ 0 - 0
src/pagesStudy/pages/start-exam/components/question-swiper-tip.vue → src/pagesStudy/pages/exam-start/components/question-swiper-tip.vue


+ 10 - 2
src/pagesStudy/pages/start-exam/components/question-wrap.vue → src/pagesStudy/pages/exam-start/components/question-wrap.vue

@@ -1,7 +1,9 @@
 <template>
 <template>
-  <scroll-view scroll-y class="question-wrap" :scroll-into-view="scrollIntoView" :scroll-with-animation="true">
+  <scroll-view v-if="visible" scroll-y class="question-wrap" :scroll-into-view="scrollIntoView"
+    :scroll-with-animation="true">
     <view class="h-20"></view>
     <view class="h-20"></view>
-    <question-item :question="question" @update:question="emit('update:question', $event)" @scrollTo="handleScrollTo" />
+    <question-item :question="question" :readonly="readonly" @update:question="emit('update:question', $event)"
+      @scrollTo="handleScrollTo" />
     <view class="h-20"></view>
     <view class="h-20"></view>
   </scroll-view>
   </scroll-view>
 </template>
 </template>
@@ -11,7 +13,13 @@ import { Study } from '@/types';
 import QuestionItem from './question-item.vue';
 import QuestionItem from './question-item.vue';
 const props = defineProps<{
 const props = defineProps<{
   question: Study.Question;
   question: Study.Question;
+  readonly?: boolean;
+  currentIndex: number;
+  index: number;
 }>();
 }>();
+const visible = computed(() => {
+  return Math.abs(props.currentIndex - props.index) <= 2;
+});
 const emit = defineEmits<{
 const emit = defineEmits<{
   (e: 'update:question', question: Study.Question): void;
   (e: 'update:question', question: Study.Question): void;
 }>();
 }>();

+ 93 - 56
src/pagesStudy/pages/start-exam/start-exam.vue → src/pagesStudy/pages/exam-start/exam-start.vue

@@ -2,48 +2,55 @@
   <ie-page :fix-height="true" :safe-area-inset-bottom="false">
   <ie-page :fix-height="true" :safe-area-inset-bottom="false">
     <ie-navbar :title="pageTitle" custom-back @left-click="handleLeftClick">
     <ie-navbar :title="pageTitle" custom-back @left-click="handleLeftClick">
       <template v-if="isReady" #headerRight>
       <template v-if="isReady" #headerRight>
-        <view class="" :class="{ 'text-red-500': practiceDuration > totalExamTime }">{{ formatPracticeDuration }}</view>
+        <view v-if="!isReadOnly" class="" :class="{ 'text-red-500': practiceDuration > totalExamTime }">{{
+          formatPracticeDuration }}</view>
+        <view v-else class="text-28">用时:{{ formatPracticeDuration }}</view>
       </template>
       </template>
     </ie-navbar>
     </ie-navbar>
-    <view v-if="isReady" class="px-20 py-14 bg-back flex justify-between items-center gap-x-20">
-      <text class="flex-1 min-w-1 text-26 ellipsis-1">{{ pageSubtitle }}</text>
-      <view class="flex items-baseline">
-        <text class="text-34 text-primary font-bold">{{ currentIndex + 1 }}</text>/
-        <text class="text-28 text-fore-subtitle">{{ totalCount }}</text>
-      </view>
-    </view>
-    <view class="flex-1 min-h-1 relative">
-      <view class="absolute inset-0 ">
-        <swiper class="h-full" :disable-touch="false" :current="currentIndex" :duration="swiperDuration"
-          @change="handleSwiperChange" @transition="handleSwiperTransition"
-          @animationfinish="handleSwiperAnimationFinish">
-          <swiper-item class="h-full" v-for="(item, index) in questionList" :key="index">
-            <question-wrap :question="item" @update:question="handleUpdateQuestion" />
-          </swiper-item>
-        </swiper>
-      </view>
-    </view>
-    <ie-safe-toolbar v-if="isReady" :height="64" :shadow="false">
-      <view class="px-18 h-full flex items-center justify-around border-0 border-t border-solid border-[#EFEFEF]">
-        <view class="w-48 h-48 flex items-center justify-center" id="question-correct-btn" @click="handleCorrect">
-          <uv-icon name="info-circle" size="24" />
+    <block v-if="isReady">
+      <view class="px-20 py-14 bg-back flex justify-between items-center gap-x-20">
+        <text class="flex-1 min-w-1 text-26 ellipsis-1">{{ pageSubtitle }}</text>
+        <view class="flex items-baseline">
+          <text class="text-34 text-primary font-bold">{{ currentIndex + 1 }}</text>/
+          <text class="text-28 text-fore-subtitle">{{ totalCount }}</text>
         </view>
         </view>
-        <view class="w-48 h-48 flex items-center justify-center" id="question-favorite-btn" @click="handleFavorite">
-          <uv-icon v-if="currentQuestion.isFavorite" name="star-fill" color="#FF9A18" size="27" />
-          <uv-icon v-else name="star" size="27" />
-        </view>
-        <view class="w-48 h-48 flex items-center justify-center" id="question-mark-btn" @click="handleMark">
-          <ie-image
-            :src="currentQuestion.isMark ? '/pagesStudy/static/image/icon-mark-active.png' : '/pagesStudy/static/image/icon-mark.png'"
-            custom-class="w-38 h-38" mode="aspectFill" />
-        </view>
-        <view class="w-48 h-48 flex items-center justify-center" id="question-calendar-btn" @click="handleCalendar">
-          <uv-icon name="calendar" size="28" />
+      </view>
+      <view class="flex-1 min-h-1 relative">
+        <view class="absolute inset-0 ">
+          <swiper class="h-full" :disable-touch="false" :current="currentIndex" :duration="swiperDuration"
+            @change="handleSwiperChange" @transition="handleSwiperTransition"
+            @animationfinish="handleSwiperAnimationFinish">
+            <block v-for="(item, index) in questionList" :key="item.id">
+              <swiper-item class="h-full" v-show="Math.abs(currentIndex - index) <= 2">
+                <question-wrap :question="item" :currentIndex="currentIndex" :index="index" :readonly="isReadOnly"
+                  @update:question="handleUpdateQuestion" />
+              </swiper-item>
+            </block>
+          </swiper>
         </view>
         </view>
       </view>
       </view>
-    </ie-safe-toolbar>
+      <ie-safe-toolbar :height="64" :shadow="false">
+        <view class="px-18 h-full flex items-center justify-around border-0 border-t border-solid border-[#EFEFEF]">
+          <view class="w-48 h-48 flex items-center justify-center" id="question-correct-btn" @click="handleCorrect">
+            <uv-icon name="info-circle" size="24" />
+          </view>
+          <view class="w-48 h-48 flex items-center justify-center" id="question-favorite-btn" @click="handleFavorite">
+            <uv-icon v-if="currentQuestion.isFavorite" name="star-fill" color="#FF9A18" size="27" />
+            <uv-icon v-else name="star" size="27" />
+          </view>
+          <view class="w-48 h-48 flex items-center justify-center" id="question-mark-btn" @click="handleMark">
+            <ie-image
+              :src="currentQuestion.isMark ? '/pagesStudy/static/image/icon-mark-active.png' : '/pagesStudy/static/image/icon-mark.png'"
+              custom-class="w-38 h-38" mode="aspectFill" />
+          </view>
+          <view class="w-48 h-48 flex items-center justify-center" id="question-calendar-btn" @click="handleCalendar">
+            <uv-icon name="calendar" size="28" />
+          </view>
+        </view>
+      </ie-safe-toolbar>
+    </block>
   </ie-page>
   </ie-page>
-  <question-stats-popup ref="questionStatsPopupRef">
+  <question-stats-popup ref="questionStatsPopupRef" :readonly="isReadOnly">
     <template #title>
     <template #title>
       <view class="ml-20">
       <view class="ml-20">
         <text class="text-30 text-primary">{{ doneCount }}</text>
         <text class="text-30 text-primary">{{ doneCount }}</text>
@@ -57,20 +64,22 @@
           <view v-for="(item, i) in groupedQuestionList" :key="i" class="">
           <view v-for="(item, i) in groupedQuestionList" :key="i" class="">
             <template v-if="item.list.length > 0">
             <template v-if="item.list.length > 0">
               <view class="h-70 bg-back px-20 leading-70 text-fore-subcontent">{{ questionTypeDesc[item.type] }}</view>
               <view class="h-70 bg-back px-20 leading-70 text-fore-subcontent">{{ questionTypeDesc[item.type] }}</view>
-              <view class="grid grid-cols-5 place-items-center gap-x-20 gap-y-20 p-30">
+              <view class="grid grid-cols-5 place-items-center gap-x-20 gap-y-30 p-30">
                 <view v-for="(qs, j) in item.list" :key="j" class="aspect-square flex items-center justify-center"
                 <view v-for="(qs, j) in item.list" :key="j" class="aspect-square flex items-center justify-center"
                   @click="hanadleNavigate(qs.index)">
                   @click="hanadleNavigate(qs.index)">
                   <view
                   <view
                     class="w-74 h-74 rounded-full flex items-center justify-center bg-white border border-solid border-border relative"
                     class="w-74 h-74 rounded-full flex items-center justify-center bg-white border border-solid border-border relative"
                     :class="{
                     :class="{
-                      'is-done': qs.question.isDone,
-                      'is-not-know': qs.question.isNotKnow,
-                      'is-mark': qs.question.isMark
+                      'is-done': !isReadOnly && qs.question.isDone,
+                      'is-not-know': !isReadOnly && qs.question.isNotKnow,
+                      'is-mark': !isReadOnly && qs.question.isMark,
+                      'is-correct': isReadOnly && qs.question.isCorrect,
+                      'is-incorrect': isReadOnly && !qs.question.isCorrect,
                     }">
                     }">
-                    <text class="z-1">{{ qs.index + 1 }}</text>
+                    <text class="z-1 font-bold text-32">{{ qs.index + 1 }}</text>
                     <ie-image v-if="qs.question.isMark" src="/pagesStudy/static/image/icon-mark-active.png"
                     <ie-image v-if="qs.question.isMark" src="/pagesStudy/static/image/icon-mark-active.png"
                       custom-class="absolute -top-12 left-14 w-28 h-28 z-1" mode="aspectFill" />
                       custom-class="absolute -top-12 left-14 w-28 h-28 z-1" mode="aspectFill" />
-                    <question-progress :progress="qs.question.progress || 0" />
+                    <question-progress v-if="!isReadOnly" :progress="qs.question.progress || 0" />
                   </view>
                   </view>
                 </view>
                 </view>
               </view>
               </view>
@@ -78,7 +87,7 @@
           </view>
           </view>
         </scroll-view>
         </scroll-view>
       </view>
       </view>
-      <view class="h-150 bg-white flex items-center gap-x-120 px-40">
+      <view v-if="!isReadOnly" class="h-150 bg-white flex items-center gap-x-120 px-40">
         <view class="flex flex-col items-center gap-x-10" @click="handleReset">
         <view class="flex flex-col items-center gap-x-10" @click="handleReset">
           <uv-icon name="reload" size="20" :color="doneCount > 0 ? '#999' : '#cccccc'" />
           <uv-icon name="reload" size="20" :color="doneCount > 0 ? '#999' : '#cccccc'" />
           <text class="mt-4 text-20 text-subcontent" :class="{ 'text-fore-light': doneCount <= 0 }">重新作答</text>
           <text class="mt-4 text-20 text-subcontent" :class="{ 'text-fore-light': doneCount <= 0 }">重新作答</text>
@@ -102,7 +111,7 @@ import QuestionSwiperTip from './components/question-swiper-tip.vue';
 import { useTransferPage } from '@/hooks/useTransferPage';
 import { useTransferPage } from '@/hooks/useTransferPage';
 import { useUserStore } from '@/store/userStore';
 import { useUserStore } from '@/store/userStore';
 import { EnumPaperType } from '@/common/enum';
 import { EnumPaperType } from '@/common/enum';
-import { getOpenExaminee, getPaper, commitExamineePaper, collectQuestion, cancelCollectQuestion, beginExaminee } from '@/api/modules/study';
+import { getOpenExaminee, getPaper, commitExamineePaper, collectQuestion, cancelCollectQuestion, beginExaminee, getExamineeResult } from '@/api/modules/study';
 import { useExam } from '@/composables/useExam';
 import { useExam } from '@/composables/useExam';
 import { Study } from '@/types';
 import { Study } from '@/types';
 import { NEXT_QUESTION, PREV_QUESTION, NEXT_QUESTION_QUICKLY, PREV_QUESTION_QUICKLY } from '@/types/injectionSymbols';
 import { NEXT_QUESTION, PREV_QUESTION, NEXT_QUESTION_QUICKLY, PREV_QUESTION_QUICKLY } from '@/types/injectionSymbols';
@@ -157,18 +166,22 @@ const examineeId = ref<number | undefined>(undefined);
 // const examineerData = ref<Study.Examinee>({} as Study.Examinee);
 // const examineerData = ref<Study.Examinee>({} as Study.Examinee);
 const paperData = ref<Study.ExamPaper>({} as Study.ExamPaper);
 const paperData = ref<Study.ExamPaper>({} as Study.ExamPaper);
 const pageTitle = computed(() => {
 const pageTitle = computed(() => {
-  const { paperType } = prevData.value;
-  return paperType === EnumPaperType.PRACTICE ? '练习' : '考试';
+  if (isReadOnly.value) {
+    return '考试解析';
+  }
+  return isExam.value ? '练习' : '考试';
 });
 });
 const isExam = computed(() => {
 const isExam = computed(() => {
   return prevData.value.paperType === EnumPaperType.SIMULATED;
   return prevData.value.paperType === EnumPaperType.SIMULATED;
 });
 });
 const pageSubtitle = computed(() => {
 const pageSubtitle = computed(() => {
-  const { name } = prevData.value;
-  return name;
+  return prevData.value.name;
+});
+const isReadOnly = computed(() => {
+  return prevData.value.readonly;
 });
 });
 const handleLeftClick = () => {
 const handleLeftClick = () => {
-  if (!isReady.value) {
+  if (isReady.value) {
     transferBack();
     transferBack();
     return;
     return;
   }
   }
@@ -261,7 +274,9 @@ const handleSwiperAnimationFinish = (e: any) => {
   }
   }
   const offsetX = transitionEndX.value - transitionStartX.value;
   const offsetX = transitionEndX.value - transitionStartX.value;
   if (offsetX < 0 && offsetX > -150) {
   if (offsetX < 0 && offsetX > -150) {
-    beforeSubmit();
+    if (!isReadOnly.value) {
+      beforeSubmit();
+    }
   }
   }
   isAnimationFinish.value = true;
   isAnimationFinish.value = true;
   transitionStartX.value = null;
   transitionStartX.value = null;
@@ -357,7 +372,7 @@ const handleUpdateQuestion = (question: Study.Question) => {
  * @param savedQuestion 上次做题历史数据
  * @param savedQuestion 上次做题历史数据
  * @param fullQuestion 当前试卷数据
  * @param fullQuestion 当前试卷数据
  */
  */
-const restoreQuestion = (savedQuestion: Study.ExamineeQuestion[], fullQuestion: Study.ApiQuestion[]) => {
+const restoreQuestion = (savedQuestion: Study.ExamineeQuestion[], fullQuestion: Study.ExamineeQuestion[]) => {
   if (!savedQuestion) {
   if (!savedQuestion) {
     return fullQuestion;
     return fullQuestion;
   }
   }
@@ -367,9 +382,12 @@ const restoreQuestion = (savedQuestion: Study.ExamineeQuestion[], fullQuestion:
       if (savedQs.answers) {
       if (savedQs.answers) {
         item.answers = savedQs.answers.filter(ans => ans.trim());
         item.answers = savedQs.answers.filter(ans => ans.trim());
       }
       }
+      item.answer1 = savedQs.answer1;
+      item.answer2 = savedQs.answer2;
       item.isMark = savedQs.isMark;
       item.isMark = savedQs.isMark;
       item.isFavorite = savedQs.isFavorite;
       item.isFavorite = savedQs.isFavorite;
       item.isNotKnow = savedQs.isNotKnow;
       item.isNotKnow = savedQs.isNotKnow;
+      item.parse = savedQs.parse;
       if (item.subQuestions) {
       if (item.subQuestions) {
         restoreQuestion(savedQs.subQuestions, item.subQuestions);
         restoreQuestion(savedQs.subQuestions, item.subQuestions);
       }
       }
@@ -394,9 +412,15 @@ const loadPracticeData = async () => {
   combinePaperData(data, paperType);
   combinePaperData(data, paperType);
 }
 }
 const loadSimulationData = async () => {
 const loadSimulationData = async () => {
-  const { paperType, simulationInfo } = prevData.value;
-  const { data } = await beginExaminee(simulationInfo.examineeId);
-  console.log('开卷信息', data)
+  const { paperType, readonly, simulationInfo } = prevData.value;
+  let data: Study.Examinee;
+  if (readonly) {
+    const res = await getExamineeResult(simulationInfo.examineeId);
+    data = res.data;
+  } else {
+    const res = await beginExaminee(simulationInfo.examineeId);
+    data = res.data;
+  }
   if (!data) {
   if (!data) {
     uni.$ie.hideLoading();
     uni.$ie.hideLoading();
     transferBack();
     transferBack();
@@ -417,8 +441,11 @@ const combinePaperData = async (examinee: Study.Examinee, paperType: EnumPaperTy
     setQuestionList(paperData.value.questions);
     setQuestionList(paperData.value.questions);
     setDuration(examinee.duration || 0);
     setDuration(examinee.duration || 0);
     await nextTick();
     await nextTick();
-    isReady.value = true;
+    const questionIndex = prevData.value.questionId ? paperData.value.questions.findIndex(item => item.id === prevData.value.questionId) : 0;
+    changeIndex(questionIndex);
+    await new Promise(resolve => setTimeout(resolve, 50));
     await nextTick();
     await nextTick();
+    isReady.value = true;
     console.log('试卷信息', res)
     console.log('试卷信息', res)
     if (!userStore.isExamGuideShow) {
     if (!userStore.isExamGuideShow) {
       setTimeout(() => {
       setTimeout(() => {
@@ -429,7 +456,9 @@ const combinePaperData = async (examinee: Study.Examinee, paperType: EnumPaperTy
       }, 300);
       }, 300);
     } else {
     } else {
       uni.$ie.hideLoading();
       uni.$ie.hideLoading();
-      startTime();
+      if (!isReadOnly.value) {
+        startTime();
+      }
     }
     }
   }
   }
 }
 }
@@ -479,4 +508,12 @@ onLoad(() => {
 .is-not-know {
 .is-not-know {
   @apply text-fore-title border-[#F2F2F2] bg-[#F2F2F2];
   @apply text-fore-title border-[#F2F2F2] bg-[#F2F2F2];
 }
 }
+
+.is-correct {
+  @apply text-[#2CC6A0] border-[#E7FCF8] bg-[#E7FCF8];
+}
+
+.is-incorrect {
+  @apply text-[#FF5B5C] border-[#FEEDE9] bg-[#FEEDE9];
+}
 </style>
 </style>

+ 1 - 1
src/pagesStudy/pages/start-exam/start-exam copy.vue → src/pagesStudy/pages/exam-start/start-exam copy.vue

@@ -309,7 +309,7 @@ const handleUpdateQuestion = (question: Study.Question) => {
  * @param savedQuestion 上次做题历史数据
  * @param savedQuestion 上次做题历史数据
  * @param fullQuestion 当前试卷数据
  * @param fullQuestion 当前试卷数据
  */
  */
-const restoreQuestion = (savedQuestion: Study.ExamineeQuestion[], fullQuestion: Study.ApiQuestion[]) => {
+const restoreQuestion = (savedQuestion: Study.ExamineeQuestion[], fullQuestion: Study.ExamineeQuestion[]) => {
   if (!savedQuestion) {
   if (!savedQuestion) {
     return fullQuestion;
     return fullQuestion;
   }
   }

+ 1 - 1
src/pagesStudy/pages/knowledge-practice/knowledge-practice.vue

@@ -58,7 +58,7 @@ const loadKnowledgeList = async () => {
 const handleStartPractice = async (node: Study.KnowledgeNode) => {
 const handleStartPractice = async (node: Study.KnowledgeNode) => {
   const isVip = await userStore.checkVip();
   const isVip = await userStore.checkVip();
   if (isVip) {
   if (isVip) {
-    transferTo('/pagesStudy/pages/start-exam/start-exam', {
+    transferTo('/pagesStudy/pages/exam-start/exam-start', {
       data: {
       data: {
         name: '知识点练习-' + node.name,
         name: '知识点练习-' + node.name,
         paperType: EnumPaperType.PRACTICE,
         paperType: EnumPaperType.PRACTICE,

+ 76 - 41
src/pagesStudy/pages/simulation-analysis/components/exam-stat.vue

@@ -3,7 +3,7 @@
     <view class="text-30 text-fore-title font-bold">答题情况</view>
     <view class="text-30 text-fore-title font-bold">答题情况</view>
     <view class="mt-20 flex items-center">
     <view class="mt-20 flex items-center">
       <view class="flex-1 text-center">
       <view class="flex-1 text-center">
-        <view class="text-40 text-fore-title font-bold">{{ questionList.length }}</view>
+        <view class="text-40 text-fore-title font-bold">{{ totalQuestions }}</view>
         <view class="mt-5 text-28 text-fore-light">题量</view>
         <view class="mt-5 text-28 text-fore-light">题量</view>
       </view>
       </view>
       <view class="flex-1 text-center">
       <view class="flex-1 text-center">
@@ -11,63 +11,81 @@
         <view class="mt-5 text-28 text-fore-light">总分</view>
         <view class="mt-5 text-28 text-fore-light">总分</view>
       </view>
       </view>
       <view class="flex-1 text-center">
       <view class="flex-1 text-center">
-        <view class="text-40 text-fore-title font-bold">{{ getScore }}</view>
+        <view class="text-40 text-fore-title font-bold">{{ stats.score }}</view>
         <view class="mt-5 text-28 text-fore-light">得分</view>
         <view class="mt-5 text-28 text-fore-light">得分</view>
       </view>
       </view>
       <view class="flex-1 text-center">
       <view class="flex-1 text-center">
-        <view class="text-40 text-fore-title font-bold">{{ rightRate }}%</view>
+        <view class="text-40 text-fore-title font-bold">{{ stats.rate }}%</view>
         <view class="mt-5 text-28 text-fore-light">正确率</view>
         <view class="mt-5 text-28 text-fore-light">正确率</view>
       </view>
       </view>
     </view>
     </view>
     <view class="h-1 bg-[#E6E6E6] my-20"></view>
     <view class="h-1 bg-[#E6E6E6] my-20"></view>
-    <view class="mt-40 flex items-center justify-end gap-x-10">
-      <view class="w-18 h-18 rounded-full unanswered"></view>
-      <view class="text-20 text-fore-subtitle">未答</view>
-      <view class="ml-10 w-18 h-18 rounded-full bg-[#2CC6A0]"></view>
-      <view class="text-20 text-fore-subtitle">答对</view>
-      <view class="ml-10 w-18 h-18 rounded-full bg-[#FF5B5C]"></view>
-      <view class="text-20 text-fore-subtitle">答错</view>
+    <view class="mt-40 flex items-center justify-end gap-x-30">
+      <view class="flex items-center gap-x-12" :class="{ 'is-filter': !filterTypes.includes('correct') }"
+        @click="handleFilter('correct')">
+        <view class="icon w-18 h-18 rounded-full bg-[#2CC6A0]"></view>
+        <view class="legend text-20 text-fore-subtitle">答对</view>
+      </view>
+      <view class="flex items-center gap-x-12" :class="{ 'is-filter': !filterTypes.includes('incorrect') }"
+        @click="handleFilter('incorrect')">
+        <view class="icon w-18 h-18 rounded-full bg-[#FF5B5C]"></view>
+        <view class="legend text-20 text-fore-subtitle">答错</view>
+      </view>
+      <view class="flex items-center gap-x-12" :class="{ 'is-filter': !filterTypes.includes('unanswered') }"
+        @click="handleFilter('unanswered')">
+        <view class="icon w-18 h-18 rounded-full is-unanswered"></view>
+        <view class="legend text-20 text-fore-subtitle">未答</view>
+      </view>
     </view>
     </view>
-    <view class="mt-20 grid grid-cols-5 gap-x-10 gap-y-30 place-items-center">
-      <view class="question-item" v-for="(item, index) in questionList" :key="item.id"
-        :class="[item.isNotAnswer ? 'is-unanswered' : (item.isRight ? 'is-correct' : 'is-incorrect')]">
-        <view class="text-36 font-bold">{{ index + 1 }}</view>
+    <view class="mt-30 grid grid-cols-5 gap-x-10 gap-y-30 place-items-center">
+      <view class="question-item" v-for="item in questionList" :key="item.id"
+        :class="[item.isNotAnswer ? 'is-unanswered' : (item.isRight ? 'is-correct' : 'is-incorrect')]"
+        @click="handleDetail(item)">
+        <view class="text-36 font-bold">{{ item.originalIndex }}</view>
       </view>
       </view>
     </view>
     </view>
   </view>
   </view>
 </template>
 </template>
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { EnumQuestionType } from '@/common/enum';
+import { EnumPaperType, EnumQuestionType } from '@/common/enum';
+import { useTransferPage } from '@/hooks/useTransferPage';
 import { Study } from '@/types';
 import { Study } from '@/types';
-
+const { transferTo } = useTransferPage();
 const props = defineProps<{
 const props = defineProps<{
   data: Study.Examinee;
   data: Study.Examinee;
 }>();
 }>();
 const paperInfo = computed(() => {
 const paperInfo = computed(() => {
   return props.data.paperInfo || {};
   return props.data.paperInfo || {};
 });
 });
-const rightTotal = computed(() => {
-  return questionList.value.filter(item => item.isRight).length;
+const stats = computed(() => {
+  return props.data.stats || {};
 });
 });
-const rightRate = computed(() => {
-  const { totalCount = 0, wrongCount = 0 } = props.data;
-  return Math.round((totalCount - wrongCount) / totalCount * 100) || 0;
-});
-const getScore = computed(() => {
-  return questionList.value.reduce((sum, item) => {
-    return sum + item.score || 0;
-  }, 0);
-})
-const wrongTotal = computed(() => {
-  return questionList.value.filter(item => !item.isRight).length;
+const totalQuestions = computed(() => {
+  return (props.data.questions || []).length;
 });
 });
+const filterTypes = ref<('correct' | 'incorrect' | 'unanswered')[]>(['correct', 'incorrect', 'unanswered']);
 const questionList = computed(() => {
 const questionList = computed(() => {
-  return (props.data.questions || []).map(item => {
+  return (props.data.questions || []).map((item, index) => {
     return {
     return {
       ...item,
       ...item,
+      originalIndex: index + 1, // 保存原始序号
       isRight: judgeCorrect(item),
       isRight: judgeCorrect(item),
       isNotAnswer: !item.answers.filter(item => item !== ' ').length
       isNotAnswer: !item.answers.filter(item => item !== ' ').length
     }
     }
+  }).filter(item => {
+    // 判断是否答对
+    if (item.isRight && filterTypes.value.includes('correct')) {
+      return true;
+    }
+    // 判断是否答错(答了但是错了)
+    if (!item.isRight && !item.isNotAnswer && filterTypes.value.includes('incorrect')) {
+      return true;
+    }
+    // 判断是否未答
+    if (item.isNotAnswer && filterTypes.value.includes('unanswered')) {
+      return true;
+    }
+    return false;
   });
   });
 });
 });
 const judgeCorrect = (qs: Study.ExamineeQuestion) => {
 const judgeCorrect = (qs: Study.ExamineeQuestion) => {
@@ -78,18 +96,25 @@ const judgeCorrect = (qs: Study.ExamineeQuestion) => {
     return rightAnswers.length === qs.answers.length && rightAnswers.every(item => qs.answers.includes(item));
     return rightAnswers.length === qs.answers.length && rightAnswers.every(item => qs.answers.includes(item));
   }
   }
 }
 }
-type QuestionItem = {
-  id: number;
-  type: number;
-  isCorrect: boolean;
+const handleFilter = (type: 'correct' | 'incorrect' | 'unanswered') => {
+  if (filterTypes.value.includes(type)) {
+    filterTypes.value = filterTypes.value.filter(item => item !== type);
+  } else {
+    filterTypes.value.push(type);
+  }
+  console.log(filterTypes.value)
 }
 }
-const list = ref<QuestionItem[]>([]);
-// 生成100条测试数据
-for (let i = 0; i < 100; i++) {
-  list.value.push({
-    id: i,
-    type: i % 3,
-    isCorrect: i % 2 === 0
+const handleDetail = (item: Study.ExamineeQuestion) => {
+  transferTo('/pagesStudy/pages/exam-start/exam-start', {
+    data: {
+      name: '模拟考试-' + props.data.subjectName,
+      readonly: true,
+      questionId: item.id,
+      paperType: EnumPaperType.SIMULATED,
+      simulationInfo: {
+        examineeId: props.data.examineeId,
+      }
+    }
   });
   });
 }
 }
 </script>
 </script>
@@ -112,4 +137,14 @@ for (let i = 0; i < 100; i++) {
   background: #FEEDE9;
   background: #FEEDE9;
   color: #FF5B5C;
   color: #FF5B5C;
 }
 }
+
+.is-filter {
+  .icon {
+    background: #bbbbbb;
+  }
+
+  .legend {
+    color: #bbbbbb;
+  }
+}
 </style>
 </style>

+ 11 - 4
src/pagesStudy/pages/simulation-analysis/components/score-stat.vue

@@ -1,23 +1,30 @@
 <template>
 <template>
-  <view class="sticky bottom-20 px-16 py-20 bg-white rounded-15 mt-20">
+  <view class="shadow-card sticky bottom-20 px-16 py-20 bg-white rounded-15 mt-20">
     <view class="text-30 text-fore-title font-bold">得分分布</view>
     <view class="text-30 text-fore-title font-bold">得分分布</view>
     <view class="mt-20 mx-10 flex items-center bg-back rounded-10 py-30">
     <view class="mt-20 mx-10 flex items-center bg-back rounded-10 py-30">
       <view class="flex-1 text-center">
       <view class="flex-1 text-center">
-        <view class="text-40 text-fore-title font-bold">276</view>
+        <view class="text-40 text-fore-title font-bold">{{ stats.maxScore}}</view>
         <view class="mt-5 text-28 text-fore-light">最高分</view>
         <view class="mt-5 text-28 text-fore-light">最高分</view>
       </view>
       </view>
       <view class="flex-1 text-center">
       <view class="flex-1 text-center">
-        <view class="text-40 text-fore-title font-bold">147</view>
+        <view class="text-40 text-fore-title font-bold">{{ stats.averageScore }}</view>
         <view class="mt-5 text-28 text-fore-light">平均分</view>
         <view class="mt-5 text-28 text-fore-light">平均分</view>
       </view>
       </view>
       <view class="flex-1 text-center">
       <view class="flex-1 text-center">
-        <view class="text-40 text-fore-title font-bold">36%</view>
+        <view class="text-40 text-fore-title font-bold">{{ stats.hitRate }}%</view>
         <view class="mt-5 text-28 text-fore-light">击败考生</view>
         <view class="mt-5 text-28 text-fore-light">击败考生</view>
       </view>
       </view>
     </view>
     </view>
   </view>
   </view>
 </template>
 </template>
 <script lang="ts" setup>
 <script lang="ts" setup>
+import { Study } from '@/types';
 
 
+const props = defineProps<{
+  data: Study.Examinee;
+}>();
+const stats = computed(() => {
+  return props.data.stats || {};
+});
 </script>
 </script>
 <style lang="scss" scoped></style>
 <style lang="scss" scoped></style>

+ 7 - 16
src/pagesStudy/pages/simulation-analysis/simulation-analysis.vue

@@ -44,27 +44,16 @@ import { Study } from '@/types';
 import { EnumQuestionType } from '@/common/enum';
 import { EnumQuestionType } from '@/common/enum';
 const { prevData } = useTransferPage();
 const { prevData } = useTransferPage();
 const examineeData = ref<Study.Examinee>({} as Study.Examinee);
 const examineeData = ref<Study.Examinee>({} as Study.Examinee);
-// const questionList = computed(() => {
-//   return (examineeData.value.questions || []).map((item: Study.ExamineeQuestion) => {
-//     return {
-//       ...item,
-//       isRight: judgeCorrect(item)
-//     }
-//   });
-// });
+
 const rightRate = computed(() => {
 const rightRate = computed(() => {
   const { totalCount = 0, wrongCount = 0 } = examineeData.value;
   const { totalCount = 0, wrongCount = 0 } = examineeData.value;
   return Math.round((totalCount - wrongCount) / totalCount * 100) || 0;
   return Math.round((totalCount - wrongCount) / totalCount * 100) || 0;
 });
 });
-// const judgeCorrect = (qs: Study.ExamineeQuestion) => {
-//   if (qs.typeId === EnumQuestionType.SINGLE_CHOICE) {
-//     return qs.answers.includes(qs.answer1);
-//   } else if (qs.typeId === EnumQuestionType.MULTIPLE_CHOICE) {
-//     const rightAnswers = qs.answer1.split('').filter(item => item !== ' ');
-//     return rightAnswers.length === qs.answers.length && rightAnswers.every(item => qs.answers.includes(item));
-//   }
-// }
+
 const formatTime = (time: number) => {
 const formatTime = (time: number) => {
+  if (!time) {
+    return '';
+  }
   const hours = Math.floor(time / 3600);
   const hours = Math.floor(time / 3600);
   const minutes = Math.floor((time % 3600) / 60);
   const minutes = Math.floor((time % 3600) / 60);
   const seconds = time % 60;
   const seconds = time % 60;
@@ -76,8 +65,10 @@ const formatTime = (time: number) => {
   }
   }
 };
 };
 const loadData = async () => {
 const loadData = async () => {
+  uni.$ie.showLoading();
   const res = await getExamineeResult(prevData.value.examineeId);
   const res = await getExamineeResult(prevData.value.examineeId);
   examineeData.value = res.data;
   examineeData.value = res.data;
+  uni.$ie.hideLoading();
 };
 };
 onLoad(() => {
 onLoad(() => {
   loadData();
   loadData();

+ 4 - 4
src/pagesStudy/pages/simulation-start/simulation-start.vue

@@ -57,7 +57,7 @@
 import { useTransferPage } from '@/hooks/useTransferPage';
 import { useTransferPage } from '@/hooks/useTransferPage';
 import { getOpenExaminee, getPaper } from '@/api/modules/study';
 import { getOpenExaminee, getPaper } from '@/api/modules/study';
 import { useUserStore } from '@/store/userStore';
 import { useUserStore } from '@/store/userStore';
-import { EnumDictName } from '@/common/enum';
+import { EnumDictName, EnumPaperType } from '@/common/enum';
 import { Study } from '@/types';
 import { Study } from '@/types';
 const { prevData, transferTo } = useTransferPage();
 const { prevData, transferTo } = useTransferPage();
 const userStore = useUserStore();
 const userStore = useUserStore();
@@ -74,7 +74,7 @@ const questionTypes = computed(() => {
     return acc + curr.type + (index < paperInfo.value.types.length - 1 ? "、" : "");
     return acc + curr.type + (index < paperInfo.value.types.length - 1 ? "、" : "");
   }, "");
   }, "");
 })
 })
-const questions = ref<Study.ApiQuestion[]>([]);
+const questions = ref<Study.ExamineeQuestion[]>([]);
 const universityInfo = computed(() => {
 const universityInfo = computed(() => {
   return prevData.value.universityInfo;
   return prevData.value.universityInfo;
 });
 });
@@ -83,9 +83,9 @@ const subjectInfo = computed(() => {
 });
 });
 const handleStartTest = () => {
 const handleStartTest = () => {
   // transferTo('/pagesStudy/pages/simulation-analysis/simulation-analysis')
   // transferTo('/pagesStudy/pages/simulation-analysis/simulation-analysis')
-  transferTo('/pagesStudy/pages/start-exam/start-exam', {
+  transferTo('/pagesStudy/pages/exam-start/exam-start', {
     data: {
     data: {
-      paperType: 'Simulated',
+      paperType: EnumPaperType.SIMULATED,
       name: '模拟考试-' + subjectInfo.value.subject,
       name: '模拟考试-' + subjectInfo.value.subject,
       simulationInfo: {
       simulationInfo: {
         examineeId: examineeId.value
         examineeId: examineeId.value

BIN
src/pagesStudy/static/image/icon-answer-correct.png


BIN
src/pagesStudy/static/image/icon-answer-incorrect.png


+ 2 - 2
src/static/theme/var.scss

@@ -22,8 +22,8 @@ $warning-dark: #f79824;
 $warning-disabled: #f9d39b;
 $warning-disabled: #f9d39b;
 $warning-light: #fdf6ec;
 $warning-light: #fdf6ec;
 
 
-$success: #5ac725;
-$success-dark: #53c21d;
+$success: #19a237;
+$success-dark: #14822e;
 $success-disabled: #a9e08f;
 $success-disabled: #a9e08f;
 $success-light: #f5fff0;
 $success-light: #f5fff0;
 
 

+ 20 - 19
src/types/study.ts

@@ -100,6 +100,7 @@ export interface QuestionState {
   isMark?: boolean;
   isMark?: boolean;
   isNotKnow?: boolean;
   isNotKnow?: boolean;
   isFavorite?: boolean;
   isFavorite?: boolean;
+  isCorrect?: boolean;
   progress?: number;
   progress?: number;
 }
 }
 
 
@@ -112,18 +113,21 @@ export interface ExamineeQuestion {
   answer1: string;
   answer1: string;
   answer2: string;
   answer2: string;
   score: number;
   score: number;
-  title: string;
+  title?: string;
+  options?: string[];
   isFavorite: boolean;
   isFavorite: boolean;
   isMark: boolean;
   isMark: boolean;
   isNotKnow: boolean;
   isNotKnow: boolean;
   answers: string[];
   answers: string[];
   subQuestions: ExamineeQuestion[];
   subQuestions: ExamineeQuestion[];
+  parse?: string;
 }
 }
 export interface Examinee {
 export interface Examinee {
   examineeId: number;
   examineeId: number;
   name: string;
   name: string;
   paperId: number;
   paperId: number;
   duration: number;
   duration: number;
+  knowledgeId: number;
   questions: ExamineeQuestion[];
   questions: ExamineeQuestion[];
   // 
   // 
   paperInfo: ExamineePaperInfo;
   paperInfo: ExamineePaperInfo;
@@ -135,6 +139,7 @@ export interface Examinee {
   subjectId: number;
   subjectId: number;
   totalCount: number;
   totalCount: number;
   wrongCount: number;
   wrongCount: number;
+  stats: ExamineeStats;
 }
 }
 export interface ExamineePaperInfo {
 export interface ExamineePaperInfo {
   score: number;
   score: number;
@@ -145,6 +150,13 @@ export interface ExamineePaperInfo {
     score: number;
     score: number;
   }[]
   }[]
 }
 }
+export interface ExamineeStats {
+  averageScore: number;
+  hitRate: number;
+  maxScore: number;
+  rate: number;
+  score: number;
+}
 
 
 /**
 /**
  * 试卷
  * 试卷
@@ -153,20 +165,13 @@ export interface ExamPaper {
   id: number;
   id: number;
   paperName: string;
   paperName: string;
   paperType: string;
   paperType: string;
-  questions: ApiQuestion[];
+  questions: ExamineeQuestion[];
   score?: number;
   score?: number;
   subjectId: number;
   subjectId: number;
   year: number;
   year: number;
 }
 }
 
 
-export interface ApiQuestion extends QuestionState {
-  id: number;
-  title: string;
-  typeId: number;
-  options: string[];
-  answers: (string | number)[];
-  subQuestions: ApiQuestion[];
-}
+export type ExamPaperQuestion = Pick<ExamineeQuestion, 'id' | 'title' | 'options' | 'subQuestions'>;
 
 
 export interface ExamPaperSubmit {
 export interface ExamPaperSubmit {
   id: number;
   id: number;
@@ -183,7 +188,7 @@ export interface ExamPaperSubmit {
 
 
 export interface QuestionOption {
 export interface QuestionOption {
   id: number;
   id: number;
-  no: string | number; // A, B, C, D
+  no: string; // A, B, C, D
   name: string;
   name: string;
   isAnswer: boolean;
   isAnswer: boolean;
 }
 }
@@ -193,17 +198,13 @@ export interface Question extends QuestionState {
   title: string;
   title: string;
   typeId: number;
   typeId: number;
   options: QuestionOption[];
   options: QuestionOption[];
-  answers: (string | number)[];
+  answers: string[];
+  answer1: string;
+  answer2: string;
+  parse?: string;
   subQuestions: Question[];
   subQuestions: Question[];
 }
 }
 
 
-export interface QuestionOption {
-  id: number;
-  no: string | number; // A, B, C, D
-  name: string;
-  isAnswer: boolean;
-}
-
 export interface SubjectListRequestDTO {
 export interface SubjectListRequestDTO {
   directed: boolean;
   directed: boolean;
 }
 }