浏览代码

优化子题逻辑,新增自动滚动和做题进度展示

shmily1213 1 月之前
父节点
当前提交
b64d8ac50d

+ 10 - 2
src/composables/useExam.ts

@@ -73,11 +73,12 @@ export const useExam = () => {
         }[]
       }
     });
-    for (let i = 0; i <= questionList.value.length - 1; i++) {
+    for (let i = 0; i <= stateQuestionList.value.length - 1; i++) {
       const qs = questionList.value[i];
       state.forEach(item => {
+        qs.progress = calcProgress(qs);
         if (qs.typeId === item.type) {
-          qs.isDone = isDone(qs);
+          // qs.isDone = isDone(qs);
           item.list.push({
             question: qs,
             index: i
@@ -112,6 +113,13 @@ export const useExam = () => {
     // return groupedQuestionList.value.reduce((acc, item) => acc + item.list.filter(q => q.question.isMark).length, 0);
     return stateQuestionList.value.filter(q => q.isMark).length;
   });
+  // 包含子题的题目计算整体做题进度
+  const calcProgress = (qs: Study.Question): number => {
+    if (qs.subQuestions && qs.subQuestions.length > 0) {
+      return qs.subQuestions.reduce((acc, q) => acc + calcProgress(q), 0) / qs.subQuestions.length;
+    }
+    return qs.isDone ? 100 : 0;
+  }
   const isDone = (qs: Study.Question): boolean => {
     if (qs.subQuestions && qs.subQuestions.length > 0) {
       return qs.subQuestions.every(q => isDone(q));

+ 42 - 14
src/pagesStudy/pages/start-exam/components/question-item.vue

@@ -1,11 +1,11 @@
 <template>
-  <view class="question-item">
+  <view class="question-item" :id="`qs_${question.id}`">
     <view class="is-main-question">
       <view v-if="question.typeId && !isSubQuestion" class="question-type">
         {{ questionTypeDesc[question.typeId as EnumQuestionType] }}
       </view>
       <view class="question-content" :class="{ 'mt-30': isSubQuestion }">
-        <uv-parse :content="getQuestionTitle"></uv-parse>
+        <uv-parse :content="getQuestionTitle()"></uv-parse>
       </view>
       <view class="question-options">
         <view class="question-option" v-for="option in question.options"
@@ -25,8 +25,8 @@
       </view>
     </view>
     <view v-if="question.subQuestions.length" class="is-sub-question">
-      <question-item :question="subQuestion" v-for="subQuestion in question.subQuestions" :key="subQuestion.id"
-        :is-sub-question="true" @update:question="emit('update:question', $event)" @select="handleSelectOption"
+      <question-item :question="subQuestion" 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" />
     </view>
   </view>
@@ -41,6 +41,8 @@ const { questionTypeDesc } = useExam();
 const props = defineProps<{
   question: Study.Question;
   isSubQuestion?: boolean;
+  index?: number;
+  total?: number;
 }>();
 const nextQuestion = inject(NEXT_QUESTION);
 const prevQuestion = inject(PREV_QUESTION);
@@ -50,13 +52,17 @@ const emit = defineEmits<{
   (e: 'update:question', question: Study.Question): void;
   (e: 'select', question: Study.Question): void;
   (e: 'notKnow', question: Study.Question): void;
+  (e: 'scrollTo', selector: string): void;
 }>();
-const getQuestionTitle = computed(() => {
+
+const getQuestionTitle = () => {
   if (props.isSubQuestion) {
-    return `[${questionTypeDesc[props.question.typeId as EnumQuestionType]}] ${props.question.title}`;
+    const prefix = questionTypeDesc[props.question.typeId as EnumQuestionType].slice(0, 2);
+    const qsOrder = props.total && props.total > 1 ? `${props.index! + 1}.` : '';
+    return `[${prefix}] ${qsOrder} ${props.question.title}`;
   }
   return props.question.title;
-});
+};
 
 const handleNotKnow = () => {
   // console.log('handleNotKnow')
@@ -66,10 +72,10 @@ const handleNotKnow = () => {
   emit('update:question', props.question);
   // emit('notKnow', props.question);
   // nextQuestion?.();
-  handleSelectNotKnow();
+  // handleSelectNotKnow();
+  emit('select', props.question);
 }
 const handleSelect = (option: Study.QuestionOption) => {
-  // console.log('handleSelect', isDone.value)
   if ([
     EnumQuestionType.JUDGMENT,
     EnumQuestionType.SINGLE_CHOICE,
@@ -96,21 +102,43 @@ const handleSelect = (option: Study.QuestionOption) => {
   if (props.isSubQuestion) {
     emit('select', props.question);
   } else {
-    handleSelectOption();
+    // 没有子题的,直接切换到下一题
+    changeQuestion();
   }
 }
+const handleSubQuestionUpdate = (question: Study.Question) => {
+  emit('update:question', question);
+  checkIsDone();
+}
 const checkIsDone = () => {
   if (props.question.subQuestions.length > 0) {
     props.question.isDone = props.question.subQuestions.every(q => q.answers.length > 0 || q.isNotKnow);
+  } else {
+    props.question.isDone = props.question.answers.length > 0 || props.question.isNotKnow;
   }
-  props.question.isDone = props.question.answers.length > 0 || props.question.isNotKnow;
 }
+// 子题选中方法
 const handleSelectOption = () => {
-  if (props.question.isDone) {
-    nextQuestion?.();
-  }
+  // changeQuestion();
+  findNotDoneSubQuestion();
 }
 const handleSelectNotKnow = () => {
+  // changeQuestion();
+  findNotDoneSubQuestion();
+}
+// 查找是否有子题没有做,有的话就自动滚动到目标处
+const findNotDoneSubQuestion = () => {
+  const notDoneSubQuestion = props.question.subQuestions.find(q => !q.isDone);
+  if (notDoneSubQuestion) {
+    const selector = `qs_${notDoneSubQuestion.id}`;
+    // 通知父组件滚动
+    emit('scrollTo', selector);
+  } else {
+    // 否则切换到下一题
+    changeQuestion();
+  }
+}
+const changeQuestion = () => {
   if (props.question.isDone) {
     nextQuestion?.();
   }

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

@@ -0,0 +1,24 @@
+<!-- background = `conic-gradient(#FF6384 0% ${val}%, #36A2EB ${val}% 100%)`; -->
+<template>
+  <view class="question-progress" :style="getStyle"></view>
+</template>
+<script lang="ts" setup>
+type ProgressOptions = {
+  progress: number;
+  bgColor?: string;
+  progressColor?: string;
+}
+const props = withDefaults(defineProps<ProgressOptions>(), {
+  progress: 0,
+  bgColor: '#FFFFFF',
+  progressColor: '#EBF9FF'
+});
+const getStyle = computed(() => {
+  return `background: conic-gradient(${props.progressColor} 0% ${props.progress}%, ${props.bgColor} ${props.progress}% 100%)`;
+});
+</script>
+<style lang="scss" scoped>
+.question-progress {
+  @apply w-full h-full rounded-full absolute inset-0 z-0;
+}
+</style>

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

@@ -1,7 +1,7 @@
 <template>
-  <scroll-view scroll-y class="question-wrap">
+  <scroll-view scroll-y class="question-wrap" :scroll-into-view="scrollIntoView" :scroll-with-animation="true">
     <view class="h-20"></view>
-    <question-item :question="question" @update:question="emit('update:question', $event)" />
+    <question-item :question="question" @update:question="emit('update:question', $event)" @scrollTo="handleScrollTo" />
     <view class="h-20"></view>
   </scroll-view>
 </template>
@@ -15,6 +15,16 @@ const props = defineProps<{
 const emit = defineEmits<{
   (e: 'update:question', question: Study.Question): void;
 }>();
+const scrollIntoView = ref('');
+const handleScrollTo = (selector: string) => {
+  console.log('收到子组件滚动请求', selector)
+  scrollIntoView.value = '';
+  setTimeout(() => {
+    if (selector) {
+      scrollIntoView.value = selector;
+    }
+  }, 200);
+}
 
 </script>
 

+ 4 - 2
src/pagesStudy/pages/start-exam/start-exam.vue

@@ -67,9 +67,10 @@
                       'is-not-know': qs.question.isNotKnow,
                       'is-mark': qs.question.isMark
                     }">
-                    <text>{{ qs.index + 1 }}</text>
+                    <text class="z-1">{{ 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" />
+                      custom-class="absolute -top-12 left-14 w-28 h-28 z-1" mode="aspectFill" />
+                    <question-progress :progress="qs.question.progress || 0" />
                   </view>
                 </view>
 
@@ -94,6 +95,7 @@
 import QuestionItem from './components/question-item.vue';
 import QuestionWrap from './components/question-wrap.vue';
 import QuestionStatsPopup from './components/question-stats-popup.vue';
+import QuestionProgress from './components/question-progress.vue';
 import { useTransferPage } from '@/hooks/useTransferPage';
 import { EnumExamMode } from '@/common/enum';
 import { getOpenExaminee, getPaper, commitExamineePaper, collectQuestion, cancelCollectQuestion } from '@/api/modules/study';

+ 2 - 1
src/types/study.ts

@@ -105,10 +105,11 @@ export interface QuestionState {
   isMark?: boolean;
   isNotKnow?: boolean;
   isFavorite?: boolean;
+  progress?: number;
 }
 
 /**
- * 开卷信息
+ * 开卷信息,包含上次做题历史数据
  */
 export interface ExamineeQuestion {
   id: number;