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