shmily1213 преди 1 месец
родител
ревизия
8ea4ac403b

+ 26 - 4
src/api/modules/study.ts

@@ -1,6 +1,6 @@
 import { ApiResponse } from "@/types";
 import flyio from "../flyio";
-import { Knowledge, StudyPlan, Subject } from "@/types/study";
+import { ApiQuestion, ExamPaper, ExamPaperSubmit, Knowledge, StudyPlan, Subject } from "@/types/study";
 
 /**
  * 获取学习计划
@@ -65,7 +65,7 @@ export function getSubjectList(params: any) {
  * @returns 
  */
 export function getKnowledgeList(params: { subjectId: number }) {
-  return flyio.get('/front/paper/knownledge', params) as Promise<ApiResponse<Knowledge[]>>;
+  return flyio.get('/front/paper/knowledge', params) as Promise<ApiResponse<Knowledge[]>>;
 }
 
 /**
@@ -74,5 +74,27 @@ export function getKnowledgeList(params: { subjectId: number }) {
  * @returns 
  */
 export function getOpenExaminee(params: { paperType: string, relateId: number }) {
-  return flyio.get('/front/exam/openExaminee', params) as Promise<ApiResponse<any>>;
-}
+  return flyio.post('/front/exam/openExaminee', null, { params }, {
+    headers: {
+      'Content-Type': 'application/www-form-urlencoded'
+    }
+  }) as Promise<ApiResponse<any>>;
+}
+
+/**
+ * 获取试卷
+ * @param params 
+ * @returns 
+ */
+export function getPaper(params: { type: string, id: number }) {
+  return flyio.get('/front/paper/paper', params) as Promise<ApiResponse<ExamPaper>>;
+}
+
+/**
+ * 提交试卷
+ * @param params 
+ * @returns 
+ */
+export function commitExamineePaper(params: ExamPaperSubmit) {
+  return flyio.post('/front/exam/commitExamineePaper', params) as Promise<ApiResponse<any>>;
+}

+ 13 - 1
src/common/enum.ts

@@ -105,8 +105,20 @@ export enum EnumQuestionType {
    * 填空
    */
   FILL_IN_THE_BLANK = 4,
+  /**
+   * 主观题
+   */
+  SUBJECTIVE = 5,
   /**
    * 简答
    */
-  SHORT_ANSWER = 5
+  SHORT_ANSWER = 6,
+  /**
+   * 问答题
+   */
+  ESSAY = 7,
+  /**
+   * 分析题
+   */
+  ANALYSIS = 8
 }

+ 72 - 95
src/composables/useExam.ts

@@ -1,5 +1,7 @@
 import { EnumQuestionType } from "@/common/enum";
+import { getPaper } from '@/api/modules/study';
 import { Study } from "@/types";
