From f77f419987dab0b86c3d405e0c93361ddab3dce2 Mon Sep 17 00:00:00 2001
From: “mitao” <“2763622819@qq.com”>
Date: 星期一, 27 四月 2026 20:48:58 +0800
Subject: [PATCH] fix:扫码分享页修改

---
 tcm-training-admin-backend/src/main/resources/static/app-download/index.html                            |  248 +++++++++++++++++++++++++++++++++++++++++--------
 tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/share/vo/AppInviteBindTicketVO.java |    2 
 tcm-training-admin-backend/src/main/resources/application.yml                                           |    2 
 tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/share/service/AppShareService.java  |    7 
 traditionalApp/App.vue                                                                                  |   12 +
 traditionalApp/manifest.json                                                                            |    3 
 traditionalApp/config/index.js                                                                          |    4 
 7 files changed, 228 insertions(+), 50 deletions(-)

diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/share/service/AppShareService.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/share/service/AppShareService.java
index f81fe5d..7386587 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/share/service/AppShareService.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/share/service/AppShareService.java
@@ -142,8 +142,9 @@
         }
         AppInviteBindTicketVO result = new AppInviteBindTicketVO();
         result.setBindTicket(bindTicket);
+        result.setInviteCode(bindingTarget.getInviteCode());
         result.setExpireAt(expireAt.format(DATE_TIME_FORMATTER));
-        result.setOpenAppUrl(buildOpenAppUrl(bindTicket));
+        result.setOpenAppUrl(buildOpenAppUrl(bindTicket, bindingTarget.getInviteCode()));
         return result;
     }
 
@@ -373,13 +374,13 @@
     /**
      * 构造安卓 App 打开注册页的深链地址。
      */
