瀏覽代碼

完成多次 财天下签约税地

yuanmingze 2 周之前
父節點
當前提交
db7f808a6a

+ 68 - 18
src/pages-auth/settlement-channel-sign/index.vue

@@ -105,7 +105,7 @@
 <script setup lang="ts">
 import { computed, ref } from 'vue'
 
-import { onLoad } from '@dcloudio/uni-app'
+import { onLoad, onShow } from '@dcloudio/uni-app'
 
 import {
   batchChannelCertApi,
@@ -200,7 +200,11 @@ const showSubmit = computed(() => {
 })
 
 const canSubmit = computed(() => {
-  return Boolean(selectedChannel.value?.selectable) && !isSubmitting.value
+  return (
+    Boolean(selectedChannel.value?.selectable) &&
+    !isSubmitting.value &&
+    !settlementSignStore.processingSignTraceId
+  )
 })
 
 const showToast = (title: string): void => {
@@ -239,7 +243,10 @@ const findCertItem = (subjectLocation: string): CertItem | undefined => {
 const isSelectableChannel = (certItem?: CertItem): boolean => {
   if (!certItem) return true
 
-  return SELECTABLE_CERT_STATUS.has(certItem.commonCertStatus ?? '')
+  return (
+    SELECTABLE_CERT_STATUS.has(certItem.commonCertStatus ?? '') ||
+    (certItem.commonCertStatus === 'SIGN' && certItem.needContinue === true)
+  )
 }
 
 const getCertStatusText = (certItem?: CertItem): string => {
@@ -438,24 +445,35 @@ const certFn = async (): Promise<void> => {
     return
   }
 
+  const userInfo = await userStore.getUserInfoByCode()
+  if (!userInfo?.userId || !userInfo.certToken) {
+    showToast('认证信息获取失败,请重试')
+    return
+  }
+
   const accountInfo = wx.getAccountInfoSync()
   const appId = accountInfo.miniProgram.appId
+  const context = settlementSignStore.createContext(subjectLocation)
 
   const signRedirectUrl = `/pages-auth/settlement-channel-sign/index?subjectLocationByUrl=${encodeURIComponent(
     subjectLocation
-  )}`
+  )}&signTraceId=${encodeURIComponent(context.signTraceId)}`
 
   const data = {
-    userId: currentUserInfo.value?.userId || '',
+    userId: userInfo.userId,
     subjectLocation,
-    token: currentUserInfo.value?.certToken || '',
+    token: userInfo.certToken,
     signRedirectUrl,
     wxMiniAppId: appId,
   }
 
-  const res = await batchChannelCertApi(data)
+  const res = await batchChannelCertApi(data).catch((error) => {
+    settlementSignStore.clearContext(context.signTraceId)
+    throw error
+  })
 
   if (res.code === 0 && res.data?.errMsg) {
+    settlementSignStore.clearContext(context.signTraceId)
     authErrorMessage.value = res.data.errMsg
     authErrorDialogVisible.value = true
     isSubmitting.value = false
@@ -463,6 +481,7 @@ const certFn = async (): Promise<void> => {
   }
 
   if (res.data.commonCertStatus === 'FAIL_SIGN') {
+    settlementSignStore.clearContext(context.signTraceId)
     showToast(res.msg || res.data.errMsg || '签约失败')
     return
   }
@@ -470,14 +489,16 @@ const certFn = async (): Promise<void> => {
   const h5SignUrl = res.data?.h5SignUrl
 
   if (h5SignUrl) {
-    const context = settlementSignStore.createContext(subjectLocation)
-
     uni.navigateTo({
       url: `/pages-auth/web-view/index?url=${encodeURIComponent(
         h5SignUrl
       )}&subjectLocation=${encodeURIComponent(
         subjectLocation
       )}&signTraceId=${encodeURIComponent(context.signTraceId)}`,
+      fail: () => {
+        settlementSignStore.clearContext(context.signTraceId)
+        showToast('签约页面打开失败,请重试')
+      },
     })
 
     return
@@ -486,37 +507,66 @@ const certFn = async (): Promise<void> => {
   await handleCertSuccess()
 }
 
-const signRedirect = async (subjectLocation: string): Promise<boolean> => {
+const signRedirect = async (subjectLocation: string, signTraceId = ''): Promise<boolean> => {
   if (!subjectLocation) {
     showToast('参数异常')
     return false
   }
+
+  if (!settlementSignStore.beginSignRedirect(subjectLocation, signTraceId)) return false
+
+  isSubmitting.value = true
+
   try {
-    await signRedirectApi({
+    await userStore.getUserInfoByCode()
+
+    const res = await signRedirectApi({
       subjectLocation,
     })
-    settlementSignStore.clearContext()
-    uni.showToast({
-      title: '签约成功',
-      icon: 'success',
-    })
+
+    if (res.data?.commonCertStatus === 'SIGN' && res.data.needContinue === true) {
+      selectedSubjectLocation.value = subjectLocation
+      await certFn()
+      return true
+    }
+
+    if (SELECTABLE_CERT_STATUS.has(res.data?.commonCertStatus ?? '')) {
+      settlementSignStore.clearContext()
+      await userStore.getUserInfoByCode()
+      showToast(res.data.commonCertStatus === 'FAIL_SIGN' ? '签约失败,请重试' : '签约尚未完成')
+      return false
+    }
+
+    await handleCertSuccess()
     return true
   } catch (error) {
     console.error('signRedirect error:', error)
     return false
+  } finally {
+    settlementSignStore.finishSignRedirect()
+    isSubmitting.value = false
   }
 }
 
 onLoad(async (query) => {
   const subjectLocation = query?.subjectLocationByUrl?.trim() || ''
+  const signTraceId = query?.signTraceId?.trim() || ''
 
   if (subjectLocation) {
-    await signRedirect(subjectLocation)
+    await signRedirect(subjectLocation, signTraceId)
+  } else {
+    await userStore.getUserInfoByCode()
   }
 
-  await userStore.getUserInfoByCode()
   await getSubjectLocationDict()
 })
+
+onShow(() => {
+  const context = settlementSignStore.returnedContext
+  if (!context) return
+
+  void signRedirect(context.subjectLocation, context.signTraceId)
+})
 </script>
 
 <style lang="scss" scoped>

+ 17 - 3
src/pages-auth/web-view/index.vue

@@ -7,7 +7,7 @@
 <script setup lang="ts">
 import { ref } from 'vue'
 
-import { onLoad } from '@dcloudio/uni-app'
+import { onLoad, onUnload } from '@dcloudio/uni-app'
 
 import { useSettlementSignStore } from '@/stores/modules/settlementSign'
 
@@ -36,6 +36,8 @@ const settlementSignStore = useSettlementSignStore()
 const url = ref('')
 const routeSubjectLocation = ref('')
 const routeSignTraceId = ref('')
+let isUnloaded = false
+let isRedirecting = false
 
 const showToast = (title: string) => {
   uni.showToast({
@@ -96,6 +98,8 @@ const resolveSignTraceId = (message: WebViewMessage): string => {
 }
 
 const redirectToSettlementSignPage = (subjectLocation: string, signTraceId: string) => {
+  if (isUnloaded || isRedirecting) return
+
   if (!subjectLocation) {
     showToast('参数异常')
     return
@@ -107,8 +111,12 @@ const redirectToSettlementSignPage = (subjectLocation: string, signTraceId: stri
     query.push(`signTraceId=${encodeURIComponent(signTraceId)}`)
   }
 
+  isRedirecting = true
   uni.redirectTo({
     url: `/pages-auth/settlement-channel-sign/index?${query.join('&')}`,
+    fail: () => {
+      isRedirecting = false
+    },
   })
 }
 
@@ -135,8 +143,14 @@ onLoad((query?: WebViewPageQuery) => {
   }
 
   url.value = safeDecodeURIComponent(query.url)
-  routeSubjectLocation.value = query.subjectLocation || ''
-  routeSignTraceId.value = query.signTraceId || ''
+  routeSubjectLocation.value = safeDecodeURIComponent(query.subjectLocation || '').trim()
+  routeSignTraceId.value = safeDecodeURIComponent(query.signTraceId || '').trim()
+})
+
+onUnload(() => {
+  isUnloaded = true
+  // 这里只记录返回,等签约页 onShow 后再请求,避免在页面退出过程中继续跳转。
+  settlementSignStore.markReturned(routeSubjectLocation.value, routeSignTraceId.value)
 })
 </script>
 

+ 2 - 1
src/services/modules/auth/index.ts

@@ -17,6 +17,7 @@ import type {
   SignAgreementRequest,
   SignCertRequest,
   SignRedirectRequest,
+  SignRedirectResponse,
 } from './type'
 
 export const getAuthInfoApi = () => {
@@ -73,7 +74,7 @@ export const signAgreementApi = (data: SignAgreementRequest) => {
 }
 
 export const signRedirectApi = (data: SignRedirectRequest) => {
-  return http.get(`/admin/user-sign-cert/sign/redirect`, data, {
+  return http.get<SignRedirectResponse>(`/admin/user-sign-cert/sign/redirect`, data, {
     loading: true,
     loadingText: '签约中...',
   })

+ 5 - 0
src/services/modules/auth/type.d.ts

@@ -145,3 +145,8 @@ export interface SignAgreementRequest {
 export interface SignRedirectRequest {
   subjectLocation: string
 }
+
+export interface SignRedirectResponse {
+  commonCertStatus: string | null
+  needContinue: boolean | null
+}

+ 1 - 0
src/services/modules/login/userInfo.d.ts

@@ -98,6 +98,7 @@ export interface CertItem {
   createTime: string
   updateTime: string
   createUser: number
+  needContinue: boolean | null
   updateUser: number
 }
 

+ 51 - 1
src/stores/modules/settlementSign.ts

@@ -10,6 +10,8 @@ export interface SettlementSignContext {
 
 interface SettlementSignState {
   context?: SettlementSignContext
+  returnedSignTraceId: string
+  processingSignTraceId: string
 }
 
 const SIGN_CONTEXT_EXPIRE_TIME = 30 * 60 * 1000
@@ -21,6 +23,8 @@ const createSignTraceId = (subjectLocation: string): string => {
 export const useSettlementSignStore = defineStore('settlementSign', {
   state: (): SettlementSignState => ({
     context: undefined,
+    returnedSignTraceId: '',
+    processingSignTraceId: '',
   }),
 
   getters: {
@@ -37,6 +41,11 @@ export const useSettlementSignStore = defineStore('settlementSign', {
     pendingSubjectLocation(): string {
       return this.validContext?.subjectLocation || ''
     },
+
+    returnedContext(): SettlementSignContext | undefined {
+      const context = this.validContext
+      return context?.signTraceId === this.returnedSignTraceId ? context : undefined
+    },
   },
 
   actions: {
@@ -48,12 +57,53 @@ export const useSettlementSignStore = defineStore('settlementSign', {
       }
 
       this.context = context
+      this.returnedSignTraceId = ''
 
       return context
     },
 
-    clearContext(): void {
+    markReturned(subjectLocation: string, signTraceId: string): void {
+      const context = this.validContext
+      if (
+        !context ||
+        context.subjectLocation !== subjectLocation ||
+        context.signTraceId !== signTraceId ||
+        this.processingSignTraceId === signTraceId
+      ) {
+        return
+      }
+
+      this.returnedSignTraceId = signTraceId
+    },
+
+    // query 回跳与 WebView 返回共用同一把锁,避免两个页面实例重复处理。
+    beginSignRedirect(subjectLocation: string, signTraceId: string): boolean {
+      if (this.processingSignTraceId) return false
+
+      const context = this.validContext
+      if (
+        signTraceId &&
+        (context?.signTraceId !== signTraceId || context.subjectLocation !== subjectLocation)
+      ) {
+        return false
+      }
+
+      this.processingSignTraceId =
+        signTraceId ||
+        (context?.subjectLocation === subjectLocation ? context.signTraceId : subjectLocation)
+      this.returnedSignTraceId = ''
+      return true
+    },
+
+    finishSignRedirect(): void {
+      this.processingSignTraceId = ''
+    },
+
+    clearContext(signTraceId?: string): void {
+      if (signTraceId && this.context?.signTraceId !== signTraceId) return
+
       this.context = undefined
+      this.returnedSignTraceId = ''
     },
   },