From 0c98579fff979ae80fc30224b87c9c6eb626f49e Mon Sep 17 00:00:00 2001
From: “mitao” <“2763622819@qq.com”>
Date: 星期四, 30 四月 2026 17:29:37 +0800
Subject: [PATCH] feat:openinstall落地页接入websdk
---
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/auth/service/AppWechatAuthClient.java | 14 ++
tcm-training-admin-backend/src/main/resources/application.yml | 6
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/share/service/AppShareService.java | 15 -
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/system/service/JpushManualPushAppService.java | 104 +++++++++++++----
traditionalApp/App.vue | 4
traditionalApp/pages/clearCache/clearCache.vue | 6 +
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/config/SaTokenWebConfig.java | 16 ++
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/share/config/AppShareProperties.java | 19 +++
traditionalApp/config/index.js | 4
tcm-training-admin-backend/src/main/resources/static/app-download/index.html | 90 ++++++++++++++-
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/system/controller/JpushManualPushController.java | 8 +
traditionalApp/pages/register/register.vue | 9 +
traditionalApp/utils/invite-cache.js | 34 +++++
13 files changed, 280 insertions(+), 49 deletions(-)
diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/auth/service/AppWechatAuthClient.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/auth/service/AppWechatAuthClient.java
index c8de56a..a9913eb 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/auth/service/AppWechatAuthClient.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/auth/service/AppWechatAuthClient.java
@@ -4,9 +4,11 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tcm.training.admin.app.auth.config.AppWechatAuthProperties;
import com.tcm.training.admin.app.auth.dto.AppWechatLoginRequest;
+import java.nio.charset.StandardCharsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
@@ -29,7 +31,7 @@
*/
@Autowired
public AppWechatAuthClient(AppWechatAuthProperties properties, ObjectMapper objectMapper) {
- this(properties, objectMapper, new RestTemplate());
+ this(properties, objectMapper, createUtf8RestTemplate());
}
/**
@@ -200,6 +202,16 @@
}
/**
+ * 创建默认按 UTF-8 解码字符串响应的 HTTP 客户端,避免微信中文昵称乱码。
+ */
+ private static RestTemplate createUtf8RestTemplate() {
+ RestTemplate restTemplate = new RestTemplate();
+ restTemplate.getMessageConverters().removeIf(StringHttpMessageConverter.class::isInstance);
+ restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));
+ return restTemplate;
+ }
+
+ /**
* 保存微信换票结果,避免在多个私有方法之间散落参数。
*/
private static class WechatAccessTokenSnapshot {
diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/share/config/AppShareProperties.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/share/config/AppShareProperties.java
index 925572c..8657513 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/share/config/AppShareProperties.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/share/config/AppShareProperties.java
@@ -16,6 +16,11 @@
private String landingBaseUrl = "https://haizhentong.com/app-download/";
/**
+ * 二维码图片公开接口基础地址,需包含协议与域名。
+ */
+ private String qrcodeImageBaseUrl = "https://haizhentong.com/api/open/app-share/qrcode-image";
+
+ /**
* 安卓 App 自定义 Scheme 基础地址。
*/
private String androidAppSchemeUrl = "haizhentong://register";
@@ -35,6 +40,20 @@
}
/**
+ * 获取二维码图片公开接口基础地址。
+ */
+ public String getQrcodeImageBaseUrl() {
+ return qrcodeImageBaseUrl;
+ }
+
+ /**
+ * 设置二维码图片公开接口基础地址。
+ */
+ public void setQrcodeImageBaseUrl(String qrcodeImageBaseUrl) {
+ this.qrcodeImageBaseUrl = qrcodeImageBaseUrl;
+ }
+
+ /**
* 获取安卓 App 自定义 Scheme 基础地址。
*/
public String getAndroidAppSchemeUrl() {
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 7386587..6a0ae31 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
@@ -301,21 +301,16 @@
* 构建二维码图片公开访问地址。
*/
private String buildQrCodeImageUrl(String inviteCode) {
- String landingBaseUrl = trimToNull(appShareProperties.getLandingBaseUrl());
- if (landingBaseUrl == null) {
- throw new IllegalArgumentException("邀请下载页地址暂未配置");
+ String qrcodeImageBaseUrl = trimToNull(appShareProperties.getQrcodeImageBaseUrl());
+ if (qrcodeImageBaseUrl == null) {
+ throw new IllegalArgumentException("分享二维码图片地址暂未配置");
}
- int schemeIndex = landingBaseUrl.indexOf("://");
- if (schemeIndex < 0) {
- throw new IllegalArgumentException("邀请下载页地址格式不正确");
- }
- int hostEndIndex = landingBaseUrl.indexOf('/', schemeIndex + 3);
- String origin = hostEndIndex >= 0 ? landingBaseUrl.substring(0, hostEndIndex) : landingBaseUrl;
String normalizedInviteCode = trimToNull(inviteCode);
if (!StringUtils.hasText(normalizedInviteCode)) {
throw new IllegalArgumentException("邀请码不能为空");
}
- return origin + "/api/open/app-share/qrcode-image?inviteCode=" + normalizedInviteCode;
+ String queryJoiner = qrcodeImageBaseUrl.contains("?") ? "&" : "?";
+ return qrcodeImageBaseUrl + queryJoiner + "inviteCode=" + normalizedInviteCode;
}
/**
diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/config/SaTokenWebConfig.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/config/SaTokenWebConfig.java
index 9bca475..927f494 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/config/SaTokenWebConfig.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/config/SaTokenWebConfig.java
@@ -6,22 +6,27 @@
import com.tcm.training.admin.common.xss.XssParamInterceptor;
import com.tcm.training.admin.upload.LocalUploadProperties;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.io.File;
+import java.util.Arrays;
@Configuration
public class SaTokenWebConfig implements WebMvcConfigurer {
private final XssParamInterceptor xssParamInterceptor;
private final LocalUploadProperties localUploadProperties;
+ private final Environment environment;
public SaTokenWebConfig(XssParamInterceptor xssParamInterceptor,
- LocalUploadProperties localUploadProperties) {
+ LocalUploadProperties localUploadProperties,
+ Environment environment) {
this.xssParamInterceptor = xssParamInterceptor;
this.localUploadProperties = localUploadProperties;
+ this.environment = environment;
}
/**
@@ -70,7 +75,7 @@
private boolean isPublicAdminApi(String requestPath) {
return "/api/auth/login".equals(requestPath)
|| "/api/auth/captcha".equals(requestPath)
- || isJpushManualPushApi(requestPath)
+ || (!isProdProfile() && isJpushManualPushApi(requestPath))
|| requestPath.startsWith("/api/open/")
|| requestPath.startsWith("/api/v1/open/");
}
@@ -86,6 +91,13 @@
}
/**
+ * 判断当前是否为生产环境,生产环境手动推送接口必须校验后台登录态。
+ */
+ private boolean isProdProfile() {
+ return Arrays.asList(environment.getActiveProfiles()).contains("prod");
+ }
+
+ /**
* 将本地上传目录映射为静态资源,可通过 /uploads/** 访问。
*/
@Override
diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/system/controller/JpushManualPushController.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/system/controller/JpushManualPushController.java
index 6d1e468..2c90ed7 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/system/controller/JpushManualPushController.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/system/controller/JpushManualPushController.java
@@ -67,6 +67,14 @@
}
/**
+ * 生成礼品发货测试通知并推送。
+ */
+ @PostMapping("/jpush/manual-push/order-shipped")
+ public ApiResponse<JpushManualPushVO> pushOrderShipped(@Valid @RequestBody JpushManualPushRequest request) {
+ return ApiResponse.ok(jpushManualPushAppService.pushOrderShipped(request));
+ }
+
+ /**
* 生成课程更新测试通知并推送。
*/
@PostMapping("/jpush/manual-push/course-update-notice")
diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/system/service/JpushManualPushAppService.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/system/service/JpushManualPushAppService.java
index c549b1d..646f802 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/system/service/JpushManualPushAppService.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/system/service/JpushManualPushAppService.java
@@ -9,6 +9,8 @@
import com.tcm.training.admin.utils.JGPushUtil;
import org.springframework.stereotype.Service;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -21,7 +23,6 @@
private static final String TEST_TITLE = "极光推送联调";
private static final String TEST_CONTENT = "这是一条极光推送联调测试消息";
private static final String TEST_SCENE = "JPUSH_MANUAL_TEST";
- private static final String MANUAL_NOTICE_TEST_SCENE = "MANUAL_NOTICE_TEST";
private static final String PUSH_STATUS_SUCCESS = "SUCCESS";
private final JGPushUtil jgPushUtil;
@@ -58,55 +59,116 @@
* 生成会员到期提醒测试通知并尝试极光推送。
*/
public JpushManualPushVO pushMemberExpireRemind(JpushManualPushRequest request) {
+ LocalDate expireDate = LocalDate.now().plusDays(3L);
+ Map<String, String> payload = new LinkedHashMap<String, String>();
+ payload.put("expireAt", expireDate.atTime(23, 59, 59).toString());
+ payload.put("remainingDays", "3");
return pushNoticeType(request,
AppSystemNoticeType.MEMBER_EXPIRE_REMIND,
"会员到期提醒",
- "您的会员有效期将于测试日期到期",
- false);
+ "您的会员有效期将于" + expireDate + "到期",
+ "MEMBER_EXPIRE_" + expireDate,
+ request.getAppUserId(),
+ false,
+ payload);
}
/**
* 生成直播提醒测试通知并尝试极光推送。
*/
public JpushManualPushVO pushLiveRemind(JpushManualPushRequest request) {
+ Long sessionId = System.currentTimeMillis();
+ LocalDateTime startTime = LocalDateTime.now().plusHours(1L);
+ Map<String, String> payload = new LinkedHashMap<String, String>();
+ payload.put("courseId", "100001");
+ payload.put("sessionId", String.valueOf(sessionId));
+ payload.put("startTime", String.valueOf(startTime));
+ payload.put("courseTitle", "测试直播课程");
return pushNoticeType(request,
AppSystemNoticeType.LIVE_REMIND,
"直播提醒",
"测试直播课程 即将开播",
- true);
+ "LIVE_SESSION",
+ sessionId,
+ true,
+ payload);
}
/**
* 生成活动通知测试通知并尝试极光推送。
*/
public JpushManualPushVO pushActivityNotice(JpushManualPushRequest request) {
+ Long activityNoticeId = System.currentTimeMillis();
+ Map<String, String> payload = new LinkedHashMap<String, String>();
+ payload.put("activityNoticeId", String.valueOf(activityNoticeId));
+ payload.put("title", "活动通知");
return pushNoticeType(request,
AppSystemNoticeType.ACTIVITY_NOTICE,
"活动通知",
- "这是一条活动通知联调测试消息",
- true);
+ "活动通知",
+ "ACTIVITY_NOTICE",
+ activityNoticeId,
+ true,
+ payload);
}
/**
- * 生成订单状态更新测试通知并尝试极光推送。
+ * 生成礼品订单创建测试通知并尝试极光推送。
*/
public JpushManualPushVO pushOrderStatusUpdate(JpushManualPushRequest request) {
+ Long orderId = System.currentTimeMillis();
+ Map<String, String> payload = new LinkedHashMap<String, String>();
+ payload.put("orderId", String.valueOf(orderId));
+ payload.put("orderNo", "TEST" + orderId);
+ payload.put("giftName", "测试礼品");
+ payload.put("itemCount", "1");
return pushNoticeType(request,
AppSystemNoticeType.ORDER_STATUS_UPDATE,
"订单状态更新",
- "您的礼品兑换订单状态已更新",
- true);
+ "您有新的礼品兑换订单待处理",
+ "POINTS_EXCHANGE_ORDER",
+ orderId,
+ true,
+ payload);
+ }
+
+ /**
+ * 生成礼品发货测试通知并尝试极光推送。
+ */
+ public JpushManualPushVO pushOrderShipped(JpushManualPushRequest request) {
+ Long orderId = System.currentTimeMillis();
+ Map<String, String> payload = new LinkedHashMap<String, String>();
+ payload.put("orderId", String.valueOf(orderId));
+ payload.put("orderNo", "TEST" + orderId);
+ payload.put("trackingNo", "SF" + orderId);
+ return pushNoticeType(request,
+ AppSystemNoticeType.ORDER_STATUS_UPDATE,
+ "订单状态更新",
+ "您的礼品兑换订单已发货",
+ "POINTS_EXCHANGE_ORDER_SHIPPED",
+ orderId,
+ true,
+ payload);
}
/**
* 生成课程更新测试通知并尝试极光推送。
*/
public JpushManualPushVO pushCourseUpdateNotice(JpushManualPushRequest request) {
+ Long videoId = System.currentTimeMillis();
+ Map<String, String> payload = new LinkedHashMap<String, String>();
+ payload.put("courseId", "100001");
+ payload.put("courseTitle", "测试课程");
+ payload.put("videoId", String.valueOf(videoId));
+ payload.put("chapterName", "测试章节");
return pushNoticeType(request,
AppSystemNoticeType.COURSE_UPDATE_NOTICE,
"课程更新通知",
"测试课程 已更新新章节",
- true);
+ "HEALTH_COURSE_VIDEO",
+ videoId,
+ true,
+ payload);
}
/**
@@ -116,31 +178,21 @@
String noticeTypeCode,
String title,
String content,
- boolean requireUserSetting) {
- Long referenceId = System.currentTimeMillis();
+ String referenceType,
+ Long referenceId,
+ boolean requireUserSetting,
+ Map<String, String> payload) {
AppSystemNoticeDispatchCommand command = new AppSystemNoticeDispatchCommand();
command.setAppUserId(request.getAppUserId());
command.setNoticeTypeCode(noticeTypeCode);
command.setTitle(title);
command.setContent(content);
- command.setReferenceType("MANUAL_TEST_" + noticeTypeCode);
+ command.setReferenceType(referenceType);
command.setReferenceId(referenceId);
command.setRequireUserSetting(requireUserSetting);
- command.setPayload(buildManualNoticePayload(request.getAppUserId(), noticeTypeCode, referenceId));
+ command.setPayload(payload);
AppSystemNotice notice = appSystemNoticeDispatchService.createAndPush(command);
return toNoticePushVO(notice);
- }
-
- /**
- * 构造手动通知测试透传参数,便于APP端区分联调消息。
- */
- private Map<String, String> buildManualNoticePayload(Long appUserId, String noticeTypeCode, Long referenceId) {
- Map<String, String> payload = new LinkedHashMap<String, String>();
- payload.put("scene", MANUAL_NOTICE_TEST_SCENE);
- payload.put("noticeTypeCode", noticeTypeCode);
- payload.put("appUserId", String.valueOf(appUserId));
- payload.put("referenceId", String.valueOf(referenceId));
- return payload;
}
/**
diff --git a/tcm-training-admin-backend/src/main/resources/application.yml b/tcm-training-admin-backend/src/main/resources/application.yml
index 29724ec..1fbb6f2 100644
--- a/tcm-training-admin-backend/src/main/resources/application.yml
+++ b/tcm-training-admin-backend/src/main/resources/application.yml
@@ -121,8 +121,10 @@
# 苹果 JWKS 公钥地址
jwks-url: https://appleid.apple.com/auth/keys
share:
- # 邀请二维码默认指向线上下载页,开发环境可在 application-local.yml 覆盖。
- landing-base-url: https://ag8spw.oplinking.com/
+ # 邀请二维码默认指向 openinstall 下载页,开发环境可在 application-local.yml 覆盖。
+ landing-base-url: https://haizhentong.com/app-download/
+ # 二维码图片仍由后端公开接口生成,不能使用 openinstall 域名拼接。
+ qrcode-image-base-url: https://haizhentong.com/api/open/app-share/qrcode-image
jpush:
appKey: b5aed7d076638a0d835786bd
masterSecret: e17305459be4f0011ca15c9e
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 678d1fa..3bc0852 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
@@ -245,8 +245,8 @@
<div class="section">
<div class="section-title">安装说明</div>
<ol class="notice">
- <li>点击“打开 APP 继续注册”,页面会优先尝试打开 APP。</li>
- <li>若尚未安装,将自动下载对应平台安装包;安装完成后返回本页会继续打开 APP。</li>
+ <li>点击“打开 APP 继续注册”,页面会通过 openinstall 自动打开或引导安装 APP。</li>
+ <li>若尚未安装,安装后首次打开 APP 会自动带上当前邀请码。</li>
<li>普通用户注册成功后,将自动关联邀请医生所属诊所。</li>
</ol>
</div>
@@ -257,6 +257,7 @@
</div>
</div>
</div>
+<script src="https://res.opstatistics.com/openinstall-ag8spw.js"></script>
<script>
const state = {
inviteCode: '',
@@ -270,7 +271,10 @@
resumeWatchTimer: 0,
openConfirmTimer: 0,
openingApp: false,
- pendingResumeOpen: false
+ pendingResumeOpen: false,
+ openinstallInstance: null,
+ openinstallReady: false,
+ openinstallInitializing: false
}
const PENDING_OPEN_STORAGE_KEY = 'appInvitePendingOpen'
@@ -293,6 +297,7 @@
state.inviteCode = inviteCode
renderInviteCode(inviteCode)
await loadInviteLanding(inviteCode)
+ initOpeninstall()
}
/**
@@ -380,6 +385,9 @@
showStatus('当前二维码未包含邀请码,请复制邀请码后手动注册')
return
}
+ if (tryOpeninstallWakeupOrInstall()) {
+ return
+ }
const openAppUrl = await ensureOpenAppUrl()
if (!openAppUrl) {
return
@@ -394,6 +402,74 @@
return
}
scheduleDownloadFallback()
+ }
+
+ /**
+ * 初始化 openinstall Web SDK,并把邀请码作为安装和唤醒参数传递。
+ */
+ function initOpeninstall() {
+ if (!state.inviteCode || state.openinstallInstance || state.openinstallInitializing) {
+ return
+ }
+ if (typeof OpenInstall === 'undefined') {
+ showStatus('openinstall 组件加载失败,已启用普通下载方式')
+ return
+ }
+ state.openinstallInitializing = true
+ const wakeupData = buildOpeninstallWakeupData()
+ try {
+ state.openinstallInstance = new OpenInstall({
+ appKey: 'ag8spw',
+ fastInstall: false,
+ onready: function () {
+ state.openinstallReady = true
+ state.openinstallInitializing = false
+ updateButtonState()
+ this.schemeWakeup()
+ }
+ }, wakeupData)
+ } catch (error) {
+ state.openinstallInstance = null
+ state.openinstallReady = false
+ state.openinstallInitializing = false
+ showStatus('openinstall 初始化失败,已启用普通下载方式')
+ }
+ }
+
+ /**
+ * 使用 openinstall 打开或安装 APP,失败时由旧逻辑兜底。
+ */
+ function tryOpeninstallWakeupOrInstall() {
+ if (!state.openinstallInstance || !state.openinstallReady) {
+ initOpeninstall()
+ return false
+ }
+ try {
+ persistPendingOpen({
+ downloadStartedAt: Date.now(),
+ resumeAttempted: true
+ })
+ document.getElementById('flowTip').textContent = '正在通过 openinstall 打开或安装 APP,并保留当前邀请码。'
+ showStatus('正在打开或安装 APP,请稍候...')
+ state.openinstallInstance.wakeupOrInstall()
+ scheduleOpenFallbackConfirm()
+ return true
+ } catch (error) {
+ showStatus('openinstall 调用失败,已启用普通下载方式')
+ return false
+ }
+ }
+
+ /**
+ * 构造 openinstall 传参对象,字段需与 APP 端解析逻辑保持一致。
+ */
+ function buildOpeninstallWakeupData() {
+ const parsedParams = typeof OpenInstall !== 'undefined' && OpenInstall.parseUrlParams
+ ? OpenInstall.parseUrlParams()
+ : {}
+ return Object.assign({}, parsedParams || {}, {
+ inviteCode: state.inviteCode
+ })
}
/**
@@ -689,14 +765,16 @@
*/
function updateButtonState() {
const primaryActionBtn = document.getElementById('primaryActionBtn')
- primaryActionBtn.disabled = state.bindTicketLoading || (!state.androidDownloadUrl && !state.iosDownloadUrl)
+ primaryActionBtn.disabled = state.bindTicketLoading || state.openinstallInitializing || (!state.openinstallReady && !state.androidDownloadUrl && !state.iosDownloadUrl)
const pendingOpen = readPendingOpen()
if (pendingOpen && pendingOpen.inviteCode === state.inviteCode && pendingOpen.resumeAttempted) {
primaryActionBtn.textContent = '已安装,点此打开 APP'
return
}
- document.getElementById('flowTip').textContent = '点击下方按钮,已安装将直接打开 APP;未安装会自动下载。'
- primaryActionBtn.textContent = state.bindTicketLoading ? '正在准备注册信息...' : '打开 APP 继续注册'
+ document.getElementById('flowTip').textContent = state.openinstallReady
+ ? '点击下方按钮,openinstall 将自动打开或安装 APP,并保留邀请码。'
+ : '点击下方按钮,已安装将直接打开 APP;未安装会自动下载。'
+ primaryActionBtn.textContent = state.bindTicketLoading || state.openinstallInitializing ? '正在准备注册信息...' : '打开 APP 继续注册'
}
/**
diff --git a/traditionalApp/App.vue b/traditionalApp/App.vue
index 782cb6e..9baa077 100644
--- a/traditionalApp/App.vue
+++ b/traditionalApp/App.vue
@@ -7,6 +7,7 @@
extractBindTicket,
extractInviteCode
} from '@/utils/utils.js'
+ import { cacheInviteCode } from '@/utils/invite-cache.js'
import { initJPushLifecycle } from '@/utils/push.js'
import {
getOpeninstallInstallData,
@@ -61,6 +62,9 @@
if (!bindTicket && !inviteCode) {
return
}
+ if (inviteCode) {
+ cacheInviteCode(inviteCode)
+ }
const payloadKey = `${bindTicket || ''}_${inviteCode || ''}`
if (payloadKey === lastHandledInvitePayload) {
return
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/pages/clearCache/clearCache.vue b/traditionalApp/pages/clearCache/clearCache.vue
index 67cd744..b7e05af 100644
--- a/traditionalApp/pages/clearCache/clearCache.vue
+++ b/traditionalApp/pages/clearCache/clearCache.vue
@@ -27,6 +27,7 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
+import { getInviteCodeCacheKey } from '@/utils/invite-cache.js'
const statusBarHeight = ref(20)
const cacheSize = ref('0M')
@@ -100,6 +101,8 @@
const appUserInfo = uni.getStorageSync('appUserInfo')
const loginRedirectMeta = uni.getStorageSync('loginRedirectMeta')
const showLogoutModal = uni.getStorageSync('showLogoutModal')
+ const inviteCodeCacheKey = getInviteCodeCacheKey()
+ const cachedInviteCode = uni.getStorageSync(inviteCodeCacheKey)
uni.clearStorageSync()
if (token) {
uni.setStorageSync('token', token)
@@ -113,6 +116,9 @@
if (showLogoutModal !== '') {
uni.setStorageSync('showLogoutModal', showLogoutModal)
}
+ if (cachedInviteCode) {
+ uni.setStorageSync(inviteCodeCacheKey, cachedInviteCode)
+ }
// #ifdef APP-PLUS
const plusRef = typeof plus !== 'undefined' ? plus : null
if (plusRef && plusRef.cache && typeof plusRef.cache.clear === 'function') {
diff --git a/traditionalApp/pages/register/register.vue b/traditionalApp/pages/register/register.vue
index 0d7abf3..1934fa5 100644
--- a/traditionalApp/pages/register/register.vue
+++ b/traditionalApp/pages/register/register.vue
@@ -177,6 +177,7 @@
} from './service.js'
import { extractBindTicket, extractInviteCode, normalizeOptionalText } from '@/utils/utils.js'
import { reportOpeninstallRegister } from '@/utils/openinstall.js'
+import { cacheInviteCode, clearCachedInviteCode, getCachedInviteCode } from '@/utils/invite-cache.js'
import { confirmCameraForInviteCodeScan, confirmMediaForLicense } from '@/utils/permission.js'
interface RegionItem {
@@ -360,6 +361,7 @@
return
}
form.inviteCode = inviteCode
+ cacheInviteCode(inviteCode)
}
})
}
@@ -626,6 +628,12 @@
const routeCode = normalizeInviteCode(routeInviteCode)
if (routeCode) {
form.inviteCode = routeCode
+ cacheInviteCode(routeCode)
+ return
+ }
+ const cachedInviteCode = getCachedInviteCode()
+ if (cachedInviteCode) {
+ form.inviteCode = cachedInviteCode
}
}
@@ -819,6 +827,7 @@
const handleRegisterSuccess = async (message: string) => {
// 注册成功后通知 openinstall,用于统计推广链路的注册转化。
reportOpeninstallRegister()
+ clearCachedInviteCode()
uni.showToast({
title: message,
icon: 'success'
diff --git a/traditionalApp/utils/invite-cache.js b/traditionalApp/utils/invite-cache.js
new file mode 100644
index 0000000..e72e208
--- /dev/null
+++ b/traditionalApp/utils/invite-cache.js
@@ -0,0 +1,34 @@
+import { extractInviteCode } from './utils.js'
+
+const INVITE_CODE_CACHE_KEY = 'openinstallInviteCode'
+
+/**
+ * 缓存 openinstall 或扫码解析出的医生邀请码,支持稍后注册时继续绑定关系。
+ */
+export const cacheInviteCode = (value) => {
+ const inviteCode = extractInviteCode(value)
+ if (!inviteCode) {
+ return ''
+ }
+ uni.setStorageSync(INVITE_CODE_CACHE_KEY, inviteCode)
+ return inviteCode
+}
+
+/**
+ * 读取本地缓存的邀请码,只返回合法的 6 位业务码。
+ */
+export const getCachedInviteCode = () => {
+ return extractInviteCode(uni.getStorageSync(INVITE_CODE_CACHE_KEY))
+}
+
+/**
+ * 清理已经完成注册使用的邀请码缓存,避免后续账号误绑定旧关系。
+ */
+export const clearCachedInviteCode = () => {
+ uni.removeStorageSync(INVITE_CODE_CACHE_KEY)
+}
+
+/**
+ * 暴露缓存 key 给清缓存页面保留业务必要数据。
+ */
+export const getInviteCodeCacheKey = () => INVITE_CODE_CACHE_KEY
--
Gitblit v1.7.1