浏览代码

Merge branch 'master' into pre

yuanmingze 1 月之前
父节点
当前提交
09a27b3278

+ 6 - 0
src/router/routes.ts

@@ -66,6 +66,12 @@ export const routes: RouteRecordRaw[] = [
     component: () => import('@/views/pedding-face-recognition/index.vue'),
     meta: { title: '获取认证状态', requiresAuth: true },
   },
+  {
+    path: '/wechat-redirect',
+    name: 'WechatRedirect',
+    component: () => import('@/views/wechat-redirect/index.vue'),
+    meta: { title: '路由跳转中', requiresAuth: false },
+  },
   {
     path: '/:pathMatch(.*)*',
     redirect: '/login',

+ 9 - 0
src/services/modules/wechat-redirect/index.ts

@@ -0,0 +1,9 @@
+import http from '../../index'
+import type { miniappUrlLinkRequest } from './type.d'
+
+export const miniappUrlLinkApi = (data: miniappUrlLinkRequest) => {
+  return http.get({
+    url: '/admin/miniapp/url-link',
+    params: data,
+  })
+}

+ 6 - 0
src/services/modules/wechat-redirect/type.d.ts

@@ -0,0 +1,6 @@
+// src/types/auth.ts
+export interface miniappUrlLinkRequest {
+  path: string
+  query: string
+  envVersion?: string
+}

+ 371 - 0
src/views/wechat-redirect/index.vue

@@ -0,0 +1,371 @@
+<template>
+  <div class="redirect-page">
+    <div class="bg bg-1" />
+    <div class="bg bg-2" />
+
+    <div class="card">
+      <div class="brand">
+        <div class="icon">
+          <van-icon name="share-o" />
+        </div>
+        <div class="text">
+          <h1>正在跳转</h1>
+          <p>请稍候,系统正在为你打开页面</p>
+        </div>
+      </div>
+
+      <div class="status-panel">
+        <div class="top">
+          <van-loading size="20px" type="spinner" />
+          <span class="status-text">{{ statusText }}</span>
+        </div>
+
+        <van-progress
+          :percentage="progress"
+          :show-pivot="false"
+          :stroke-width="6"
+          track-color="rgba(255,255,255,0.08)"
+          color="linear-gradient(90deg, #3b82f6 0%, #22c55e 100%)"
+        />
+
+        <div class="meta">
+          <span>{{ stepLabel }}</span>
+          <span>{{ progress }}%</span>
+        </div>
+      </div>
+
+      <div class="tips">
+        <div class="title">处理中</div>
+        <div class="list">
+          <div class="item">
+            <van-icon name="passed" class="item-icon" />
+            <span>链接校验</span>
+          </div>
+          <div class="item">
+            <van-icon name="shield-o" class="item-icon" />
+            <span>安全检测</span>
+          </div>
+          <div class="item">
+            <van-icon name="guide-o" class="item-icon" />
+            <span>准备跳转</span>
+          </div>
+        </div>
+      </div>
+
+      <div class="footer">
+        <p>若长时间未跳转,请稍后重试</p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
+import { useRoute } from 'vue-router'
+import { showToast } from 'vant'
+import { miniappUrlLinkApi } from '@/services/modules/wechat-redirect'
+
+const route = useRoute()
+
+const progress = ref(10)
+
+const getPushRecordId = (): string => {
+  const { pushRecordId } = route.query
+
+  if (typeof pushRecordId === 'string') {
+    return pushRecordId
+  }
+
+  if (Array.isArray(pushRecordId) && pushRecordId.length > 0) {
+    return pushRecordId[0] ?? ''
+  }
+
+  return ''
+}
+
+const getLoginUrl = (pushRecordId: string): string => {
+  const baseUrl =
+    import.meta.env.VITE_APP_ENV === 'prod'
+      ? 'https://hcp.yaoyi.net/h5/login'
+      : 'https://hcppre.yaoyi.net/h5/login'
+
+  return `${baseUrl}?pushRecordId=${encodeURIComponent(pushRecordId)}`
+}
+
+const getMiniappLinkParams = (redirectUrl: string) => {
+  return {
+    path: 'pages/index/index',
+    query: `redirect=${encodeURIComponent(redirectUrl)}`,
+    envVersion: import.meta.env.VITE_APP_ENV === 'prod' ? 'release' : 'trial',
+  } as const
+}
+
+const redirectToMiniapp = async (): Promise<void> => {
+  const pushRecordId = getPushRecordId()
+
+  if (!pushRecordId) {
+    showToast('链接不完整或参数错误')
+    return
+  }
+
+  try {
+    const redirectUrl = getLoginUrl(pushRecordId)
+    const params = getMiniappLinkParams(redirectUrl)
+    const res = await miniappUrlLinkApi(params)
+
+    if (res.code !== 0 || !res.data) {
+      showToast(res.msg || '跳转链接生成失败')
+      return
+    }
+
+    window.location.href = res.data
+  } catch (error) {
+    console.error('[redirectToMiniapp] failed:', error)
+    showToast('页面跳转失败,请稍后重试')
+  }
+}
+
+const statusText = computed(() => {
+  if (progress.value < 30) return '正在初始化页面'
+  if (progress.value < 70) return '正在校验链接有效性'
+  if (progress.value < 100) return '正在准备跳转'
+  return '正在跳转...'
+})
+
+const stepLabel = computed(() => {
+  if (progress.value < 30) return '初始化中'
+  if (progress.value < 70) return '校验中'
+  if (progress.value < 100) return '准备跳转'
+  return '跳转中'
+})
+
+let timer: number | null = null
+
+const clearTimer = (): void => {
+  if (timer !== null) {
+    window.clearInterval(timer)
+    timer = null
+  }
+}
+
+const startProgress = (): void => {
+  timer = window.setInterval(() => {
+    if (progress.value >= 95) {
+      clearTimer()
+      progress.value = 100
+      return
+    }
+
+    if (progress.value < 30) {
+      progress.value += 8
+      return
+    }
+
+    if (progress.value < 70) {
+      progress.value += 5
+      return
+    }
+
+    progress.value += 2
+  }, 300)
+}
+
+onMounted(() => {
+  startProgress()
+  redirectToMiniapp()
+})
+
+onBeforeUnmount(() => {
+  clearTimer()
+})
+</script>
+
+<style scoped lang="scss">
+.redirect-page {
+  position: relative;
+  min-height: 100vh;
+  overflow: hidden;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 24px;
+  box-sizing: border-box;
+  color: #fff;
+  background:
+    radial-gradient(circle at top left, rgba(59, 130, 246, 0.22), transparent 38%),
+    radial-gradient(circle at bottom right, rgba(34, 197, 94, 0.18), transparent 34%),
+    linear-gradient(180deg, #0b1220 0%, #111827 52%, #0f172a 100%);
+
+  .bg {
+    position: absolute;
+    border-radius: 50%;
+    filter: blur(48px);
+    pointer-events: none;
+  }
+
+  .bg-1 {
+    top: -80px;
+    left: -60px;
+    width: 220px;
+    height: 220px;
+    background: rgba(59, 130, 246, 0.25);
+  }
+
+  .bg-2 {
+    right: -70px;
+    bottom: -80px;
+    width: 240px;
+    height: 240px;
+    background: rgba(34, 197, 94, 0.18);
+  }
+
+  .card {
+    position: relative;
+    z-index: 1;
+    width: 100%;
+    max-width: 420px;
+    padding: 28px 22px 22px;
+    border-radius: 24px;
+    background: rgba(15, 23, 42, 0.72);
+    border: 1px solid rgba(255, 255, 255, 0.08);
+    box-shadow:
+      0 20px 50px rgba(0, 0, 0, 0.35),
+      inset 0 1px 0 rgba(255, 255, 255, 0.06);
+    backdrop-filter: blur(18px);
+  }
+
+  .brand {
+    display: flex;
+    align-items: center;
+    gap: 14px;
+    margin-bottom: 24px;
+
+    .icon {
+      flex-shrink: 0;
+      width: 52px;
+      height: 52px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      border-radius: 16px;
+      font-size: 24px;
+      background: linear-gradient(135deg, rgba(59, 130, 246, 0.95), rgba(34, 197, 94, 0.9));
+      box-shadow: 0 10px 24px rgba(59, 130, 246, 0.28);
+    }
+
+    .text {
+      h1 {
+        margin: 0;
+        font-size: 22px;
+        font-weight: 700;
+        line-height: 1.25;
+      }
+
+      p {
+        margin: 6px 0 0;
+        font-size: 13px;
+        line-height: 1.5;
+        color: rgba(255, 255, 255, 0.68);
+      }
+    }
+  }
+
+  .status-panel {
+    padding: 18px 16px;
+    border-radius: 18px;
+    background: rgba(255, 255, 255, 0.04);
+    border: 1px solid rgba(255, 255, 255, 0.06);
+
+    .top {
+      display: flex;
+      align-items: center;
+      gap: 10px;
+      margin-bottom: 14px;
+    }
+
+    .status-text {
+      font-size: 14px;
+      color: rgba(255, 255, 255, 0.9);
+    }
+
+    .meta {
+      margin-top: 12px;
+      display: flex;
+      justify-content: space-between;
+      gap: 12px;
+      font-size: 12px;
+      color: rgba(255, 255, 255, 0.58);
+    }
+  }
+
+  .tips {
+    margin-top: 18px;
+    padding: 18px 16px;
+    border-radius: 18px;
+    background: rgba(255, 255, 255, 0.03);
+    border: 1px solid rgba(255, 255, 255, 0.05);
+
+    .title {
+      margin-bottom: 12px;
+      font-size: 13px;
+      font-weight: 600;
+      color: rgba(255, 255, 255, 0.86);
+    }
+
+    .list {
+      display: flex;
+      flex-direction: column;
+      gap: 10px;
+    }
+
+    .item {
+      display: flex;
+      align-items: center;
+      gap: 10px;
+      font-size: 13px;
+      color: rgba(255, 255, 255, 0.72);
+
+      .item-icon {
+        font-size: 16px;
+        color: #7dd3fc;
+      }
+    }
+  }
+
+  .footer {
+    margin-top: 18px;
+    text-align: center;
+
+    p {
+      margin: 0;
+      font-size: 12px;
+      color: rgba(255, 255, 255, 0.45);
+    }
+  }
+}
+
+@media (max-width: 480px) {
+  .redirect-page {
+    padding: 16px;
+
+    .card {
+      padding: 22px 16px 18px;
+      border-radius: 20px;
+    }
+
+    .brand {
+      .icon {
+        width: 46px;
+        height: 46px;
+        font-size: 22px;
+      }
+
+      .text {
+        h1 {
+          font-size: 20px;
+        }
+      }
+    }
+  }
+}
+</style>