Procházet zdrojové kódy

Merge remote-tracking branch 'origin/master'

Lambert před 1 týdnem
rodič
revize
76c5416c2e

+ 25 - 5
src/view/animal/application/components/Application.vue

@@ -152,7 +152,7 @@
           </van-form>
         </div>
         <div class="dialog-footer">
-          <van-button type="primary" @click="onSubmit" block native-type="submit">
+          <van-button type="primary" @click="onSubmit" block native-type="submit" :loading="submitting">
             提交
           </van-button>
         </div>
@@ -245,6 +245,7 @@ const ethicsAdviceFileList = ref<any[]>([])
 
 const safePromise = ref<boolean>(false)
 const animalTypeList = ref<any[]>([])
+const submitting = ref<boolean>(false) // 提交状态,用于控制按钮加载状态
 
 const defaultFormData = {
   projectGroupName: '',
@@ -457,6 +458,9 @@ const onComeTimeConfirm = (date: Date) => {
 
 // 提交
 const onSubmit = async () => {
+  // 设置提交状态为加载中
+  submitting.value = true
+  
   try {
     await expertDialogFormRef.value?.validate()
   } catch (error: any) {
@@ -477,6 +481,7 @@ const onSubmit = async () => {
       type: 'warning',
       message: errorMessage,
     })
+    submitting.value = false // 重置提交状态
     return
   }
 
@@ -485,6 +490,7 @@ const onSubmit = async () => {
       type: 'warning',
       message: '请阅读并勾选安全承诺!',
     })
+    submitting.value = false // 重置提交状态
     return
   }
 
@@ -493,6 +499,7 @@ const onSubmit = async () => {
       type: 'warning',
       message: '请输入雄性或雌性数量!',
     })
+    submitting.value = false // 重置提交状态
     return
   }
 
@@ -503,6 +510,7 @@ const onSubmit = async () => {
         type: 'warning',
         message: '请上传生产许可证副本!',
       })
+      submitting.value = false // 重置提交状态
       return
     }
     if (!state.form.animalTestDateFile.length) {
@@ -510,6 +518,7 @@ const onSubmit = async () => {
         type: 'warning',
         message: '请上传近三个月动物质量检测证明!',
       })
+      submitting.value = false // 重置提交状态
       return
     }
   }
@@ -520,6 +529,7 @@ const onSubmit = async () => {
       type: 'warning',
       message: '请上传实验动物福利伦理审查申请表!',
     })
+    submitting.value = false // 重置提交状态
     return
   }
   if (!state.form.ethicsAdviceFile.length) {
@@ -527,6 +537,7 @@ const onSubmit = async () => {
       type: 'warning',
       message: '请上传实验动物福利伦理审查意见表!',
     })
+    submitting.value = false // 重置提交状态
     return
   }
 
