Parcourir la source

封装公共hooks,封装提交逻辑

yuanmingze il y a 3 mois
Parent
commit
81d660c106

+ 92 - 0
src/hooks/useInvoice.ts

@@ -0,0 +1,92 @@
+import { ref, reactive } from 'vue'
+import { useRouter } from 'vue-router'
+import { showToast, showSuccessToast } from 'vant'
+import { getStatusApi, submitInvoiceApplyApi } from '@/services/modules/invoiceInformation'
+import type { PushRecordIdRequest } from '@/services/modules/invoiceInformation/type.d.ts'
+import { useUserStore } from '@/stores/modules/user'
+
+interface ToStatus {
+  /** 待处理事件 */
+  eventStatus?: string
+  /** 开票状态 */
+  invoiceStatus?: string
+  /** 是否上传身份证图片 */
+  isIdImgReady?: boolean
+  [property: string]: any
+}
+
+/**
+ * 发票状态 Hook
+ * 用于复用:状态查询 + 提交开票申请
+ */
+export function useInvoice() {
+  const userStore = useUserStore()
+  const router = useRouter()
+
+  // state
+  const btnDisabled = ref(false)
+  const statusMap = ref<ToStatus>({})
+  const isLoading = ref(false)
+
+  // 已提交状态映射
+  const isSubmitStatusMap = ['PENDING', 'FINISHED']
+
+  // 请求参数
+  const params = reactive<PushRecordIdRequest>({
+    pushRecordId: userStore.pushRecordId,
+  })
+
+  /**
+   * 获取开票状态
+   */
+  const getStatus = async () => {
+    isLoading.value = true
+    try {
+      const res = await getStatusApi(params)
+      if (res.code === 0) {
+        statusMap.value = res.data as ToStatus
+        const invoiceStatus = res.data.invoiceStatus
+        if (invoiceStatus && isSubmitStatusMap.includes(invoiceStatus)) {
+          btnDisabled.value = true
+        } else {
+          btnDisabled.value = false
+        }
+      }
+    } catch (err) {
+      console.error('获取状态失败', err)
+      showToast('获取开票状态失败,请稍后重试')
+    } finally {
+      isLoading.value = false
+    }
+  }
+
+  /**
+   * 提交开票申请
+   */
+  const submitInvoiceApply = async () => {
+    try {
+      const res = await submitInvoiceApplyApi(params)
+      if (res.code === 0 && res.data) {
+        showSuccessToast('提交成功')
+        setTimeout(() => {
+          userStore.LogOut()
+          router.replace({ path: '/success' })
+        }, 800)
+      } else {
+        showToast('提交失败,请稍后重试')
+      }
+    } catch (err) {
+      console.error('提交失败', err)
+      showToast('提交失败,请稍后重试')
+    }
+  }
+
+  return {
+    params,
+    statusMap,
+    btnDisabled,
+    isLoading,
+    getStatus,
+    submitInvoiceApply,
+  }
+}

+ 12 - 0
src/services/modules/identityUpload/index.ts

@@ -0,0 +1,12 @@
+import http from '../../index'
+
+import type { OcrRequest } from './type.d'
+export const ocrApi = (data: OcrRequest) => {
+  const formData = new FormData()
+  formData.append('file', data.file)
+  formData.append('side', data.side)
+  return http.post({
+    url: '/admin/invoice-expert/ocr',
+    data: formData,
+  })
+}

+ 8 - 0
src/services/modules/identityUpload/type.d.ts

@@ -0,0 +1,8 @@
+/** OCR 识别请求参数 */
+export interface OcrRequest {
+  /** 文件对象 */
+  file: File
+  /** 身份证正反面:front=人像面,back=国徽面 */
+  side: 'front' | 'back'
+  [key: string]: any
+}

+ 4 - 14
src/views/face-recognition/index.vue

@@ -27,11 +27,12 @@ import StepProgress from '@/components/StepProgress.vue'
 import { getFaceAuthInfoApi, getFaceAuthResultApi } from '@/services/modules/faceRecognition'
 import { ref, onBeforeMount, onMounted, onUnmounted } from 'vue'
 import { showToast } from 'vant'
-import { useRouter } from 'vue-router'
-const router = useRouter()
+import { useInvoice } from '@/hooks/useInvoice'
 
 const etsUrl = ref('')
 