+import { Question } from "@/types/study";
 
 export const useExam = () => {
   const questionTypeDesc: Record<EnumQuestionType, string> = {
@@ -7,7 +9,10 @@ export const useExam = () => {
     [EnumQuestionType.MULTIPLE_CHOICE]: '多选题',
     [EnumQuestionType.JUDGMENT]: '判断题',
     [EnumQuestionType.FILL_IN_THE_BLANK]: '填空题',
-    [EnumQuestionType.SHORT_ANSWER]: '简答题'
+    [EnumQuestionType.SUBJECTIVE]: '主观题',
+    [EnumQuestionType.SHORT_ANSWER]: '简答题',
+    [EnumQuestionType.ESSAY]: '问答题',
+    [EnumQuestionType.ANALYSIS]: '分析题',
   }
   // 题型顺序
   const questionTypeOrder = [
@@ -15,7 +20,10 @@ export const useExam = () => {
     EnumQuestionType.MULTIPLE_CHOICE,
     EnumQuestionType.JUDGMENT,
     EnumQuestionType.FILL_IN_THE_BLANK,
-    EnumQuestionType.SHORT_ANSWER
+    EnumQuestionType.SUBJECTIVE,
+    EnumQuestionType.SHORT_ANSWER,
+    EnumQuestionType.ESSAY,
+    EnumQuestionType.ANALYSIS
   ];
   let interval: NodeJS.Timeout | null = null;
   const countDownCallback = ref<() => void>(() => { });
@@ -58,7 +66,7 @@ export const useExam = () => {
     for (let i = 0; i <= questionList.value.length - 1; i++) {
       const qs = questionList.value[i];
       state.forEach(item => {
-        if (qs.type === item.type) {
+        if (qs.typeId === item.type) {
           qs.isDone = isDone(qs);
           item.list.push({
             question: qs,
@@ -88,10 +96,10 @@ export const useExam = () => {
     return stateQuestionList.value.reduce((acc, item) => acc + item.list.filter(q => q.question.isMark).length, 0);
   });
   const isDone = (qs: Study.Question): boolean => {
-    if (qs.subQuestions.length > 0) {
+    if (qs.subQuestions && qs.subQuestions.length > 0) {
       return qs.subQuestions.every(q => isDone(q));
     }
-    return qs.answer.length > 0;
+    return qs.answer && qs.answer.length > 0;
   }
   const nextQuestion = () => {
     if (currentIndex.value >= questionList.value.length - 1) {
@@ -130,6 +138,15 @@ export const useExam = () => {
       }, 0);
     }, 0);
   }
+  const changeIndex = (index: number) => {
+    swiperDuration.value = 0;
+    setTimeout(() => {
+      currentIndex.value = index;
+      setTimeout(() => {
+        swiperDuration.value = 300;
+      }, 0);
+    }, 0);
+  }
   // 开始计时
   const startPracticeDuration = () => {
     interval = setInterval(() => {
@@ -167,98 +184,55 @@ export const useExam = () => {
   const setCountDownCallback = (callback: () => void) => {
     countDownCallback.value = callback;
   }
-  const loadExamData = async () => {
-    // const { data } = await getOpenExaminee({
-    //   paperType: prevData.value.paperType,
-    //   relateId: prevData.value.relateId
+  const loadExamData = async (paperType: string, paperId: number) => {
+    // const res = await getPaper({
+    //   type: paperType,
+    //   id: paperId
     // });
-    // console.log(data)
-    questionList.value = [
-      {
-        id: 1,
-        name: '下列括号中测试字体长度这是一个长问题测试字的读音完全正确的一项是()',
-        type: EnumQuestionType.SINGLE_CHOICE,
-        options: [
-          {
-            id: 1,
-            no: 'A',
-            name: '选项1',
-            isAnswer: false
-          },
-          {
-            id: 2,
-            no: 'B',
-            name: '选项2',
-            isAnswer: false
-          },
-          {
-            id: 3,
-            no: 'C',
-            name: '选项3',
-            isAnswer: false
-          },
-          {
-            id: 4,
-            no: 'D',
-            name: '选项4',
-            isAnswer: false
-          }
-        ],
-        answer: [],
-        subQuestions: []
-      },
-      {
-        id: 2,
-        name: '题目2',
-        type: EnumQuestionType.MULTIPLE_CHOICE,
-        options: [],
+    // // questionList.value = res.data.questions;
+    // console.log(questionList.value)
+    // startPracticeDuration();
+  }
+  const setQuestionList = (list: Study.ApiQuestion[]) => {
+    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、给每个项目补充额外字段
+    const parseQuestion = (item: Study.ApiQuestion): Study.Question => {
+      return {
+        title: item.title,
+        typeId: item.typeId,
+        id: item.id,
         answer: [],
-        subQuestions: [
-          {
-            id: 1,
-            name: '题目2-1',
-            type: EnumQuestionType.SINGLE_CHOICE,
-            options: [
-              {
-                id: 1,
-                no: 'A',
-                name: '选项1',
-                isAnswer: false
-              },
-              {
-                id: 2,
-                no: 'B',
-                name: '选项2',
-                isAnswer: false
-              },
-              {
-                id: 3,
-                no: 'C',
-                name: '选项3',
-                isAnswer: false
-              },
-              {
-                id: 4,
-                no: 'D',
-                name: '选项4',
-                isAnswer: false
-              }
-            ],
-            answer: [],
-            subQuestions: []
-          }
-        ]
-      },
-      {
-        id: 3,
-        name: '题目3',
-        type: EnumQuestionType.JUDGMENT,
-        options: [],
+        subQuestions: item.subQuestions?.map(parseQuestion) || [],
+        options: item.options.map((option, index) => {
+          return {
+            name: option,
+            no: orders[index],
+            id: index,
+            isAnswer: false
+          } as Study.QuestionOption
+        })
+      } as Study.Question
+    }
+    const arr: Study.Question[] = list.map(item => {
+      return parseQuestion(item);
+    });
+    questionList.value = arr;
+  }
+  const reset = () => {
+    questionList.value = questionList.value.map(item => {
+      return {
+        ...item,
         answer: [],
-        subQuestions: []
-      }
-    ];
-    // startPracticeDuration();
+        isMark: false,
+        isNotKnow: false
+      } as Study.Question;
+    });
+    changeIndex(0);
+    practiceDuration.value = 0;
+    examDuration.value = 0;
+    interval && clearInterval(interval);
+    interval = null;
   }
   return {
     questionList,
@@ -289,6 +263,9 @@ export const useExam = () => {
     stopExamDuration,
     setExamDuration,
     setPracticeDuration,
-    setCountDownCallback
+    setCountDownCallback,
+    setQuestionList,
+    changeIndex,
+    reset
   }
 }

+ 7 - 1
src/pagesStudy/components/knowledge-tree-node.vue

@@ -24,7 +24,7 @@
       :style="{ height: nodeData.actualHeight + 'px' }">
       <knowledge-tree-node v-for="child in nodeData.children" :key="child.name" :node-data="child"
         :parent-data="nodeData" @node-click="handleNodeClick" @update-height="handleUpdateHeight"
-        @start-practice="handleStartPractice">
+        @start-practice="handleChildStartPractice">
         <template #default>
           <slot></slot>
         </template>
@@ -100,6 +100,12 @@ const handleClick = () => {
 const handleStartPractice = () => {
   emit('startPractice', props.nodeData);
 };
+
+// 处理子节点开始练习事件
+const handleChildStartPractice = (nodeData: Study.KnowledgeNode) => {
+  emit('startPractice', nodeData);
+};
+
 // 处理子节点点击事件
 const handleNodeClick = (eventData: { node: Study.KnowledgeNode; parent: Study.KnowledgeNode }) => {
   // 向上传递点击事件

+ 45 - 21
src/pagesStudy/pages/start-exam/components/question-item.vue

@@ -1,18 +1,28 @@
 <template>
   <view class="question-item">
-    <view class="question-type">{{ questionTypeDesc[question.type as EnumQuestionType] }}</view>
-    <view class="question-content">{{ question.name }}</view>
+    <view class="question-type">{{ questionTypeDesc[question.typeId as EnumQuestionType] }}</view>
+    <view class="question-content">
+      <uv-parse :content="question.title"></uv-parse>
+    </view>
     <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-content">{{ option.name }}</view>
-      </view>
-      <view class="question-option" :class="{ 'question-option-not-know': question.isNotKnow }" @click="handleNotKnow">
-        <view class="question-option-index">
-          <uv-icon name="info-circle" :color="question.isNotKnow ? '#31A0FC' : '#999'" size="18" />
+      <view v-if="question.subQuestions.length === 0">
+        <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-content">
+            <uv-parse :content="option.name"></uv-parse>
+          </view>
         </view>
-        <view class="question-option-content text-fore-light">不会</view>
+        <view class="question-option" :class="{ 'question-option-not-know': question.isNotKnow }"
+          @click="handleNotKnow">
+          <view class="question-option-index">
+            <uv-icon name="info-circle" :color="question.isNotKnow ? '#31A0FC' : '#999'" size="18" />
+          </view>
+          <view class="question-option-content text-fore-light">不会</view>
+        </view>
+      </view>
+      <view v-else class="">
+        <question-item :question="subQuestion" v-for="subQuestion in question.subQuestions" :key="subQuestion.id" />
       </view>
     </view>
   </view>
@@ -38,27 +48,38 @@ const handleNotKnow = () => {
 }
 const handleSelect = (option: Study.QuestionOption) => {
   console.log('handleSelect', option)
-  if (props.question.type === EnumQuestionType.SINGLE_CHOICE) {
-    props.question.answer.push(option.no);
+  if ([
+    EnumQuestionType.JUDGMENT,
+    EnumQuestionType.SINGLE_CHOICE,
+    EnumQuestionType.SUBJECTIVE,
+    EnumQuestionType.SHORT_ANSWER,
+    EnumQuestionType.ESSAY,
+    EnumQuestionType.ANALYSIS
+  ].includes(props.question.typeId)) {
+    props.question.answer = [option.no];
+    // props.question.answer.push(option.no);
     nextQuestion?.();
-  } else if (props.question.type === EnumQuestionType.MULTIPLE_CHOICE) {
+  } else if (props.question.typeId === EnumQuestionType.MULTIPLE_CHOICE) {
     if (props.question.answer.includes(option.no)) {
       props.question.answer = props.question.answer.filter(item => item !== option.no);
     } else {
       props.question.answer.push(option.no);
     }
-  } else if (props.question.type === EnumQuestionType.JUDGMENT) {
-    props.question.answer.push(option.no);
   }
   // props.question.answer = option.no;
 }
 const isSelected = (option: Study.QuestionOption) => {
-  const { type, answer } = props.question;
-  if (type === EnumQuestionType.SINGLE_CHOICE) {
+  const { typeId, answer } = props.question;
+  if ([
+    EnumQuestionType.JUDGMENT,
+    EnumQuestionType.SINGLE_CHOICE,
+    EnumQuestionType.SUBJECTIVE,
+    EnumQuestionType.SHORT_ANSWER,
+    EnumQuestionType.ESSAY,
+    EnumQuestionType.ANALYSIS
+  ].includes(typeId)) {
     return answer.includes(option.no);
-  } else if (type === EnumQuestionType.MULTIPLE_CHOICE) {
-    return answer.includes(option.no);
-  } else if (type === EnumQuestionType.JUDGMENT) {
+  } else if (typeId === EnumQuestionType.MULTIPLE_CHOICE) {
     return answer.includes(option.no);
   }
   return false;
@@ -91,11 +112,14 @@ const isSelected = (option: Study.QuestionOption) => {
         @apply text-30 text-fore-title ml-20;
       }
     }
+
     .question-option-selected {
       @apply bg-[#b5eaff8e];
+
       .question-option-index {
         @apply bg-primary text-white;
       }
+
       .question-option-content {
         @apply text-30 text-primary;
       }

+ 110 - 50
src/pagesStudy/pages/start-exam/start-exam.vue

@@ -1,14 +1,14 @@
 <template>
   <ie-page :fix-height="true" :safe-area-inset-bottom="false">
     <ie-navbar :title="pageTitle" custom-back @left-click="handleLeftClick">
-      <template #headerRight>
+      <template v-if="isReady" #headerRight>
         <view v-if="isExamMode" class="countdown-text" :class="{ 'text-red-500': examDuration < 30 }">
           {{ formatExamDuration }}
         </view>
         <view v-else class="">{{ formatPracticeDuration }}</view>
       </template>
     </ie-navbar>
-    <view class="px-20 py-14 bg-back flex justify-between items-center gap-x-20">
+    <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>/
@@ -26,7 +26,7 @@
         </swiper>
       </view>
     </view>
-    <ie-safe-toolbar :height="64" :shadow="false">
+    <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" @click="handleFavorite">
           <uv-icon v-if="currentQuestion.isFavorite" name="star-fill" color="#FF9A18" size="27" />
@@ -56,17 +56,20 @@
         <scroll-view class="h-full" scroll-y>
           <view v-for="(item, i) in stateQuestionList" :key="i" class="">
             <template v-if="item.list.length > 0">
-              <view class="h-60 bg-back px-20 leading-60 text-fore-subcontent">{{ questionTypeDesc[item.type] }}</view>
-              <view class="grid grid-cols-5 place-items-center gap-x-20 gap-y-20">
-                <view v-for="(qs, j) in item.list" :key="j" class="py-20 flex items-center justify-center">
+              <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 v-for="(qs, j) in item.list" :key="j" class="aspect-square flex items-center justify-center"
+                  @click="hanadleNavigate(qs.index)">
                   <view
-                    class="w-52 h-52 rounded-full flex items-center justify-center bg-white border border-solid border-border"
+                    class="w-74 h-74 rounded-full flex items-center justify-center bg-white border border-solid border-border relative"
                     :class="{
                       'is-done': qs.question.isDone,
                       'is-not-know': qs.question.isNotKnow,
                       'is-mark': qs.question.isMark
                     }">
-                    {{ qs.index + 1 }}
+                    <text>{{ qs.index + 1 }}</text>
+                    <ie-image v-if="qs.question.isMark" src="/pagesStudy/static/image/icon-mark-active.png"
+                      custom-class="absolute -top-20 right-16 w-32 h-32" mode="aspectFill" />
                   </view>
                 </view>
 
@@ -77,11 +80,11 @@
         </scroll-view>
       </view>
       <view class="h-150 bg-white flex items-center gap-x-120 px-40">
-        <view class="flex flex-col items-center gap-x-10">
-          <uv-icon name="reload" size="20" />
-          <text class="mt-4 text-20 text-subcontent">重新作答</text>
+        <view class="flex flex-col items-center gap-x-10" @click="handleReset">
+          <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>
         </view>
-        <view class="flex-1 py-20 text-center rounded-full bg-primary text-white">交卷</view>
+        <view class="flex-1 py-20 text-center rounded-full bg-primary text-white" @click="beforeSubmit">交卷</view>
       </view>
     </view>
   </question-stats-popup>
@@ -92,16 +95,17 @@ import QuestionItem from './components/question-item.vue';
 import QuestionStatsPopup from './components/question-stats-popup.vue';
 import { useTransferPage } from '@/hooks/useTransferPage';
 import { EnumExamMode, EnumQuestionType } from '@/common/enum';
-import { getOpenExaminee } from '@/api/modules/study';
+import { getOpenExaminee, getPaper, commitExamineePaper } from '@/api/modules/study';
 import { useExam } from '@/composables/useExam';
 import { Study } from '@/types';
 import { NEXT_QUESTION, PREV_QUESTION, NEXT_QUESTION_QUICKLY, PREV_QUESTION_QUICKLY } from '@/types/injectionSymbols';
-const { prevData } = useTransferPage();
-const { questionList, stateQuestionList, questionTypeDesc, favoriteList, notKnowList, markList, currentIndex,
+import { ExamPaper, ExamPaperSubmit } from '@/types/study';
+const { prevData, transferBack } = useTransferPage();
+const { setQuestionList, questionList, stateQuestionList, questionTypeDesc, favoriteList, notKnowList, markList, currentIndex,
   totalCount, doneCount, notDoneCount, notKnowCount, markCount,
   loadExamData, nextQuestion, prevQuestion, nextQuestionQuickly, prevQuestionQuickly, swiperDuration,
   formatPracticeDuration, formatExamDuration, practiceDuration, startPracticeDuration, stopPracticeDuration,
-  examDuration, startExamDuration, stopExamDuration, setExamDuration, setCountDownCallback } = useExam();
+  examDuration, startExamDuration, stopExamDuration, setExamDuration, setCountDownCallback, changeIndex, reset } = useExam();
 
 provide(NEXT_QUESTION, nextQuestion);
 provide(PREV_QUESTION, prevQuestion);
@@ -111,6 +115,8 @@ provide(PREV_QUESTION_QUICKLY, prevQuestionQuickly);
 const isAnimationFinish = ref(false);
 const transitionStartX = ref(null);
 const transitionEndX = ref(null);
+const isReady = ref(false);
+const paperData = ref<ExamPaper>({} as ExamPaper);
 const pageTitle = computed(() => {
   const { mode } = prevData.value;
   return mode === EnumExamMode.PRACTICE ? '练习' : '考试';
@@ -123,6 +129,10 @@ const isExamMode = computed(() => {
   return prevData.value.mode === EnumExamMode.EXAM;
 });
 const handleLeftClick = () => {
+  if (!isReady.value) {
+    transferBack();
+    return;
+  }
   beforeQuit();
 };
 const handleSwiperChange = (e: any) => {
@@ -152,19 +162,28 @@ const beforeQuitPractice = () => {
   });
 };
 const beforeQuitExam = () => {
+  if (!isReady.value) {
+    return;
+  }
   uni.$ie.showModal({
     title: '提示',
     content: '当前考试未完成,确认退出?',
   }).then(confirm => {
     if (confirm) {
-      uni.navigateBack();
+      handleSubmit();
+      // uni.navigateBack();
     }
   });
 };
 const currentQuestion = computed(() => {
   console.log(questionList.value[currentIndex.value])
-  return questionList.value[currentIndex.value];
+  return questionList.value[currentIndex.value] || {};
 });
+const hanadleNavigate = (index: number) => {
+  // console.log('hanadleNavigate', qs)
+  // const index = questionList.value.findIndex(q => q.id === qs.id);
+  changeIndex(index);
+}
 const handleFavorite = () => {
   console.log('handleFavorite')
   currentQuestion.value.isFavorite = !currentQuestion.value.isFavorite;
@@ -189,6 +208,7 @@ const handleSwiperTransition = (e: any) => {
     return;
   }
 };
+
 const startTime = () => {
   if (isExamMode.value) {
     startExamDuration();
@@ -196,6 +216,7 @@ const startTime = () => {
     startPracticeDuration();
   }
 }
+
 const stopTime = () => {
   if (isExamMode.value) {
     stopExamDuration();
@@ -203,6 +224,7 @@ const stopTime = () => {
     stopPracticeDuration();
   }
 }
+
 const handleSwiperAnimationFinish = (e: any) => {
   if (transitionStartX.value == null || transitionEndX.value == null || currentIndex.value !== questionList.value.length - 1) {
     isAnimationFinish.value = true;
@@ -212,57 +234,95 @@ const handleSwiperAnimationFinish = (e: any) => {
   }
   const offsetX = transitionEndX.value - transitionStartX.value;
   if (offsetX < 0 && offsetX > -150) {
-    const text = notDoneCount.value > 0 ? `还有${notDoneCount.value}题未做,确认交卷?` : '是否确认交卷?';
-    stopTime();
-    uni.$ie.showModal({
-      title: '提示',
-      content: text,
-    }).then(confirm => {
-      if (confirm) {
-        // uni.navigateBack();
-        handleSubmit();
-      } else {
-        startTime();
-      }
-    });
+    beforeSubmit();
   }
   isAnimationFinish.value = true;
   transitionStartX.value = null;
   transitionEndX.value = null;
 };
+
+const beforeSubmit = () => {
+  const text = notDoneCount.value > 0 ? `还有${notDoneCount.value}题未做,确认交卷?` : '是否确认交卷?';
+  stopTime();
+  uni.$ie.showModal({
+    title: '提示',
+    content: text,
+  }).then(confirm => {
+    if (confirm) {
+      // uni.navigateBack();
+      handleSubmit();
+    } else {
+      startTime();
+    }
+  });
+}
+
+const handleReset = () => {
+  if (doneCount.value <= 0) {
+    return;
+  }
+  uni.$ie.showModal({
+    title: '重新作答',
+    content: '是否确认清空全部作答数据?',
+  }).then(confirm => {
+    if (confirm) {
+      questionStatsPopupRef.value.close();
+      reset();
+      setTimeout(() => {
+        startTime();
+      }, 300);
+    }
+  });
+}
+
 const handleSubmit = () => {
+  console.log('handleSubmit', questionList.value)
   uni.$ie.showLoading('保存中...');
   setTimeout(() => {
     uni.$ie.hideLoading();
+    const params = {
+      ...paperData.value,
+      questions: questionList.value,
+    } as ExamPaperSubmit;
+    if (!isExamMode.value) {
+      params.duration = practiceDuration.value;
+    }
+    console.log(params);
+    commitExamineePaper(params);
     uni.navigateBack();
   }, 1000);
   console.log('handleSubmit')
 }
+
 const loadData = async () => {
-  // const { data } = await getOpenExaminee({
-  //   paperType: prevData.value.paperType,
-  //   relateId: prevData.value.relateId
-  // });
-  // console.log(data)
-  // 构造模拟数据
-  console.log(questionList.value)
-  loadExamData();
-  // setExamDuration(35)
+  uni.$ie.showLoading();
+  const { data } = await getOpenExaminee({
+    paperType: prevData.value.paperType,
+    relateId: prevData.value.relateId
+  });
+  if (!data) {
+    uni.$ie.hideLoading();
+    transferBack();
+    return;
+  }
+  if (data.paperId) {
+    const res = await getPaper({
+      type: prevData.value.paperType,
+      id: data.paperId
+    });
+    uni.$ie.hideLoading();
+    paperData.value = res.data;
+    setQuestionList(paperData.value.questions)
+    // console.log(123, paperData)
+    // loadExamData(prevData.value.paperType, data.paperId);
+  }
+  isReady.value = true;
   setCountDownCallback(() => {
-    uni.$ie.showToast('考试结束');
-    // uni.navigateBack();
+    handleSubmit();
   });
   startTime();
-  // startExamDuration();
 };
-onMounted(() => {
-  setTimeout(() => {
-    console.log(stateQuestionList.value)
-    handleCalendar();
-  }, 500);
-});
 onLoad(() => {
-  console.log(prevData.value)
   loadData();
 });
 </script>

+ 47 - 3
src/types/study.ts

@@ -82,10 +82,50 @@ export interface QuestionState {
   isFavorite?: boolean;
 }
 
-export interface Question extends QuestionState {
+/**
+ * 试卷
+ */
+export interface ExamPaper {
+  id: number;
+  paperName: string;
+  paperType: string;
+  questions: ApiQuestion[];
+  score?: number;
+  subjectId: number;
+  year: number;
+}
+
+export interface ApiQuestion extends QuestionState {
   id: number;
+  title: string;
+  typeId: number;
+  options: string[];
+  answer: (string | number)[];
+  subQuestions: ApiQuestion[];
+}
+
+export interface ExamPaperSubmit {
+  id: number;
+  paperName: string;
+  paperType: string;
+  questions: Question[];
+  score?: number;
+  subjectId: number;
+  year: number;
+  duration?: number;
+}
+
+export interface QuestionOption {
+  id: number;
+  no: string | number; // A, B, C, D
   name: string;
-  type: number;
+  isAnswer: boolean;
+}
+
+export interface Question extends QuestionState {
+  id: number;
+  title: string;
+  typeId: number;
   options: QuestionOption[];
   answer: (string | number)[];
   subQuestions: Question[];
@@ -96,4 +136,8 @@ export interface QuestionOption {
   no: string | number; // A, B, C, D
   name: string;
   isAnswer: boolean;
-}
+}
+
+export function QuestionOption(arg0: (option: string, index: number) => { name: string; no: string; id: number; isAnswer: false; }, as: any, QuestionOption: any): QuestionOption[] {
+  throw new Error("Function not implemented.");
+}