-    private String buildOpenAppUrl(String bindTicket) {
+    private String buildOpenAppUrl(String bindTicket, String inviteCode) {
         String schemeUrl = trimToNull(appShareProperties.getAndroidAppSchemeUrl());
         if (schemeUrl == null) {
             throw new IllegalArgumentException("App 打开地址暂未配置");
         }
         String queryJoiner = schemeUrl.contains("?") ? "&" : "?";
-        return schemeUrl + queryJoiner + "bindTicket=" + bindTicket;
+        return schemeUrl + queryJoiner + "bindTicket=" + bindTicket + "&inviteCode=" + inviteCode;
     }
 
     /**
diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/share/vo/AppInviteBindTicketVO.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/share/vo/AppInviteBindTicketVO.java
index dc7d790..6669201 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/share/vo/AppInviteBindTicketVO.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/share/vo/AppInviteBindTicketVO.java
@@ -12,6 +12,8 @@
 
     private String bindTicket;
 
+    private String inviteCode;
+
     private String expireAt;
 
     private String openAppUrl;
diff --git a/tcm-training-admin-backend/src/main/resources/application.yml b/tcm-training-admin-backend/src/main/resources/application.yml
index e789370..623eac6 100644
--- a/tcm-training-admin-backend/src/main/resources/application.yml
+++ b/tcm-training-admin-backend/src/main/resources/application.yml
@@ -115,7 +115,7 @@
       access-token-url: https://api.weixin.qq.com/sns/oauth2/access_token
     apple:
       # 苹果登录 clientId,需与 iOS 包内 Sign in with Apple 配置保持一致
-      client-id: "com.haizhentong.app"
+      client-id: "com.HaiZhenKangDao.hzt"
       # 苹果 identity token 固定签发方
       issuer: https://appleid.apple.com
       # 苹果 JWKS 公钥地址
diff --git a/tcm-training-admin-backend/src/main/resources/static/app-download/index.html b/tcm-training-admin-backend/src/main/resources/static/app-download/index.html
index ee68011..0d73a94 100644
--- a/tcm-training-admin-backend/src/main/resources/static/app-download/index.html
+++ b/tcm-training-admin-backend/src/main/resources/static/app-download/index.html
@@ -232,13 +232,11 @@
         <div class="invite-box">
             <div class="invite-label">邀请码</div>
             <div class="invite-code" id="inviteCodeValue">--</div>
-            <div class="copy-tip">安装完成后请返回本页,点击“打开 App 继续注册”完成绑定。</div>
+            <div class="copy-tip" id="flowTip">点击下方按钮,已安装将直接打开 APP;未安装会自动下载。</div>
         </div>
 
         <div class="btn-group">
-            <button class="download-btn" id="androidDownloadBtn" type="button">下载 Android 版</button>
-            <button class="download-btn secondary" id="iosDownloadBtn" type="button">下载 iOS 版</button>
-            <button class="download-btn secondary" id="openAppBtn" type="button">打开 App 继续注册</button>
+            <button class="download-btn" id="primaryActionBtn" type="button">打开 APP 继续注册</button>
             <button class="copy-btn" id="copyInviteBtn" type="button">复制邀请码</button>
         </div>
 
@@ -247,8 +245,8 @@
         <div class="section">
             <div class="section-title">安装说明</div>
             <ol class="notice">
-                <li>点击对应平台下载按钮,下载安装 APP。</li>
-                <li>安装完成后返回本页,点击“打开 App 继续注册”。</li>
+                <li>点击“打开 APP 继续注册”,页面会优先尝试打开 APP。</li>
+                <li>若尚未安装,将自动下载对应平台安装包;安装完成后返回本页会继续打开 APP。</li>
                 <li>普通用户注册成功后,将自动关联邀请医生所属诊所。</li>
             </ol>
         </div>
@@ -266,8 +264,16 @@
         iosDownloadUrl: '',
         wechatBrowser: false,
         openAppBaseUrl: '',
-        bindTicketLoading: false
+        bindTicketLoading: false,
+        openAppUrl: '',
+        autoOpenTimer: 0,
+        openingApp: false,
+        pendingResumeOpen: false
     }
+
+    const PENDING_OPEN_STORAGE_KEY = 'appInvitePendingOpen'
+    const AUTO_DOWNLOAD_DELAY_MS = 1600
+    const RESUME_AUTO_OPEN_DELAY_MS = 500
 
     /**
      * 页面初始化。
@@ -276,6 +282,7 @@
         bindActions()
         detectWechatEnvironment()
         bindWechatEnvironmentListeners()
+        bindResumeAutoOpenListeners()
         const inviteCode = normalizeInviteCode(readQueryParam('inviteCode'))
         state.inviteCode = inviteCode
         renderInviteCode(inviteCode)
@@ -321,6 +328,7 @@
             document.getElementById('updateNoticeText').textContent = '当前已提供最新安装包下载。'
         }
         updateButtonState()
+        scheduleResumeAutoOpen()
     }
 
     /**
@@ -330,6 +338,7 @@
         document.getElementById('clinicTitle').textContent = '欢迎下载中医培训 APP'
         document.getElementById('inviteCodeValue').textContent = inviteCode || '--'
         document.getElementById('updateNoticeText').textContent = '如需绑定邀请关系,请复制邀请码后在注册页填写。'
+        document.getElementById('flowTip').textContent = '邀请信息加载失败时,可复制邀请码后在 APP 注册页手动填写。'
         updateButtonState()
     }
 
@@ -337,11 +346,8 @@
      * 绑定页面按钮事件。
      */
     function bindActions() {
-        document.getElementById('androidDownloadBtn').addEventListener('click', function () {
-            handleDownload(state.androidDownloadUrl, 'ANDROID')
-        })
-        document.getElementById('iosDownloadBtn').addEventListener('click', function () {
-            handleDownload(state.iosDownloadUrl, 'IOS')
+        document.getElementById('primaryActionBtn').addEventListener('click', function () {
+            void handlePrimaryAction(false)
         })
         document.getElementById('copyInviteBtn').addEventListener('click', function () {
             if (!state.inviteCode) {
@@ -350,47 +356,54 @@
             }
             copyInviteCode(state.inviteCode, true)
         })
-        document.getElementById('openAppBtn').addEventListener('click', function () {
-            void handleOpenApp()
-        })
         document.getElementById('wechatGuideClose').addEventListener('click', function () {
             hideWechatGuide()
         })
     }
 
     /**
-     * 处理下载动作,仅负责下载安装包。
+     * 处理主按钮动作,优先拉起 APP,未安装时自动下载。
      */
-    function handleDownload(url, platform) {
+    async function handlePrimaryAction(fromResume) {
         if (shouldBlockDirectDownload()) {
             showWechatGuide()
-            showStatus('请点击右上角,选择在浏览器中打开后再下载')
-            return
-        }
-        const normalizedUrl = normalizeOptionalText(url)
-        if (!normalizedUrl) {
-            showStatus(platform === 'ANDROID' ? 'Android 安装包暂未配置' : 'iOS 安装包暂未配置')
-            return
-        }
-        window.location.href = normalizedUrl
-    }
-
-    /**
-     * 生成绑定票据并通过深链打开 App 继续注册。
-     */
-    async function handleOpenApp() {
-        if (state.bindTicketLoading) {
+            showStatus('请点击右上角,选择在浏览器中打开后继续')
             return
         }
         if (!state.inviteCode) {
-            showStatus('当前二维码未包含邀请码,请改用手动填写邀请码')
+            showStatus('当前二维码未包含邀请码,请复制邀请码后手动注册')
             return
+        }
+        const openAppUrl = await ensureOpenAppUrl()
+        if (!openAppUrl) {
+            return
+        }
+        persistPendingOpen()
+        tryOpenApp(openAppUrl)
+        if (fromResume) {
+            clearPendingOpen()
+            showStatus('正在尝试打开 APP,如未打开请点击按钮重试')
+            return
+        }
+        scheduleDownloadFallback()
+    }
+
+    /**
+     * 确保已拿到可打开 APP 的深链地址。
+     */
+    async function ensureOpenAppUrl() {
+        if (state.openAppUrl) {
+            return state.openAppUrl
+        }
+        if (state.bindTicketLoading) {
+            return ''
         }
         if (!state.openAppBaseUrl) {
             showStatus('App 打开地址暂未配置')
-            return
+            return ''
         }
         state.bindTicketLoading = true
+        updateButtonState()
         try {
             const response = await fetch('/api/open/app-invite/bind-ticket', {
                 method: 'POST',
@@ -404,14 +417,166 @@
             const payload = await response.json()
             if (!payload || payload.code !== 0 || !payload.data || !payload.data.openAppUrl) {
                 showStatus(payload && (payload.msg || payload.message) ? payload.msg || payload.message : '绑定凭证生成失败,请稍后重试')
-                return
+                return ''
             }
-            window.location.href = payload.data.openAppUrl
-            showStatus('若未自动打开 App,请安装后返回本页再次点击')
+            state.openAppUrl = normalizeOptionalText(payload.data.openAppUrl)
+            return state.openAppUrl
         } catch (error) {
-            showStatus('打开 App 失败,请安装后返回本页再次点击')
+            showStatus('绑定凭证生成失败,请稍后重试')
+            return ''
         } finally {
             state.bindTicketLoading = false
+            updateButtonState()
+        }
+    }
+
+    /**
+     * 通过 Scheme 拉起 APP。
+     */
+    function tryOpenApp(openAppUrl) {
+        state.openingApp = true
+        showStatus('正在打开 APP,请稍候...')
+        window.location.href = openAppUrl
+        window.setTimeout(function () {
+            state.openingApp = false
+        }, AUTO_DOWNLOAD_DELAY_MS)
+    }
+
+    /**
+     * 拉起失败时按当前平台下载 APP。
+     */
+    function scheduleDownloadFallback() {
+        clearAutoOpenTimer()
+        state.autoOpenTimer = window.setTimeout(function () {
+            if (document.hidden) {
+                return
+            }
+            const downloadUrl = resolveCurrentPlatformDownloadUrl()
+            if (!downloadUrl) {
+                showStatus('当前平台安装包暂未配置,请复制邀请码后联系工作人员')
+                return
+            }
+            document.getElementById('flowTip').textContent = '安装完成后返回本页,页面会自动打开 APP 继续注册。'
+            document.getElementById('primaryActionBtn').textContent = '安装完成后自动打开中...'
+            showStatus('未检测到 APP,正在为您下载对应安装包')
+            window.location.href = downloadUrl
+        }, AUTO_DOWNLOAD_DELAY_MS)
+    }
+
+    /**
+     * 根据浏览器平台选择安装包地址。
+     */
+    function resolveCurrentPlatformDownloadUrl() {
+        if (isIosBrowser()) {
+            return state.iosDownloadUrl
+        }
+        if (isAndroidBrowser()) {
+            return state.androidDownloadUrl
+        }
+        return state.androidDownloadUrl || state.iosDownloadUrl
+    }
+
+    /**
+     * 判断当前访问环境是否为 iOS 浏览器。
+     */
+    function isIosBrowser() {
+        const userAgent = String(window.navigator.userAgent || '').toLowerCase()
+        return /iphone|ipad|ipod/.test(userAgent)
+    }
+
+    /**
+     * 判断当前访问环境是否为 Android 浏览器。
+     */
+    function isAndroidBrowser() {
+        const userAgent = String(window.navigator.userAgent || '').toLowerCase()
+        return userAgent.indexOf('android') >= 0 || userAgent.indexOf('adr') >= 0
+    }
+
+    /**
+     * 记录安装后返回页面需要继续打开 APP。
+     */
+    function persistPendingOpen() {
+        try {
+            window.localStorage.setItem(PENDING_OPEN_STORAGE_KEY, JSON.stringify({
+                inviteCode: state.inviteCode,
+                openAppUrl: state.openAppUrl,
+                createdAt: Date.now()
+            }))
+        } catch (error) {
+        }
+    }
+
+    /**
+     * 根据本地待继续记录恢复自动打开 APP。
+     */
+    function scheduleResumeAutoOpen() {
+        const pendingOpen = readPendingOpen()
+        if (!pendingOpen || pendingOpen.inviteCode !== state.inviteCode || state.pendingResumeOpen) {
+            return
+        }
+        state.pendingResumeOpen = true
+        if (pendingOpen.openAppUrl) {
+            state.openAppUrl = pendingOpen.openAppUrl
+        }
+        window.setTimeout(function () {
+            if (!document.hidden) {
+                void handlePrimaryAction(true)
+            }
+            state.pendingResumeOpen = false
+        }, RESUME_AUTO_OPEN_DELAY_MS)
+    }
+
+    /**
+     * 绑定页面返回前台时的自动继续动作。
+     */
+    function bindResumeAutoOpenListeners() {
+        window.addEventListener('pageshow', function () {
+            scheduleResumeAutoOpen()
+        })
+        document.addEventListener('visibilitychange', function () {
+            if (!document.hidden && !state.openingApp) {
+                scheduleResumeAutoOpen()
+            }
+        })
+    }
+
+    /**
+     * 读取待继续打开 APP 的本地记录。
+     */
+    function readPendingOpen() {
+        try {
+            const rawValue = window.localStorage.getItem(PENDING_OPEN_STORAGE_KEY)
+            if (!rawValue) {
+                return null
+            }
+            const parsedValue = JSON.parse(rawValue)
+            if (!parsedValue || Date.now() - Number(parsedValue.createdAt || 0) > 24 * 60 * 60 * 1000) {
+                window.localStorage.removeItem(PENDING_OPEN_STORAGE_KEY)
+                return null
+            }
+            return parsedValue
+        } catch (error) {
+            return null
+        }
+    }
+
+    /**
+     * 清理已经消费的待继续打开记录。
+     */
+    function clearPendingOpen() {
+        try {
+            window.localStorage.removeItem(PENDING_OPEN_STORAGE_KEY)
+        } catch (error) {
+        }
+    }
+
+    /**
+     * 清理当前延迟打开或下载任务。
+     */
+    function clearAutoOpenTimer() {
+        if (state.autoOpenTimer) {
+            window.clearTimeout(state.autoOpenTimer)
+            state.autoOpenTimer = 0
         }
     }
 
@@ -419,8 +584,9 @@
      * 根据当前接口数据刷新下载按钮状态。
      */
     function updateButtonState() {
-        document.getElementById('androidDownloadBtn').disabled = !state.androidDownloadUrl
-        document.getElementById('iosDownloadBtn').disabled = !state.iosDownloadUrl
+        const primaryActionBtn = document.getElementById('primaryActionBtn')
+        primaryActionBtn.disabled = state.bindTicketLoading || (!state.androidDownloadUrl && !state.iosDownloadUrl)
+        primaryActionBtn.textContent = state.bindTicketLoading ? '正在准备注册信息...' : '打开 APP 继续注册'
     }
 
     /**
diff --git a/traditionalApp/App.vue b/traditionalApp/App.vue
index 7191d44..110f83d 100644
--- a/traditionalApp/App.vue
+++ b/traditionalApp/App.vue
@@ -3,7 +3,7 @@
 		onLaunch,
 		onShow
 	} from '@dcloudio/uni-app'
-	import { extractBindTicket } from '@/utils/utils.js'
+	import { extractBindTicket, extractInviteCode } from '@/utils/utils.js'
 
 	let lastHandledBindTicket = ''
 
@@ -25,14 +25,20 @@
 	 */
 	function handleOpenByScheme() {
 		// #ifdef APP-PLUS
-		const bindTicket = extractBindTicket(typeof plus !== 'undefined' && plus.runtime ? plus.runtime.arguments : '')
+		const runtimeArguments = typeof plus !== 'undefined' && plus.runtime ? plus.runtime.arguments : ''
+		const bindTicket = extractBindTicket(runtimeArguments)
 		if (!bindTicket || bindTicket === lastHandledBindTicket) {
 			return
 		}
 		lastHandledBindTicket = bindTicket
+		const inviteCode = extractInviteCode(runtimeArguments)
 		const currentPages = getCurrentPages()
 		const currentRoute = currentPages && currentPages.length > 0 ? currentPages[currentPages.length - 1].route : ''
-		const targetUrl = `/pages/register/register?bindTicket=${encodeURIComponent(bindTicket)}`
+		const targetParams = [`bindTicket=${encodeURIComponent(bindTicket)}`]
+		if (inviteCode) {
+			targetParams.push(`inviteCode=${encodeURIComponent(inviteCode)}`)
+		}
+		const targetUrl = `/pages/register/register?${targetParams.join('&')}`
 		if (currentRoute === 'pages/register/register') {
 			uni.redirectTo({
 				url: targetUrl
diff --git a/traditionalApp/config/index.js b/traditionalApp/config/index.js
index e889a40..0ba8136 100644
--- a/traditionalApp/config/index.js
+++ b/traditionalApp/config/index.js
@@ -1,6 +1,6 @@
 export default {
-	// BASE_URL: 'http://192.168.110.188:48080',
-	BASE_URL: 'https://haizhentong.com',
+	BASE_URL: 'http://192.168.110.188:48080',
+	// BASE_URL: 'https://haizhentong.com',
 	uploadUrl: "https://haizhentong.com/api/open/file/upload",
 	imageUrl: 'http://221.182.45.100:11224/images/'
 };
\ No newline at end of file
diff --git a/traditionalApp/manifest.json b/traditionalApp/manifest.json
index f6fbc26..8cde6ec 100644
--- a/traditionalApp/manifest.json
+++ b/traditionalApp/manifest.json
@@ -55,6 +55,9 @@
 			},
 			/* ios打包配置 */
 			"ios": {
+				"schemes" : [
+					"haizhentong"
+				],
 				"dSYMs": false,
 				"capabilities": {
 					"entitlements": {

--
Gitblit v1.7.1