Browse Source

fix:修复多选题取消勾选时正常提交后端

liuzhenlin 2 weeks ago
parent
commit
e3a1d68893
3 changed files with 110 additions and 23 deletions
  1. 20 0
      components.d.ts
  2. 89 23
      src/view/exam/index.vue
  3. 1 0
      vite.config.ts

+ 20 - 0
components.d.ts

@@ -16,25 +16,45 @@ declare module 'vue' {
     VanActionBar: typeof import('vant/es')['ActionBar']
     VanActionBarButton: typeof import('vant/es')['ActionBarButton']
     VanActionBarIcon: typeof import('vant/es')['ActionBarIcon']
+    VanBackTop: typeof import('vant/es')['BackTop']
     VanButton: typeof import('vant/es')['Button']
+    VanCalendar: typeof import('vant/es')['Calendar']
     VanCell: typeof import('vant/es')['Cell']
     VanCellGroup: typeof import('vant/es')['CellGroup']
     VanCheckbox: typeof import('vant/es')['Checkbox']
     VanCheckboxGroup: typeof import('vant/es')['CheckboxGroup']
     VanCol: typeof import('vant/es')['Col']
+    VanCollapse: typeof import('vant/es')['Collapse']
+    VanCollapseItem: typeof import('vant/es')['CollapseItem']
+    VanDatePicker: typeof import('vant/es')['DatePicker']
     VanDialog: typeof import('vant/es')['Dialog']
+    VanEmpty: typeof import('vant/es')['Empty']
     VanField: typeof import('vant/es')['Field']
+    VanFloatingBubble: typeof import('vant/es')['FloatingBubble']
+    VanForm: typeof import('vant/es')['Form']
     VanIcon: typeof import('vant/es')['Icon']
+    VanImage: typeof import('vant/es')['Image']
     VanList: typeof import('vant/es')['List']
     VanNotify: typeof import('vant/es')['Notify']
+    VanPicker: typeof import('vant/es')['Picker']
+    VanPickerGroup: typeof import('vant/es')['PickerGroup']
+    VanPopup: typeof import('vant/es')['Popup']
     VanRadio: typeof import('vant/es')['Radio']
     VanRadioGroup: typeof import('vant/es')['RadioGroup']
     VanRow: typeof import('vant/es')['Row']
+    VanSearch: typeof import('vant/es')['Search']
+    VanStep: typeof import('vant/es')['Step']
+    VanStepper: typeof import('vant/es')['Stepper']
+    VanSteps: typeof import('vant/es')['Steps']
     VanSwipe: typeof import('vant/es')['Swipe']
     VanSwipeItem: typeof import('vant/es')['SwipeItem']
+    VanTab: typeof import('vant/es')['Tab']
     VanTabbar: typeof import('vant/es')['Tabbar']
     VanTabbarItem: typeof import('vant/es')['TabbarItem']
+    VanTabs: typeof import('vant/es')['Tabs']
     VanTag: typeof import('vant/es')['Tag']
     VanTextEllipsis: typeof import('vant/es')['TextEllipsis']
+    VanTimePicker: typeof import('vant/es')['TimePicker']
+    VanUploader: typeof import('vant/es')['Uploader']
   }
 }

+ 89 - 23
src/view/exam/index.vue

@@ -83,10 +83,10 @@
     <div class="btns">
       <van-row gutter="10">
         <van-col span="12">
-          <van-button :disabled="curQuNo <= 0" class="w100" @click="handlePre">上一题</van-button>
+          <van-button :disabled="isPreDisabled" :loading="isSubmitting" class="w100" @click="handlePre">上一题</van-button>
         </van-col>
         <van-col span="12">
-          <van-button :disabled="curQuNo == sortQuList.length - 1" class="w100" @click="handleNext">下一题</van-button>
+          <van-button :disabled="isNextDisabled" :loading="isSubmitting" class="w100" @click="handleNext">下一题</van-button>
         </van-col>
       </van-row>
       <!-- <van-row gutter="10" class="mt10">
@@ -95,7 +95,7 @@
     </div>
   </div>
   <van-action-bar placeholder>
-    <van-action-bar-button type="primary" text="交卷" @click="handleSubmitAllAnswer(false)" />
+    <van-action-bar-button type="primary" text="交卷" :disabled="isSubmitDisabled" :loading="isSubmitting" @click="handleSubmitAllAnswer(false)" />
   </van-action-bar>
 </template>
 
@@ -129,18 +129,68 @@
   const checkboxRefs = ref([])
   const debounceTimer = ref(0)
   const isClearingAnswer = ref(false)
