From 1c1769a9e97d3dc68fe35c56af13e9e5a7fc209a Mon Sep 17 00:00:00 2001
From: “mitao” <“2763622819@qq.com”>
Date: 星期四, 30 四月 2026 11:25:21 +0800
Subject: [PATCH] feat:openinstall接入
---
traditionalApp/pages/register/register.vue | 3
traditionalApp/utils/openinstall.js | 98 ++++++++++++++++
tcm-training-admin-backend/src/main/resources/application.yml | 2
traditionalApp/utils/utils.js | 84 ++++++++++++-
traditionalApp/App.vue | 93 ++++++++++-----
traditionalApp/manifest.json | 36 +++++
6 files changed, 272 insertions(+), 44 deletions(-)
diff --git a/tcm-training-admin-backend/src/main/resources/application.yml b/tcm-training-admin-backend/src/main/resources/application.yml
index 97ed108..29724ec 100644
--- a/tcm-training-admin-backend/src/main/resources/application.yml
+++ b/tcm-training-admin-backend/src/main/resources/application.yml
@@ -122,7 +122,7 @@
jwks-url: https://appleid.apple.com/auth/keys
share:
# 邀请二维码默认指向线上下载页,开发环境可在 application-local.yml 覆盖。
- landing-base-url: https://haizhentong.com/app-download/
+ landing-base-url: https://ag8spw.oplinking.com/
jpush:
appKey: b5aed7d076638a0d835786bd
masterSecret: e17305459be4f0011ca15c9e
diff --git a/traditionalApp/App.vue b/traditionalApp/App.vue
index 2fd1ad1..57d9944 100644
--- a/traditionalApp/App.vue
+++ b/traditionalApp/App.vue
@@ -7,60 +7,90 @@
extractBindTicket,
extractInviteCode
} from '@/utils/utils.js'
+ import {
+ getOpeninstallInstallData,
+ initOpeninstall,
+ registerOpeninstallWakeUp
+ } from '@/utils/openinstall.js'
- let lastHandledBindTicket = ''
+ let lastHandledInvitePayload = ''
/**
- * 应用启动时保留钩子,当前不再依赖系统剪贴板承接推广关系。
+ * 应用启动时初始化极光与 openinstall,承接推广关系参数。
*/
onLaunch(() => {
// #ifdef APP-PLUS
- const jpushModule = uni.requireNativePlugin("JG-JPush")
-
- jpushModule.setLoggerEnable(true);
- jpushModule.initJPushService();
- // 监听是否连接成功
- jpushModule.addConnectEventListener(result => {
- console.log('qweqweqwe', result);
- let connectEnable = result.connectEnable;
- console.log(connectEnable, '极光连接成功');
- if (uni.getStorageSync('token')) {
-
- }
- });
-
- jpushModule.addNotificationListener(result => {
- console.log('qeqwewqe', result);
- // plus.runtime.setBadgeNumber('111111');
- // jpushModule.setBadge('2222222222222')
-
-
- });
+ initJPush()
+ initOpeninstall()
+ registerOpeninstallWakeUp(handleOpeninstallPayload)
+ getOpeninstallInstallData(handleOpeninstallPayload)
// #endif
})
/**
- * App 被外部 Scheme 拉起时,解析 bindTicket 并跳转注册页。
+ * App 被外部 Scheme 拉起时,解析 bindTicket 或 inviteCode 并跳转注册页。
*/
onShow(() => {
handleOpenByScheme()
})
/**
- * 读取原生启动参数中的绑定票据,并在命中时跳转注册页。
+ * 初始化极光推送服务。
+ */
+ function initJPush() {
+ // #ifdef APP-PLUS
+ const jpushModule = uni.requireNativePlugin('JG-JPush')
+
+ jpushModule.setLoggerEnable(true)
+ jpushModule.initJPushService()
+ // 监听极光连接状态,便于联调确认服务是否可用。
+ jpushModule.addConnectEventListener(result => {
+ console.log('极光连接状态', result)
+ })
+
+ jpushModule.addNotificationListener(result => {
+ console.log('极光通知回调', result)
+ })
+ // #endif
+ }
+
+ /**
+ * 读取原生启动参数中的绑定票据或邀请码,并在命中时跳转注册页。
*/
function handleOpenByScheme() {
// #ifdef APP-PLUS
const runtimeArguments = typeof plus !== 'undefined' && plus.runtime ? plus.runtime.arguments : ''
- const bindTicket = extractBindTicket(runtimeArguments)
- if (!bindTicket || bindTicket === lastHandledBindTicket) {
+ handleInvitePayload(runtimeArguments)
+ // #endif
+ }
+
+ /**
+ * 处理 openinstall 安装参数或唤醒参数。
+ */
+ function handleOpeninstallPayload(payload) {
+ handleInvitePayload(payload)
+ }
+
+ /**
+ * 统一解析外部推广参数,并跳转到注册页回填。
+ */
+ function handleInvitePayload(payload) {
+ const bindTicket = extractBindTicket(payload)
+ const inviteCode = extractInviteCode(payload)
+ if (!bindTicket && !inviteCode) {
return
}
- lastHandledBindTicket = bindTicket
- const inviteCode = extractInviteCode(runtimeArguments)
+ const payloadKey = `${bindTicket || ''}_${inviteCode || ''}`
+ if (payloadKey === lastHandledInvitePayload) {
+ return
+ }
+ lastHandledInvitePayload = payloadKey
const currentPages = getCurrentPages()
const currentRoute = currentPages && currentPages.length > 0 ? currentPages[currentPages.length - 1].route : ''
- const targetParams = [`bindTicket=${encodeURIComponent(bindTicket)}`]
+ const targetParams = []
+ if (bindTicket) {
+ targetParams.push(`bindTicket=${encodeURIComponent(bindTicket)}`)
+ }
if (inviteCode) {
targetParams.push(`inviteCode=${encodeURIComponent(inviteCode)}`)
}
@@ -74,7 +104,6 @@
uni.navigateTo({
url: targetUrl
})
- // #endif
}
</script>
@@ -82,4 +111,4 @@
/*每个页面公共css */
@import "@/uni_modules/uview-pro/index.scss";
@import "@/static/style/index.scss";
-</style>
\ No newline at end of file
+</style>
diff --git a/traditionalApp/manifest.json b/traditionalApp/manifest.json
index 4995e98..d129b26 100644
--- a/traditionalApp/manifest.json
+++ b/traditionalApp/manifest.json
@@ -48,7 +48,8 @@
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
],
"minSdkVersion": 28,
- "targetSdkVersion": 30
+ "targetSdkVersion": 30,
+ "schemes": "ag8spw"
},
/* ios打包配置 */
"ios": {
@@ -61,11 +62,13 @@
"capabilities": {
"entitlements": {
"com.apple.developer.associated-domains": [
- "applinks:static-mp-dd6d96de-bdf2-4284-a975-638af4cc8ec8.next.bspapp.com"
+ "applinks:static-mp-dd6d96de-bdf2-4284-a975-638af4cc8ec8.next.bspapp.com",
+ "applinks:ag8spw.oplinking.com"
]
}
},
- "idfa": false
+ "idfa": false,
+ "urltypes": "ag8spw"
},
/* SDK配置 */
"sdkConfigs": {
@@ -129,6 +132,33 @@
}
},
"nativePlugins": {
+ "openinstall-plugin": {
+ "APP_KEY_ANDROID": "ag8spw",
+ "APP_KEY_IOS": "ag8spw",
+ "__plugin_info__": {
+ "name": "openinstall",
+ "description": "openinstall 一键拉起与免填邀请码插件",
+ "platforms": "Android,iOS",
+ "url": "https://ext.dcloud.net.cn/plugin?id=692",
+ "android_package_name": "com.HaiZhenKangDao.hzt",
+ "ios_bundle_id": "com.HaiZhenKangDao.hzt",
+ "isCloud": true,
+ "bought": 1,
+ "pid": "692",
+ "parameters": {
+ "APP_KEY_ANDROID": {
+ "des": "[Android]openinstall 控制台分配的 AppKey",
+ "key": "com.openinstall.APP_KEY",
+ "value": ""
+ },
+ "APP_KEY_IOS": {
+ "des": "[iOS]openinstall 控制台分配的 AppKey",
+ "key": "com.openinstall.APP_KEY",
+ "value": ""
+ }
+ }
+ }
+ },
"JG-JCore": {
"JPUSH_APPKEY_ANDROID": "b5aed7d076638a0d835786bd",
"JPUSH_APPKEY_IOS": "b5aed7d076638a0d835786bd",
diff --git a/traditionalApp/pages/register/register.vue b/traditionalApp/pages/register/register.vue
index 5902300..0d7abf3 100644
--- a/traditionalApp/pages/register/register.vue
+++ b/traditionalApp/pages/register/register.vue
@@ -176,6 +176,7 @@
validateInviteBindTicket
} from './service.js'
import { extractBindTicket, extractInviteCode, normalizeOptionalText } from '@/utils/utils.js'
+import { reportOpeninstallRegister } from '@/utils/openinstall.js'
import { confirmCameraForInviteCodeScan, confirmMediaForLicense } from '@/utils/permission.js'
interface RegionItem {
@@ -816,6 +817,8 @@
* 注册成功后提示并返回登录页。
*/
const handleRegisterSuccess = async (message: string) => {
+ // 注册成功后通知 openinstall,用于统计推广链路的注册转化。
+ reportOpeninstallRegister()
uni.showToast({
title: message,
icon: 'success'
diff --git a/traditionalApp/utils/openinstall.js b/traditionalApp/utils/openinstall.js
new file mode 100644
index 0000000..7faae0b
--- /dev/null
+++ b/traditionalApp/utils/openinstall.js
@@ -0,0 +1,98 @@
+let openinstallPlugin = null
+let initialized = false
+let wakeUpRegistered = false
+
+/**
+ * 安全获取 openinstall 原生插件实例,非 APP 或插件不存在时返回空。
+ */
+export const getOpeninstallPlugin = () => {
+ // #ifdef APP-PLUS
+ if (openinstallPlugin) {
+ return openinstallPlugin
+ }
+ try {
+ openinstallPlugin = uni.requireNativePlugin('openinstall-plugin')
+ } catch (error) {
+ console.log('openinstall 插件不可用', error)
+ openinstallPlugin = null
+ }
+ return openinstallPlugin
+ // #endif
+ // #ifndef APP-PLUS
+ return null
+ // #endif
+}
+
+/**
+ * 初始化 openinstall 插件,避免多次启动重复初始化。
+ */
+export const initOpeninstall = () => {
+ const plugin = getOpeninstallPlugin()
+ if (!plugin || initialized) {
+ return false
+ }
+ try {
+ plugin.init()
+ initialized = true
+ return true
+ } catch (error) {
+ console.log('openinstall 初始化失败', error)
+ return false
+ }
+}
+
+/**
+ * 注册 openinstall 拉起参数监听,用于已安装 APP 的扫码唤醒场景。
+ */
+export const registerOpeninstallWakeUp = (callback) => {
+ const plugin = getOpeninstallPlugin()
+ if (!plugin || wakeUpRegistered || typeof callback !== 'function') {
+ return false
+ }
+ try {
+ plugin.registerWakeUp((result) => {
+ callback(result)
+ })
+ wakeUpRegistered = true
+ return true
+ } catch (error) {
+ console.log('openinstall 拉起监听注册失败', error)
+ return false
+ }
+}
+
+/**
+ * 读取 openinstall 安装参数,用于未安装 APP 下载后首次打开场景。
+ */
+export const getOpeninstallInstallData = (callback) => {
+ const plugin = getOpeninstallPlugin()
+ if (!plugin || typeof callback !== 'function') {
+ return false
+ }
+ try {
+ plugin.getInstall(8, (result) => {
+ callback(result)
+ })
+ return true
+ } catch (error) {
+ console.log('openinstall 安装参数获取失败', error)
+ return false
+ }
+}
+
+/**
+ * 注册成功后上报 openinstall,便于后台统计注册转化。
+ */
+export const reportOpeninstallRegister = () => {
+ const plugin = getOpeninstallPlugin()
+ if (!plugin) {
+ return false
+ }
+ try {
+ plugin.reportRegister()
+ return true
+ } catch (error) {
+ console.log('openinstall 注册上报失败', error)
+ return false
+ }
+}
diff --git a/traditionalApp/utils/utils.js b/traditionalApp/utils/utils.js
index f129ac8..7032f75 100644
--- a/traditionalApp/utils/utils.js
+++ b/traditionalApp/utils/utils.js
@@ -225,33 +225,101 @@
}
/**
- * 从 URL、纯文本或扫码内容中提取 6 位邀请码。
+ * 将外部唤醒参数归一化为可解析文本,兼容 openinstall 对象、JSON 和 URL。
+ */
+export const normalizeInvitePayloadText = (value) => {
+ if (!value) {
+ return ''
+ }
+ if (typeof value === 'string') {
+ return value.trim()
+ }
+ if (typeof value !== 'object') {
+ return ''
+ }
+ const candidateKeys = ['bindData', 'data', 'channelData', 'params', 'wakeUpData']
+ for (const key of candidateKeys) {
+ const nestedValue = value[key]
+ if (nestedValue) {
+ const nestedText = normalizeInvitePayloadText(nestedValue)
+ if (nestedText) {
+ return nestedText
+ }
+ }
+ }
+ try {
+ return JSON.stringify(value)
+ } catch {
+ return ''
+ }
+}
+
+/**
+ * 从 URL、纯文本、对象或扫码内容中提取 6 位邀请码。
*/
export const extractInviteCode = (value) => {
- const normalizedValue = normalizeOptionalText(value)
+ const normalizedValue = normalizeInvitePayloadText(value)
if (!normalizedValue) {
return ''
}
- const queryMatchedInviteCode = normalizedValue.match(/[?&]inviteCode=([^&#]+)/i)
+ const objectInviteCode = extractObjectField(normalizedValue, 'inviteCode')
+ if (objectInviteCode) {
+ return normalizeInviteCodeText(objectInviteCode)
+ }
+ const queryMatchedInviteCode = normalizedValue.match(/(?:^|[?&])inviteCode=([^&#]+)/i)
if (queryMatchedInviteCode && queryMatchedInviteCode[1]) {
- const decodedInviteCode = decodeURIComponent(queryMatchedInviteCode[1]).trim().toUpperCase()
- return /^[A-Z0-9]{6}$/.test(decodedInviteCode) ? decodedInviteCode : ''
+ return normalizeInviteCodeText(decodeURIComponent(queryMatchedInviteCode[1]))
}
const matchedInviteCode = normalizedValue.toUpperCase().match(/[A-Z0-9]{6}/)
return matchedInviteCode ? matchedInviteCode[0] : ''
}
/**
- * 从 URL 中提取 bindTicket 参数。
+ * 从 URL、对象或 JSON 文本中提取 bindTicket 参数。
*/
export const extractBindTicket = (value) => {
- const normalizedValue = normalizeOptionalText(value)
+ const normalizedValue = normalizeInvitePayloadText(value)
if (!normalizedValue) {
return ''
}
- const queryMatchedBindTicket = normalizedValue.match(/[?&]bindTicket=([^&#]+)/i)
+ const objectBindTicket = extractObjectField(normalizedValue, 'bindTicket')
+ if (objectBindTicket) {
+ return objectBindTicket.trim()
+ }
+ const queryMatchedBindTicket = normalizedValue.match(/(?:^|[?&])bindTicket=([^&#]+)/i)
if (!queryMatchedBindTicket || !queryMatchedBindTicket[1]) {
return ''
}
return decodeURIComponent(queryMatchedBindTicket[1]).trim()
}
+
+/**
+ * 从 JSON 字符串中读取指定字段。
+ */
+const extractObjectField = (value, fieldName) => {
+ try {
+ const parsedValue = JSON.parse(value)
+ if (!parsedValue || typeof parsedValue !== 'object') {
+ return ''
+ }
+ const directValue = parsedValue[fieldName]
+ if (typeof directValue === 'string') {
+ return directValue
+ }
+ const bindDataValue = parsedValue.bindData || parsedValue.data || parsedValue.params
+ if (bindDataValue && bindDataValue !== value) {
+ return extractObjectField(normalizeInvitePayloadText(bindDataValue), fieldName)
+ }
+ } catch {
+ return ''
+ }
+ return ''
+}
+
+/**
+ * 标准化邀请码文本。
+ */
+const normalizeInviteCodeText = (value) => {
+ const inviteCode = normalizeOptionalText(value).toUpperCase()
+ return /^[A-Z0-9]{6}$/.test(inviteCode) ? inviteCode : ''
+}
--
Gitblit v1.7.1