@@ -558,13 +569,22 @@ const onSubmit = async () => {
 
   const post = platAnimalCageApplicationApi.create
   const [err]: ToResponse = await to(post(params))
-  if (err) return
+  if (err) {
+    submitting.value = false // 重置提交状态
+    return
+  }
+  
   showToast({
     type: 'success',
     message: '操作成功',
   })
-  closeDialog()
-  emit('refresh')
+  
+  // 接口成功后继续保持加载状态2秒,提供更好的用户体验
+  setTimeout(() => {
+    closeDialog()
+    emit('refresh')
+    submitting.value = false // 2秒后重置提交状态
+  }, 2000)
 }
 
 watch(
@@ -674,4 +694,4 @@ defineExpose({
   top: 16px;
   right: 16px;
 }
-</style>
+</style>

+ 75 - 131
src/view/entry/add.vue

@@ -157,42 +157,13 @@
           type="primary"
           text="立即预约"
           native-type="submit"
+          :loading="submitting"
         />
       </van-action-bar>
     </van-form>
   </div>
 
-  <!-- <van-dialog v-model:show="noticeShow" :title="noticeInfo.noticeTitle" show-cancel-button>
-    <div class="ck-editor" v-html="noticeInfo.noticeContent"></div>
-  </van-dialog> -->
-
-  <van-popup
-    v-model:show="state.needToKnowShow"
-    round
-    :closeable="true"
-    position="bottom"
-    :style="{ height: '90vh' }"
-  >
-    <div class="need-to-know">
-      <h4 class="mt8 mb8">申请须知</h4>
-      <div
-        class="ck-editor"
-        v-html="noticeInfo.noticeContent"
-      ></div>
-      <footer>
-        <van-button
-          class="w100"
-          type="primary"
-          round
-          :disabled="confirmDisabled"
-          :loading="confirmDisabled"
-          @click="confirmAppoint"
-        >
-          我知道了
-        </van-button>
-      </footer>
-    </div>
-  </van-popup>
+  <!-- 申请须知弹窗已移动到跳转页面 -->
 </template>
 
 <script name="home" lang="ts" setup>
@@ -221,11 +192,9 @@
   const userTypeList = ref(<RowDicDataType[]>[])
   const platformApi = usePlatformApi()
   const platformList = ref()
-  const confirmDisabled = ref(false)
+  // confirmDisabled变量已移除,申请须知已移动到跳转页面
   const state = reactive({
     safePromise: false,
-    safeRead: false,
-    isRead: false,
     form: {
       id: 0,
       deptId: null,
@@ -253,17 +222,11 @@
       isMolecularChecked: '20',
       molecularTime: null,
       platOtherNeed: '',
-    },
-    needToKnowShow: false,
-  })
-  const noticeShow = ref(false)
-  const noticeInfo = reactive({
-    noticeTitle: '',
-    noticeContent: '',
+    }
   })
-  const createEntryPayload = ref({})
-
   const selectPlatform = ref('') // 10: 细胞 20: 分子
+  const debounceTimer = ref(0) // 防抖计时器
+  const submitting = ref<boolean>(false) // 提交状态,用于控制按钮加载状态
 
   watch(selectPlatform, (newVal) => {
     if (newVal === '10' || newVal === '20') {
@@ -274,22 +237,7 @@
     }
   })
 
-  const confirmAppoint = async () => {
-    confirmDisabled.value = true
-    const [err]: ToResponse = await to(platformAppointApi.create(createEntryPayload.value))
-    confirmDisabled.value = false
-    if (err) {
-      state.needToKnowShow = false
-      return
-    }
-    state.needToKnowShow = false
-    showNotify({
-      type: 'success',
-      message: '入室申请创建成功',
-    })
-
-    router.push('/entry')
-  }
+  // confirmAppoint方法已移除,申请须知已移动到跳转页面
 
   const getDicts = async () => {
     await Promise.all([
@@ -367,65 +315,82 @@
       })
     }
   }
