| tcm-training-admin-backend/src/main/resources/application.yml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| traditionalApp/App.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| traditionalApp/manifest.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| traditionalApp/pages/register/register.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| traditionalApp/utils/openinstall.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| traditionalApp/utils/utils.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
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 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> </style> 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", 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' traditionalApp/utils/openinstall.js
New file @@ -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 } } 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 : '' }