Просмотр исходного кода

新增人脸识别 申请成功功能

yuanmingze 3 месяцев назад
Родитель
Сommit
53408c2c54

+ 1 - 1
auto-imports.d.ts

@@ -6,5 +6,5 @@
 // biome-ignore lint: disable
 export {}
 declare global {
-
+  const showToast: typeof import('vant/es').showToast
 }

+ 1 - 0
components.d.ts

@@ -16,6 +16,7 @@ declare module 'vue' {
     StepProgress: typeof import('./src/components/StepProgress.vue')['default']
     VanButton: typeof import('vant/es')['Button']
     VanCellGroup: typeof import('vant/es')['CellGroup']
+    VanDialog: typeof import('vant/es')['Dialog']
     VanField: typeof import('vant/es')['Field']
     VanIcon: typeof import('vant/es')['Icon']
     VanNavBar: typeof import('vant/es')['NavBar']

+ 6 - 0
src/services/modules/faceRecognition/index.ts

@@ -5,3 +5,9 @@ export const getFaceAuthInfoApi = () => {
     url: '/admin/invoice-order/get-face-auth-info',
   })
 }
+
+export const getFaceAuthResultApi = () => {
+  return http.post({
+    url: '/admin/invoice-order/get-face-auth-result',
+  })
+}

+ 7 - 0
src/services/modules/invoiceInformation/index.ts

@@ -21,3 +21,10 @@ export const getStatusApi = (data: InvoiceOrderIdRequest) => {
     params: data,
   })
 }
+
+export const submitInvoiceApplyApi = (data: InvoiceOrderIdRequest) => {
+  return http.post({
+    url: '/admin/invoice-order/submit-invoice-apply',
+    data: data,
+  })
+}

+ 144 - 15
src/views/face-recognition/index.vue

@@ -2,45 +2,174 @@
   <div class="face-recognition">
     <!-- 顶部导航 -->
     <van-nav-bar title="自然人开票" fixed placeholder />
+
+    <!-- 步骤卡片 -->
     <div class="card">
       <StepProgress :activeStep="2" />
     </div>
+
+    <!-- iframe 容器 -->
+    <div class="iframe-page">
+      <iframe
+        :src="etsUrl"
+        frameborder="0"
+        class="iframe"
+        allowfullscreen
+        scrolling="no"
+        ref="iframeRef"
+      ></iframe>
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
 import StepProgress from '@/components/StepProgress.vue'
-import { getFaceAuthInfoApi } from '@/services/modules/faceRecognition'
-import { onBeforeMount } from '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()
 
+const etsUrl = ref('')
+
+// 获取认证链接
 const getConfirmInvoiceInfo = async () => {
   const res = await getFaceAuthInfoApi()
-  console.log('res', res)
+  if (res.code === 0 && res.data?.faceAuthUrl) {
+    etsUrl.value = res.data.faceAuthUrl
+  } else {
+    console.warn('获取认证链接失败', res)
+  }
+}
+
+// 获取认证结果
+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)
+    } else {
+      showToast('认证失败,请刷新页面后重试')
+    }
+  } catch (err) {
+    console.error('获取认证结果失败', err)
+    showToast('网络错误,请稍后重试')
+  }
 }
+
+// 🧠 监听 iframe 发来的消息
+const handleMessage = (event: MessageEvent) => {
+  // 忽略 React DevTools 的心跳消息
+  if (event.data?.source === 'react-devtools-content-script') return
+
+  console.log('📩 收到 iframe 消息:', event.data)
+
+  if (event.data?.type === 'AUTH_DONE') {
+    showToast('认证完成,跳转下一步')
+    // 比如继续执行下一步逻辑
+  } else if (event.data?.type === 'AUTH_ERROR') {
+    showToast('认证失败:' + event.data.message)
+  }
+}
+
+onMounted(() => {
+  window.addEventListener('message', handleMessage)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('message', handleMessage)
+})
+
 onBeforeMount(() => {
   getConfirmInvoiceInfo()
+
+  // 模拟获取认证结果
+  setTimeout(() => {
+    getFaceAuthResult()
+  }, 5000)
 })
 </script>
 