-  const onRead = () => {
-    noticeShow.value = true
-    noticeInfo.noticeTitle = '预约须知'
-    const arr = state.form.platformList.map((item: any) => {
-      return item.isChecked ? item.platformDesc : ''
-    })
-    if (arr.length) {
-      noticeInfo.noticeContent = arr.join('\n')
-      state.isRead = true
-    }
-  }
+  // onRead方法已移除,申请须知已移动到跳转页面
   const onSubmit = async (type: string) => {
-    onRead()
-    if (!state.safePromise) {
-      showNotify({
-        type: 'warning',
-        message: '请阅读并勾选安全承诺!',
-      })
-      return
+    // 设置提交状态为加载中
+    submitting.value = true
+    
+    // 防抖处理:如果已有计时器,清除并重新设置
+    if (debounceTimer.value) {
+      clearTimeout(debounceTimer.value)
     }
-    const [errValid] = await to(formRef.value.validate())
-    if (errValid) return
-    const params = JSON.parse(JSON.stringify(state.form))
-    params.isTemporary = type
-    const arr = params.platformList.filter((item: any) => item.isChecked)
-    params.platformList = arr.map((item: any) => {
-      return {
-        ...item,
-        isChecked: '10',
+    
+    // 设置新的防抖计时器,500ms后执行
+    debounceTimer.value = setTimeout(async () => {
+      if (!state.safePromise) {
+        showNotify({
+          type: 'warning',
+          message: '请阅读并勾选安全承诺!',
+        })
+        debounceTimer.value = 0
+        submitting.value = false // 重置提交状态
+        return
       }
-    })
-    if (!params.platformList.length) {
-      showNotify({
-        type: 'warning',
-        message: '请选择平台',
+      const [errValid] = await to(formRef.value.validate())
+      if (errValid) {
+        debounceTimer.value = 0
+        submitting.value = false // 重置提交状态
+        return
+      }
+      const params = JSON.parse(JSON.stringify(state.form))
+      params.isTemporary = type
+      const arr = params.platformList.filter((item: any) => item.isChecked)
+      params.platformList = arr.map((item: any) => {
+        return {
+          ...item,
+          isChecked: '10',
+        }
       })
-      return
-    }
-    for (const item of params.platformList) {
-      if (!item.platformTime) {
+      if (!params.platformList.length) {
         showNotify({
           type: 'warning',
-          message: '请选择平台预约时间',
+          message: '请选择平台',
         })
+        debounceTimer.value = 0
+        submitting.value = false // 重置提交状态
         return
       }
-    }
-
-    createEntryPayload.value = params
-
-    state.needToKnowShow = true
+      for (const item of params.platformList) {
+        if (!item.platformTime) {
+          showNotify({
+            type: 'warning',
+            message: '请选择平台预约时间',
+          })
+          debounceTimer.value = 0
+          submitting.value = false // 重置提交状态
+          return
+        }
+      }
 
-    // const [err]: ToResponse = await to(platformAppointApi.create(params))
-    // if (err) return
-    // showNotify({
-    //   type: 'success',
-    //   message: '入室申请创建成功'
-    // })
-    // router.push('/entry')
+      // 直接提交申请,不再显示申请须知(已在跳转页面显示)
+      const [err]: ToResponse = await to(platformAppointApi.create(params))
+      if (err) {
+        debounceTimer.value = 0
+        submitting.value = false // 重置提交状态
+        return
+      }
+      showNotify({
+        type: 'success',
+        message: '入室申请创建成功'
+      })
+      
+      // 接口成功后继续保持加载状态2秒,提供更好的用户体验
+      setTimeout(() => {
+        debounceTimer.value = 0
+        router.push('/entry')
+        submitting.value = false // 2秒后重置提交状态
+      }, 2000)
+    }, 500)
   }
   onMounted(async () => {
     await getDicts()
@@ -462,26 +427,5 @@
       color: #333;
     }
   }
-  .ck-editor {
-    height: calc(100vh - 100px);
-    overflow-y: auto;
-    padding: 0 10px;
-  }
-  .need-to-know {
-    height: calc(100% - 20px);
-    overflow: hidden;
-    display: flex;
-    flex-direction: column;
-    padding: 10px 20px;
-    white-space: pre-wrap;
-    p {
-      flex: 1;
-      overflow-y: auto;
-    }
-    footer {
-      flex: 0 0 45px;
-      margin-top: 4px;
-      border-top: 1px solid #f7f8fa;
-    }
-  }
-</style>
+  /* 申请须知相关样式已移除,申请须知已移动到跳转页面 */
+</style>

+ 81 - 2
src/view/entry/index.vue

@@ -167,6 +167,35 @@
     <!-- <van-popup v-model:show="showEntryAddDialog" position="bottom" :style="{ padding: '64px' }">
       <EntryAdd @entry-add-success="handleEntryAddSuccess" />
     </van-popup> -->
+    
+    <!-- 申请须知弹窗 -->
+    <van-popup
+      v-model:show="needToKnowShow"
+      round
+      :closeable="true"
+      position="bottom"
+      :style="{ height: '90vh' }"
+    >
+      <div class="need-to-know">
+        <h4 class="mt8 mb8">{{ noticeInfo.noticeTitle }}</h4>
+        <div
+          class="ck-editor"
+          v-html="noticeInfo.noticeContent"
+        ></div>
+        <footer>
+          <van-button
+            class="w100"
+            type="primary"
+            round
+            :disabled="confirmDisabled"
+            :loading="confirmDisabled"
+            @click="confirmAppoint"
+          >
+            我知道了
+          </van-button>
+        </footer>
+      </div>
+    </van-popup>
   </div>
 </template>
 
@@ -240,12 +269,38 @@
       },
     })
   }
+  // 申请须知弹窗相关状态
+  const needToKnowShow = ref(false)
+  const confirmDisabled = ref(false)
+  const noticeInfo = reactive({
+    noticeTitle: '',
+    noticeContent: '',
+  })
+  
+  // 获取申请须知内容
+  const getNoticeContent = async () => {
+    // 这里可以调用API获取申请须知内容,暂时使用默认内容
+    noticeInfo.noticeTitle = '入室申请须知'
+    noticeInfo.noticeContent = '请仔细阅读以下入室申请须知:\n\n1. 申请人需遵守实验室各项规章制度\n2. 申请需经过审核流程\n3. 请确保填写信息准确无误\n4. 申请通过后需按时完成相关手续'
+  }
+  
+  // 确认申请须知
+  const confirmAppoint = () => {
+    confirmDisabled.value = true
+    // 确认后跳转到入室申请页面
+    router.push('/entry/add')
+    confirmDisabled.value = false
+    needToKnowShow.value = false
+  }
+  
   const onClick = async () => {
     const [err, res]: ToResponse = await to(platformApi.onCheckUserCertificate({}))
     if (err) return
     const { check, message } = res.data
     if (check) {
-      router.push('/entry/add')
+      // 先显示申请须知弹窗
+      await getNoticeContent()
+      needToKnowShow.value = true
     } else {
       showNotify({
         type: 'warning',
@@ -308,4 +363,28 @@
       }
     }
   }
-</style>
+  
+  // 申请须知弹窗样式
+  .ck-editor {
+    height: calc(100vh - 100px);
+    overflow-y: auto;
+    padding: 0 10px;
+  }
+  .need-to-know {
+    height: calc(100% - 20px);
+    overflow: hidden;
+    display: flex;
+    flex-direction: column;
+    padding: 10px 20px;
+    white-space: pre-wrap;
+    p {
+      flex: 1;
+      overflow-y: auto;
+    }
+    footer {
+      flex: 0 0 45px;
+      margin-top: 4px;
+      border-top: 1px solid #f7f8fa;
+    }
+  }
+</style>

+ 19 - 7
src/view/exam/index.vue

@@ -22,9 +22,9 @@
         <span class="score">({{ currentNode.quScore }}分)</span>
       </div>
       <!-- 单选、判断 -->
-      <van-radio-group v-if="currentNode.quType == 1 || currentNode.quType == 3" v-model="currentNode.isCorrect">
+      <van-radio-group v-if="currentNode.quType == 1 || currentNode.quType == 3" v-model="currentNode.isCorrect" @change="handleAnswerChange">
         <van-cell-group>
-          <van-cell v-for="item in currentNode.quContent" :key="item.id" clickable @click="currentNode.isCorrect = item.name">
+          <van-cell v-for="item in currentNode.quContent" :key="item.id" clickable @click="currentNode.isCorrect = item.name; handleAnswerChange()">
             <template #title>
               <div class="selection">
                 <span> {{ item.name }}:</span>
@@ -38,9 +38,9 @@
         </van-cell-group>
       </van-radio-group>
       <!-- 多选 -->
-      <van-checkbox-group v-else-if="currentNode.quType == 2" v-model="currentNode.isCorrect">
+      <van-checkbox-group v-else-if="currentNode.quType == 2" v-model="currentNode.isCorrect" @change="handleAnswerChange">
         <van-cell-group>
-          <van-cell v-for="(item, index) in currentNode.quContent" clickable :key="item.id" :title="`复选框 ${item}`" @click="toggle(index)">
+          <van-cell v-for="(item, index) in currentNode.quContent" clickable :key="item.id" :title="`复选框 ${item}`" @click="toggle(index); handleAnswerChange()">
             <template #title>
               <div class="selection">
                 <span> {{ item.name }}:</span>
@@ -61,6 +61,7 @@
           v-model="item.content"
           :label="setFillNo(item.name)"
           label-width="20"
+          @update:model-value="handleAnswerChange"
         ></van-field>
       </van-cell-group>
       <!-- 问答 -->
@@ -75,6 +76,7 @@
           placeholder="请输入"
           show-word-limit
           label-width="20"
+          @update:model-value="handleAnswerChange"
         />
       </van-cell-group>
     </div>
@@ -125,9 +127,19 @@
     quContent: []
   })
   const checkboxRefs = ref([])