+// --- 初始化 Hooks ---
+const { submitInvoiceApply } = useInvoice()
 // 获取认证链接
 const getConfirmInvoiceInfo = async () => {
   const res = await getFaceAuthInfoApi()
@@ -46,19 +47,8 @@ const getConfirmInvoiceInfo = async () => {
 const getFaceAuthResult = async () => {
   try {
     const res = await getFaceAuthResultApi()
-
     if (res.code === 0 && res.data?.success) {
-      showToast('认证成功,跳转下一步')
-
-      // ✅ 设置一次性标记(给下个页面用)
-      sessionStorage.setItem('FACE_AUTH_DONE', '1')
-
-      // ✅ 使用 replace 替换当前路由,不留返回记录
-      setTimeout(() => {
-        router.replace({
-          path: '/invoice-information',
-        })
-      }, 800)
+      submitInvoiceApply()
     } else {
       showToast('认证失败,请刷新页面后重试')
     }

+ 71 - 35
src/views/identity-upload/index.vue

@@ -44,7 +44,14 @@
 
     <!-- 下一步按钮 -->
     <div class="btn-section">
-      <van-button block type="primary" round class="next-btn" :disabled="!isReady" @click="onNext">
+      <van-button
+        block
+        type="primary"
+        round
+        class="next-btn"
+        :disabled="!isReady"
+        @click="handleNextDebounced"
+      >
         下一步
       </van-button>
     </div>
@@ -52,9 +59,19 @@
 </template>
 
 <script setup lang="ts">
-import { ref, computed } from 'vue'
+import { ref, computed, reactive, onMounted } from 'vue'
 import { showToast, showFailToast } from 'vant'
 import { sysFileUploadApi } from '@/services/modules/common'
+import { ocrApi } from '@/services/modules/identityUpload/index'
+import { useDebounceFn } from '@/utils/util'
+import { useRouter } from 'vue-router'
+/** 背景图 */
+import frontBg from '@/assets/images/id-front-bg.png'
+import backBg from '@/assets/images/id-back-bg.png'
+// ✅ 使用封装好的 Hook
+import { useInvoice } from '@/hooks/useInvoice'
+// --- 初始化 Hooks ---
+const { getStatus, submitInvoiceApply, statusMap } = useInvoice()
 
 /** 上传项类型 */
 interface UploadFileItem {
@@ -72,9 +89,7 @@ interface UploadItem {
   bg: string
 }
 
-/** 背景图 */
-import frontBg from '@/assets/images/id-front-bg.png'
-import backBg from '@/assets/images/id-back-bg.png'
+const router = useRouter()
 
 /** 上传列表 */
 const uploadList = ref<UploadItem[]>([
@@ -82,7 +97,7 @@ const uploadList = ref<UploadItem[]>([
   { label: '请上传身份证国徽面', field: 'back', fileList: [], bg: backBg },
 ])
 
-/** 上传+OCR识别逻辑 */
+/** 上传 + OCR识别逻辑 */
 const handleAfterRead = async (file: any, item: UploadItem, index: number) => {
   const target = uploadList.value[index]
   if (!target) return
@@ -98,14 +113,11 @@ const handleAfterRead = async (file: any, item: UploadItem, index: number) => {
     if (res.code === 0 && res.data?.url) {
       const baseUrl = import.meta.env.VITE_APP_URL
       const fullUrl = res.data.url.startsWith('http') ? res.data.url : `${baseUrl}${res.data.url}`
-
       target.fileList = [{ url: fullUrl, status: 'done', fileId: res.data.fileId }]
       showToast(`${item.label} 上传成功`)
-
-      // 调用OCR识别
-      const ocrRes = await handleOcrCheck(item.field, res.data.fileId, fullUrl)
+      // 调用 OCR 接口(传原始文件 + side)
+      const ocrRes = await handleOcrCheck(item.field, file.file)
       if (!ocrRes.success) {
-        // OCR 识别失败:清除该图片并提示
         target.fileList = []
         showFailToast(`识别${item.label}失败,请重新上传`)
       }
@@ -121,44 +133,68 @@ const handleAfterRead = async (file: any, item: UploadItem, index: number) => {
 }
 
 /** 模拟OCR识别接口逻辑(你可以替换成实际API) */
-const handleOcrCheck = async (
-  side: 'front' | 'back',
-  fileId: string,
-  url: string,
-): Promise<{ success: boolean }> => {
-  // 示例:这里可以根据 side 调用不同的 OCR 接口
-  // 例如 front 调用 /ocr/idcard/front   back 调用 /ocr/idcard/back
+/** OCR识别逻辑 */
+const handleOcrCheck = async (side: 'front' | 'back', file: File) => {
   try {
-    console.log('识别请求 =>', { side, fileId, url })
-    // 模拟延时识别
-    await new Promise((resolve) => setTimeout(resolve, 800))
+    // 真实调用
+    const res = await ocrApi({ file, side })
+    console.log('res', res)
+    if (res.code === 0) {
+      // 当是人面向
+      if (side === 'front') {
+        submitInfo.idCard = res.data.idcard
+        submitInfo.idCardFrontImg = res.data.imgUrl
+      } else {
+        submitInfo.idcardPeriodStart = res.data.begin
+        submitInfo.idcardPeriodEnd = res.data.end
+        submitInfo.idCardBackImg = res.data.imgUrl
+      }
+      console.log('submitInfo', submitInfo)
 
-    // ✅ 模拟成功 / ❌ 模拟失败
-    const success = Math.random() > 0.2 // 80% 成功率示例
-    return { success }
+      return { success: true }
+    }
+    return { success: false }
   } catch (err) {
     console.error('OCR error', err)
     return { success: false }
   }
 }
+const submitInfo = reactive({
+  id: '',
+  idCard: '',
+  idcardPeriodStart: '',
+  idcardPeriodEnd: '',
+  idCardFrontImg: '',
+  idCardBackImg: '',
+})
 
 /** 是否完成上传 */
-const isReady = computed(() => uploadList.value.every((item) => item.fileList.length > 0))
+// ✅ 校验:每个字段必须有值(非空字符串)
+const isReady = computed(() => {
+  return Object.values(submitInfo).every(
+    (value) => value !== '' && value !== null && value !== undefined,
+  )
+})
 
-/** 下一步按钮点击 */
-const onNext = () => {
+const handleNext = async () => {
   if (!isReady.value) return showToast('请先上传身份证正反面')
 
-  // 拼装最终提交数据
-  const result = uploadList.value.map((item) => ({
-    field: item.field,
-    fileId: item.fileList[0]?.fileId,
-    url: item.fileList[0]?.url,
-  }))
+  // 需要人脸识别->跳转人脸识别
+  if (statusMap.value.eventStatus === 'PROCESSED') {
+    return router.push({ path: '/face-recognition' })
+  }
 
-  console.log('提交数据:', result)
-  showToast('上传完成,进入下一步')
+  // 其他情况,直接提交
+  await submitInvoiceApply()
 }
+/** 防抖包装 */
+const handleNextDebounced = useDebounceFn(handleNext, 1000)
+
+/** 下一步按钮点击 */
+
+onMounted(async () => {
+  await Promise.all([getStatus()])
+})
 </script>
 
 <style scoped>

+ 36 - 100
src/views/invoice-information/index.vue

@@ -11,7 +11,6 @@
             <div class="dot-skeleton"></div>
             <div class="title-skeleton"></div>
           </div>
-
           <div class="card-body">
             <div class="info-skeleton" v-for="j in 3" :key="j">
               <div class="label-skeleton"></div>
@@ -62,6 +61,7 @@
             </div>
           </div>
         </div>
+
         <!-- 发票信息 -->
         <div class="card">
           <div class="card-header">
@@ -113,49 +113,42 @@
         round
         color="linear-gradient(90deg, #ff7a00, #ffa94d)"
         class="next-btn"
-        @click="handleNext"
+        @click="handleNextDebounced"
       >
         下一步
       </van-button>
     </div>
-
-    <!-- 通用弹窗 -->
-    <!-- <ModernDialog
-      v-model:show="showDialog"
-      title="申请成功"
-      message="您的开票信息已提交至税务部门审核,审核通过后通知您缴纳税款,请关注缴税信息。"
-      confirmText="我知道了"
-      @confirm="onConfirm"
-    /> -->
   </div>
 </template>
 
 <script setup lang="ts">
+import { ref, reactive, onMounted } from 'vue'
+import { showToast } from 'vant'
 import { useRouter } from 'vue-router'
 import { useDebounceFn } from '@/utils/util'
-// import ModernDialog from '@/components/ModernDialog.vue'
-import {
-  getConfirmInvoiceInfoApi,
-  getStatusApi,
-  submitInvoiceApplyApi,
-} from '@/services/modules/invoiceInformation'
-import type { PushRecordIdRequest } from '@/services/modules/invoiceInformation/type.d.ts'
-import { reactive, onMounted, ref } from 'vue'
 import StepProgress from '@/components/StepProgress.vue'
-import { showToast, showSuccessToast } from 'vant'
+import { getConfirmInvoiceInfoApi } from '@/services/modules/invoiceInformation'
+import type { PushRecordIdRequest } from '@/services/modules/invoiceInformation/type.d.ts'
 import { useUserStore } from '@/stores/modules/user'
 
-const userStore = useUserStore()
+// ✅ 使用封装好的 Hook
+import { useInvoice } from '@/hooks/useInvoice'
+// --- 初始化 Hooks ---
+const { getStatus, submitInvoiceApply, btnDisabled, statusMap } = useInvoice()
 
+// --- 其余本页面独有逻辑 ---
+const userStore = useUserStore()
 const router = useRouter()
 const loading = ref(true)
 const invoiceInfo = ref<any>({})
+const activeStep = ref(1)
 
+// 请求参数
 const params = reactive<PushRecordIdRequest>({
   pushRecordId: userStore.pushRecordId,
 })
 
-const btnDisabled = ref<boolean>(false)
+// 获取发票确认信息
 const getConfirmInvoiceInfo = async () => {
   try {
     loading.value = true
@@ -164,100 +157,43 @@ const getConfirmInvoiceInfo = async () => {
       invoiceInfo.value = res.data
     }
   } catch (err: any) {
-    const { code, message } = err
-    // 如果存在错误码,则显示错误信息,且按钮禁用点击
-    if (code === 1 && message) {
-      showToast(message)
-      btnDisabled.value = true
-    }
+    const { message } = err
+    showToast(message)
   } finally {
     loading.value = false
   }
 }
 
-interface ToStatus {
-  /**
-   * 待处理事件
-   */
-  eventStatus?: string
-  /**
-   * 开票状态
-   */
-  invoiceStatus?: string
-  /**
-   * 是否上传身份证图片
-   */
-  isIdImgReady?: boolean
-  [property: string]: any
-}
-
-const statusMap = ref<ToStatus>({})
-// 已经提交状态
-const isSubmitStausMap = ['PENDING', 'FINISHED']
-const getStatus = async () => {
-  const res = await getStatusApi(params)
-  if (res.code === 0) {
-    statusMap.value = res.data as ToStatus
-    const invoiceStatus = res.data.invoiceStatus
-    if (invoiceStatus && isSubmitStausMap.includes(invoiceStatus)) {
-      btnDisabled.value = true
-    }
-  }
-}
-
-const handleNext = useDebounceFn(async () => {
+/**
+ * 下一步逻辑
+ * 使用 hooks 提供的状态进行判断
+ */
+const handleNext = async () => {
+  // 未上传身份证,跳转上传页
   if (!statusMap.value.isIdImgReady) {
-    return router.push({
-      path: '/identity-upload',
-    })
+    return router.push({ path: '/identity-upload' })
   }
 
-  // 当eventStatus === PROCESSED 需要人脸认证
+  // 已处理状态,跳转人脸识别
   if (statusMap.value.eventStatus === 'PROCESSED') {
-    return router.push({
-      path: '/face-recognition',
-    })
+    return router.push({ path: '/face-recognition' })
   }
-  return submitInvoiceApply()
-  // 当前账户没有身份照片,跳转上传证件页面
-}, 1000)
 
-const submitInvoiceApply = async () => {
-  try {
-    const res = await submitInvoiceApplyApi(params)
-    if (res.code === 0 && res.data) {
-      showSuccessToast('提交成功')
-      setTimeout(() => {
-        userStore.LogOut()
-        router.replace({
-          path: '/success',
-        })
-      }, 800)
-    }
-  } catch (err) {
-    console.log('err', err)
-    showToast('提交失败,请稍后重试')
-  }
+  // 其他情况,直接提交
+  await submitInvoiceApply()
 }
 
-const activeStep = ref(1)
+/** 防抖包装 */
+const handleNextDebounced = useDebounceFn(handleNext, 1000)
 
+/** 跳转缴税详情页 */
 const toDetail = () => {
-  router.push({
-    path: '/invoice-information/detail',
-  })
+  router.push({ path: '/invoice-information/detail' })
 }
-onMounted(() => {
-  getStatus()
-  getConfirmInvoiceInfo()
-  const authFlag = sessionStorage.getItem('FACE_AUTH_DONE')
-  if (authFlag === '1') {
-    activeStep.value = 3
-    sessionStorage.removeItem('FACE_AUTH_DONE') // 清除标记
-    submitInvoiceApply()
-  } else {
-    activeStep.value = 1
-  }
+
+/** 生命周期 */
+onMounted(async () => {
+  await Promise.all([getStatus(), getConfirmInvoiceInfo()])
 })
 </script>