-<style lang="scss" scoped>
+<style scoped lang="scss">
 .face-recognition {
+  height: 100dvh;
+  display: flex;
+  flex-direction: column;
   background: #f5f6f8;
-  min-height: 100vh;
   font-family:
     -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Microsoft YaHei',
     sans-serif;
   font-size: 3.8vw;
   padding: 4vw 3.5vw;
+  box-sizing: border-box;
+  overflow: hidden;
+}
 
-  .card {
-    background: #fff;
-    border-radius: 14px;
-    margin-bottom: 5vw;
-    padding: 4vw 3.5vw;
-    box-shadow:
-      0 4px 12px rgba(0, 0, 0, 0.06),
-      0 1px 3px rgba(0, 0, 0, 0.04);
-    transition: all 0.3s ease;
-  }
+/* 顶部步骤进度卡片 */
+.card {
+  background: #fff;
+  border-radius: 14px;
+  margin-bottom: 4vw;
+  padding: 4vw 3.5vw;
+  box-shadow:
+    0 4px 12px rgba(0, 0, 0, 0.06),
+    0 1px 3px rgba(0, 0, 0, 0.04);
+  flex-shrink: 0;
+}
+
+/* iframe 外层 */
+.iframe-page {
+  flex: 1;
+  min-height: 0;
+  overflow: hidden;
+  /* 用负边距抵消父 padding,使 iframe 全宽 */
+  margin-left: -3.5vw;
+  margin-right: -3.5vw;
+  margin-bottom: -4vw; /* 让底部贴合视口 */
+  border-radius: 0;
+  position: relative;
+}
+
+/* iframe 内容 */
+.iframe {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100vw; /* 全屏宽 */
+  height: 100%;
+  border: none;
+  display: block;
+  overflow: hidden;
+
+  /* 禁止横向滚动条 */
+  overflow-x: hidden;
+  overflow-y: auto;
+  -webkit-overflow-scrolling: touch; /* 移动端平滑滚动 */
+}
+
+/* iframe 外层滚动条美化(仅当内部非跨域时生效) */
+.iframe::-webkit-scrollbar {
+  width: 6px;
+}
+.iframe::-webkit-scrollbar-thumb {
+  background: rgba(255, 122, 0, 0.4);
+  border-radius: 4px;
+}
+.iframe::-webkit-scrollbar-thumb:hover {
+  background: rgba(255, 122, 0, 0.7);
+}
+.iframe::-webkit-scrollbar-track {
+  background: transparent;
 }
 </style>

+ 129 - 2
src/views/invoice-information/index.vue

@@ -24,8 +24,9 @@
       <!-- 加载完成后显示真实内容 -->
       <template v-else>
         <div class="card">
-          <StepProgress :activeStep="1" />
+          <StepProgress :activeStep="activeStep" />
         </div>
+
         <!-- 卡片:申请人信息 -->
         <div class="card">
           <div class="card-header">
@@ -115,15 +116,46 @@
         下一步
       </van-button>
     </div>
+
+    <!-- ✅ 弹窗(放在最外层,不要嵌套第二个 template) -->
+    <van-dialog
+      v-model:show="showDialog"
+      :show-cancel-button="false"
+      :show-confirm-button="false"
+      class="modern-dialog"
+      close-on-click-overlay
+    >
+      <div class="dialog-content">
+        <div class="dialog-title">申请成功</div>
+        <div class="dialog-message">
+          您的开票信息已提交至税务部门审核,审核通过后通知您缴纳税款,请关注缴税信息。
+        </div>
+        <van-button
+          type="primary"
+          block
+          round
+          class="dialog-btn"
+          color="linear-gradient(90deg, #ff7a00, #ffa94d)"
+          @click="onConfirm"
+        >
+          我知道了
+        </van-button>
+      </div>
+    </van-dialog>
   </div>
 </template>
 
 <script setup lang="ts">
 import { useRouter } from 'vue-router'