-  const toggle = (index) => {
-    checkboxRefs.value[index].toggle()
+  const hasUnsavedChanges = ref(false)
+  const activeRequestCount = ref(0)
+  const isSubmitting = computed(() => activeRequestCount.value > 0)
+
+  // 检查题目是否已经回答过
+  const isQuestionAnswered = (qu: any) => {
+    if (!qu) return false
+    const quType = String(qu.quType)
+    if (quType === '1' || quType === '3') {
+      return !!(qu.isCorrect && String(qu.isCorrect).trim() !== '')
+    } else if (quType === '2') {
+      return Array.isArray(qu.isCorrect) && qu.isCorrect.filter((x: any) => x && String(x).trim() !== '').length > 0
+    } else if (quType === '4') {
+      return Array.isArray(qu.quContent) && qu.quContent.some((item: any) => item.content && String(item.content).trim() !== '')
+    } else if (quType === '5') {
+      return !!(qu.answer && String(qu.answer).trim() !== '')
+    }
+    return false
+  }
+
+  // 上一题按钮禁用条件:第一题,或者正在提交中,或者有未保存的改动
+  const isPreDisabled = computed(() => {
+    return curQuNo.value <= 0 || isSubmitting.value || hasUnsavedChanges.value
+  })
+
+  // 下一题按钮禁用条件:最后一题,或者正在提交中,或者有未保存改动,或者当前题目尚未选择/保存答案
+  const isNextDisabled = computed(() => {
+    if (curQuNo.value >= sortQuList.value.length - 1) return true
+    if (isSubmitting.value) return true
+    if (hasUnsavedChanges.value) return true
+    if (!isQuestionAnswered(currentNode.value)) return true
+    return false
+  })
+
+  // 交卷按钮禁用条件:必须到最后一个题目,且不能在提交中/有未保存改动/未完成最后一题
+  const isSubmitDisabled = computed(() => {
+    if (sortQuList.value.length === 0) return true
+    if (curQuNo.value !== sortQuList.value.length - 1) return true
+    if (isSubmitting.value) return true
+    if (hasUnsavedChanges.value) return true
+    if (!isQuestionAnswered(currentNode.value)) return true
+    return false
+  })
+
+  const toggle = (index: number) => {
+    if (checkboxRefs.value && checkboxRefs.value[index]) {
+      (checkboxRefs.value[index] as any).toggle()
+    }
   }
   
   // 处理答案变化,立即提交到后端
   const handleAnswerChange = () => {
     if (isClearingAnswer.value) return
-    // 使用防抖,避免频繁调用接口
+    hasUnsavedChanges.value = true
+    const nodeToSubmit = currentNode.value
     clearTimeout(debounceTimer.value)
+
+    const quType = String(nodeToSubmit.quType)
+    const delay = (quType === '4' || quType === '5') ? 500 : 100
     debounceTimer.value = setTimeout(() => {
-      submitAnswer()
-    }, 500)
+      submitAnswer(nodeToSubmit)
+    }, delay) as any
   }
   // 答题失败时清理当前题已输入答案
   const clearQuestionAnswer = (question: any) => {
@@ -199,6 +249,7 @@
         quTypeVal: quTypeList[item.quType],
         quScore: item.quScore,
         answer: item.quType == 5 ? '' : [],
+        isCorrect: item.quType == 2 ? [] : '',
         quContent: item.quContent
       })
     })
@@ -231,8 +282,9 @@
             val.isCorrect = item.answer
           } else {
             // 多选 判断
-            val.answer = item.answer.split(' ')
-            val.isCorrect = item.answer.split(' ')
+            let validAnswers = item.answer ? item.answer.split(' ').filter((x: any) => x && x.trim() !== '') : []
+            val.answer = validAnswers
+            val.isCorrect = validAnswers
           }
         }
       })
@@ -279,8 +331,8 @@
   const onClickRight = () => {
     router.go(-1)
   }
-  const submitAnswer = async () => {
-    let curAnswer = currentNode.value
+  const submitAnswer = async (node = currentNode.value) => {
+    let curAnswer = node
     let params = {
       epId: epId,
       answerId: answerId,
@@ -292,40 +344,54 @@
       // 填空题
       params['fillAnswer'] = curAnswer.quContent.map((item: any) => ({
         name: setFillNo(item.name),
-        answer: item.content
+        answer: item.content || ''
       }))
     } else if (curAnswer.quType == '5') {
       // 问答题
-      params['answer'] = curAnswer.answer
+      params['answer'] = curAnswer.answer || ''
     } else if (curAnswer.quType == '1' || curAnswer.quType == '3') {
       // 单选
       params['answer'] = curAnswer.isCorrect || ''
     } else {
       // 多选
-      params['answer'] = curAnswer.isCorrect.join(' ')
+      params['answer'] = Array.isArray(curAnswer.isCorrect) ? curAnswer.isCorrect.filter((x: any) => x && String(x).trim() !== '').join(' ') : ''
     }
-    if (params.answer.length || params.fillAnswer.length) {
-      const [err]: ToResponse = await to(trainingApi.submitDocAmswer(params))
-      if (err) {
-        clearQuestionAnswer(curAnswer)
-      }
+
+    activeRequestCount.value++
+    const [err]: ToResponse = await to(trainingApi.submitDocAmswer(params))
+    activeRequestCount.value--
+    if (err) {
+      clearQuestionAnswer(curAnswer)
+      return false
     }
+    hasUnsavedChanges.value = false
+    return true
   }
   const handlePre = async () => {
-    // 不再重复提交答案,因为选择答案时已经实时提交了
+    if (isPreDisabled.value) return
     if (curQuNo.value >= 1) {
       curQuNo.value--
+      checkboxRefs.value = []
       currentNode.value = sortQuList.value[curQuNo.value]
     }
   }
   const handleNext = async () => {
-    // 不再重复提交答案,因为选择答案时已经实时提交了
-    if (curQuNo.value < sortQuList.value.length) {
+    if (isNextDisabled.value) return
+    if (curQuNo.value < sortQuList.value.length - 1) {
       curQuNo.value++
+      checkboxRefs.value = []
       currentNode.value = sortQuList.value[curQuNo.value]
     }
   }
   const handleSubmitAllAnswer = async (auto: any) => {
+    if (!auto && isSubmitDisabled.value) return
+
+    if (hasUnsavedChanges.value) {
+      clearTimeout(debounceTimer.value)
+      const success = await submitAnswer(currentNode.value)
+      if (!success && !auto) return // 手动交卷如果当前题目提交失败,阻断
+    }
+
     let params = {
       answerId: answerId,
       auto: true

+ 1 - 0
vite.config.ts

@@ -29,6 +29,7 @@ export default defineConfig({
     },
   },
   server: {
+    host: '0.0.0.0',
     port: 4200,
     https: false,
     allowedHosts: ['4ik677er1300.vicp.fun'],