+  const debounceTimer = ref(0)
   const toggle = (index) => {
     checkboxRefs.value[index].toggle()
   }
+  
+  // 处理答案变化,立即提交到后端
+  const handleAnswerChange = () => {
+    // 使用防抖,避免频繁调用接口
+    clearTimeout(debounceTimer.value)
+    debounceTimer.value = setTimeout(() => {
+      submitAnswer()
+    }, 500)
+  }
   const getDicts = () => {
     Promise.all([dictApi.getDictDataByType('sys_user_type')]).then(([type]) => {
       userTypeList.value = type.data.values || []
@@ -273,14 +285,14 @@
     }
   }
   const handlePre = async () => {
-    await submitAnswer()
+    // 不再重复提交答案,因为选择答案时已经实时提交了
     if (curQuNo.value >= 1) {
       curQuNo.value--
       currentNode.value = sortQuList.value[curQuNo.value]
     }
   }
   const handleNext = async () => {
-    await submitAnswer()
+    // 不再重复提交答案,因为选择答案时已经实时提交了
     if (curQuNo.value < sortQuList.value.length) {
       curQuNo.value++
       currentNode.value = sortQuList.value[curQuNo.value]
@@ -378,4 +390,4 @@
       }
     }
   }
-</style>
+</style>

+ 44 - 25
src/view/instr/appoint.vue