-import { getConfirmInvoiceInfoApi, getStatusApi } from '@/services/modules/invoiceInformation'
+import {
+  getConfirmInvoiceInfoApi,
+  getStatusApi,
+  submitInvoiceApplyApi,
+} from '@/services/modules/invoiceInformation'
 import type { InvoiceOrderIdRequest } from '@/services/modules/invoiceInformation/type.d.ts'
 import { reactive, onMounted, ref } from 'vue'
 import StepProgress from '@/components/StepProgress.vue'
+import { showToast } from 'vant'
 
 const router = useRouter()
 const loading = ref(true)
@@ -166,6 +198,26 @@ const nextBtn = async () => {
   }
 }
 
+const showDialog = ref(false)
+const submitInvoiceApply = async () => {
+  try {
+    const res = await submitInvoiceApplyApi(params)
+    if (res.code === 0 && res.data) {
+      showDialog.value = true
+    }
+  } catch (err) {
+    console.error('提交失败', err)
+    showToast('提交失败,请稍后重试')
+  }
+}
+
+const activeStep = ref(1)
+const onConfirm = () => {
+  showDialog.value = false
+  
+
+}
+
 const toDetail = () => {
   router.push({
     path: '/invoice-information/detail',
@@ -173,6 +225,14 @@ const toDetail = () => {
 }
 onMounted(() => {
   getConfirmInvoiceInfo()
+  const authFlag = sessionStorage.getItem('FACE_AUTH_DONE')
+  if (authFlag === '1') {
+    activeStep.value = 3
+    sessionStorage.removeItem('FACE_AUTH_DONE') // 清除标记
+    submitInvoiceApply()
+  } else {
+    activeStep.value = 1
+  }
 })
 </script>
 
@@ -334,4 +394,71 @@ onMounted(() => {
     }
   }
 }
+
+/* 修正版:留白 + 按钮不被挤压 */
+.modern-dialog {
+  width: 80vw;
+  border-radius: 5vw !important;
+  background: #fff;
+  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.15);
+  padding: 0; /* 外层不再加内边距,改到内容区 */
+  overflow: visible; /* 允许按钮投影外扩 */
+  text-align: center;
+  animation: dialogPop 0.28s ease-out;
+  box-sizing: border-box;
+
+  .dialog-content {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 6vw 5vw 5vw; /* ✅ 内容留白在这里 */
+    gap: 5vw; /* 内容与按钮的竖向间距 */
+  }
+
+  .dialog-title {
+    font-size: 4.4vw;
+    font-weight: 700;
+    color: #222;
+  }
+
+  .dialog-message {
+    font-size: 3.6vw;
+    line-height: 1.7;
+    color: #555;
+    word-break: break-word;
+  }
+
+  /* ✅ 独立按钮:有左右边距与圆角,不会被挤压 */
+  .dialog-btn {
+    width: calc(100% - 10vw); /* 等同左右各 5vw 的边距 */
+    margin: 0 auto 2.5vw; /* 底部也留点气口 */
+    height: 12vw;
+    border-radius: 3vw;
+    font-size: 4vw;
+    font-weight: 600;
+    display: inline-flex; /* 避免被拉伸成整块底边 */
+    align-items: center;
+    justify-content: center;
+    border: none;
+    box-shadow: 0 4px 12px rgba(255, 128, 54, 0.25);
+    letter-spacing: 0.2vw;
+  }
+}
+
+/* 蒙层与动画(可保留) */
+:deep(.van-overlay) {
+  backdrop-filter: blur(8px);
+  background: rgba(0, 0, 0, 0.25);
+  animation: fadeIn 0.3s ease;
+}
+@keyframes dialogPop {
+  from {
+    transform: translateY(4vw) scale(0.9);
+    opacity: 0;
+  }
+  to {
+    transform: translateY(0) scale(1);
+    opacity: 1;
+  }
+}
 </style>

+ 0 - 1
src/views/login/index.vue

@@ -68,7 +68,6 @@ import type { SendTtsSmsRequest, LoginEtssmsRequest } from '@/services/modules/l
 import { useUserStore } from '@/stores/modules/user'
 
 const userStore = useUserStore()
-
 const router = useRouter()
 
 /* 表单数据 */