“mitao”
2026-04-30 1c1769a9e97d3dc68fe35c56af13e9e5a7fc209a
feat:openinstall接入
1个文件已添加
5个文件已修改
316 ■■■■■ 已修改文件
tcm-training-admin-backend/src/main/resources/application.yml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/App.vue 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/manifest.json 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/pages/register/register.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/utils/openinstall.js 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/utils/utils.js 84 ●●●●● 补丁 | 查看 | 原始文档 | 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 : ''
}