@@ -112,7 +112,7 @@
     </van-form>
   </div>
   <van-action-bar placeholder>
-    <van-action-bar-button class="w100" type="primary" text="提交" @click="onClickButton" />
+    <van-action-bar-button class="w100" type="primary" text="提交" @click="onClickButton" :loading="state.loading" />
   </van-action-bar>
   <!-- 选择服务 -->
   <van-popup v-model:show="state.shwoService" position="bottom">
@@ -439,33 +439,52 @@
       })
     }
 
+  // 防抖计时器
+  const debounceTimer = ref(0)
+  
   const onClickButton = async () => {
-    state.loading = true
-    const [errValid] = await to(formRef.value.validate())
-    const customForm = customFormRef.value.getFormData()
-    if (errValid || (state.form.createForm.length && !customForm)) {
-      state.loading = false
-      return
-    }
-    const params = JSON.parse(JSON.stringify(state.form))
-    params.userName = params.nickName
-    params.sampleForm = JSON.stringify(customForm)
-    delete params.createForm
-    const [err]: ToResponse = await to(instAppoint.add(params))
-    if (err) {
-      state.loading = false
-      return
+    // 防抖处理:如果已经有计时器在运行,则清除并重新设置
+    if (debounceTimer.value) {
+      clearTimeout(debounceTimer.value)
     }
-    showNotify({
-      type: 'success',
-      message: '预约成功'
-    })
-    router.push({
-      path: '/instr-detail',
-      query: {
-        id: params.instId
+    
+    // 设置新的防抖计时器
+    debounceTimer.value = setTimeout(async () => {
+      state.loading = true
+      const [errValid] = await to(formRef.value.validate())
+      const customForm = customFormRef.value.getFormData()
+      if (errValid || (state.form.createForm.length && !customForm)) {
+        state.loading = false
+        debounceTimer.value = 0
+        return
       }
-    })
+      const params = JSON.parse(JSON.stringify(state.form))
+      params.userName = params.nickName
+      params.sampleForm = JSON.stringify(customForm)
+      delete params.createForm
+      const [err]: ToResponse = await to(instAppoint.add(params))
+      if (err) {
+        state.loading = false
+        debounceTimer.value = 0
+        return
+      }
+      showNotify({
+        type: 'success',
+        message: '预约成功'
+      })
+      
+      // 接口成功后继续保持加载状态2秒,提供更好的用户体验
+      setTimeout(() => {
+        router.push({
+          path: '/instr-detail',
+          query: {
+            id: params.instId
+          }
+        })
+        debounceTimer.value = 0
+        state.loading = false // 2秒后重置加载状态
+      }, 2000)
+    }, 500) // 500ms防抖延迟
   }
   onMounted(() => {
     const id = route.query.id ? +route.query.id : 0