From 6c296dbaafd144767331f2bc3d268eb75c7bba05 Mon Sep 17 00:00:00 2001 From: speakeloudest Date: Fri, 17 Apr 2026 14:35:48 +0300 Subject: [PATCH] =?UTF-8?q?=E5=A4=84=E7=90=86=E4=B8=8B=E8=BD=BD=E9=93=BE?= =?UTF-8?q?=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Help/index.vue | 83 ++++++++++++++++++-- src/pages/Home/components/DownloadButton.vue | 61 +++++++++++++- src/utils/openinstall.ts | 59 +++++++------- 3 files changed, 165 insertions(+), 38 deletions(-) diff --git a/src/pages/Help/index.vue b/src/pages/Help/index.vue index 195cbdd..29339c1 100644 --- a/src/pages/Help/index.vue +++ b/src/pages/Help/index.vue @@ -73,9 +73,18 @@
下载 + +
+
+
@@ -109,11 +118,21 @@ @@ -132,6 +151,7 @@ import WindowsIcon from './windows.svg?component' import AndroidIcon from './android.svg?component' import request from '@/utils/request' import { getAllQueryString } from '@/utils/url-utils.ts' +import { toast } from 'vue-sonner' import DownloadMethodList from './DownloadMethodList/DownloadMethodList.vue' import FAQAccordion from './FAQAccordion/index.vue' @@ -147,13 +167,46 @@ const handleDownload = async (key: string) => { const platform = platformMap[key] || 'windows' const ic = getAllQueryString('ic') || sessionStorage.getItem('ic') || 'uSSfg1Y1vt' - if (platform === 'ios') { - if (window.OI_SDK && window.OI_SDK.OI) { - window.OI_SDK.OI.wakeupOrInstall({ data: { platform: 'download', inviteCode: ic } }) + // 设备环境检测 + const ua = navigator.userAgent + const isAndroidDevice = /Android/i.test(ua) + const isIosDevice = + /iPhone|iPod|iPad/i.test(ua) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) + const isMobileDevice = isAndroidDevice || isIosDevice + + // 1. 安卓下载逻辑 + if (key === 'android') { + if (isAndroidDevice) { + isLoadingAndroid.value = true + await triggerOpenInstall(ic) + isLoadingAndroid.value = false + return + } else { + window.open( + 'https://api.hifast.biz/v1/common/client/download/file/Hi%E5%BF%ABVPN-android-1.0.0.apk', + '_blank', + ) return } } + // 2. iOS/Mac 下载逻辑 + if (key === 'ios' || key === 'mac') { + if (isMobileDevice) { + if (key === 'ios') isLoadingIos.value = true + await triggerOpenInstall(ic) + isLoadingIos.value = false + return + } else { + window.open( + 'https://apps.apple.com/us/app/hi%E5%BF%ABvpn/id6755683167?l=zh-Hans-CN', + '_blank', + ) + return + } + } + + // 3. 原有逻辑 (如 Windows) try { const res: any = await request.get('/api/v1/common/client/download', { invite_code: ic, @@ -167,7 +220,27 @@ const handleDownload = async (key: string) => { } } +// 提取 OpenInstall 触发逻辑 +const triggerOpenInstall = async (ic: string) => { + if (!(window as any).OI_SDK?.isReady && (window as any).OI_SDK_PROMISE) { + try { + await Promise.race([ + (window as any).OI_SDK_PROMISE, + new Promise((_, reject) => setTimeout(() => reject(new Error('OpenInstall timeout')), 3000)), + ]) + } catch (e) { + console.warn('OpenInstall readiness wait failed or timeout:', e) + } + } + + if ((window as any).OI_SDK && (window as any).OI_SDK.OI) { + ;(window as any).OI_SDK.OI.wakeupOrInstall({ data: { platform: 'download', inviteCode: ic } }) + } +} + const activeIndex = ref(0) +const isLoadingIos = ref(false) +const isLoadingAndroid = ref(false) const otherClients = computed(() => { return [ diff --git a/src/pages/Home/components/DownloadButton.vue b/src/pages/Home/components/DownloadButton.vue index 237b76e..0aa81af 100644 --- a/src/pages/Home/components/DownloadButton.vue +++ b/src/pages/Home/components/DownloadButton.vue @@ -30,7 +30,7 @@ v-else :id="mainButton?.id" :aria-label="mainButton?.label" - @click="mainButton.key === 'win' ? handleDownload(mainButton.key) : null" + @click="handleDownload(mainButton.key)" class="flex h-full w-full cursor-pointer items-center justify-center transition-transform hover:brightness-110 active:scale-95" > @@ -142,10 +142,47 @@ const handleDownload = async (key: string) => { ios: 'ios', } const platform = platformMap[key] || 'windows' + const ic = getAllQueryString('ic') || sessionStorage.getItem('ic') || 'uSSfg1Y1vt' + // 设备环境检测 + const ua = navigator.userAgent + const isAndroidDevice = /Android/i.test(ua) + const isIosDevice = + /iPhone|iPod|iPad/i.test(ua) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) + const isMobileDevice = isAndroidDevice || isIosDevice + + // 1. 安卓下载逻辑 + if (key === 'android') { + if (isAndroidDevice) { + await triggerOpenInstall(ic) + return + } else { + window.open( + 'https://api.hifast.biz/v1/common/client/download/file/Hi%E5%BF%ABVPN-android-1.0.0.apk', + '_blank', + ) + return + } + } + + // 2. iOS/Mac 下载逻辑 + if (key === 'ios' || key === 'mac') { + if (isMobileDevice) { + await triggerOpenInstall(ic) + return + } else { + window.open( + 'https://apps.apple.com/us/app/hi%E5%BF%ABvpn/id6755683167?l=zh-Hans-CN', + '_blank', + ) + return + } + } + + // 3. 原有逻辑 (如 Windows) try { const res: any = await request.get('/api/v1/common/client/download', { - invite_code: getAllQueryString('ic') || sessionStorage.getItem('ic') || 'uSSfg1Y1vt', + invite_code: ic, platform: platform, }) if (res.url) { @@ -159,6 +196,24 @@ const handleDownload = async (key: string) => { } } +// 提取 OpenInstall 触发逻辑 +const triggerOpenInstall = async (ic: string) => { + if (!(window as any).OI_SDK?.isReady && (window as any).OI_SDK_PROMISE) { + try { + await Promise.race([ + (window as any).OI_SDK_PROMISE, + new Promise((_, reject) => setTimeout(() => reject(new Error('OpenInstall timeout')), 3000)), + ]) + } catch (e) { + console.warn('OpenInstall readiness wait failed or timeout:', e) + } + } + + if ((window as any).OI_SDK && (window as any).OI_SDK.OI) { + ;(window as any).OI_SDK.OI.wakeupOrInstall({ data: { platform: 'download', inviteCode: ic } }) + } +} + const allDownloadOptions = computed(() => [ { key: 'win', diff --git a/src/utils/openinstall.ts b/src/utils/openinstall.ts index df5c01d..c7fd225 100644 --- a/src/utils/openinstall.ts +++ b/src/utils/openinstall.ts @@ -11,8 +11,13 @@ script.charset = 'UTF-8' script.src = 'https://web.cdn.openinstall.io/openinstall.js' document.head.appendChild(script) +let resolveReady: (sdk: OpenInstallSdk) => void +window.OI_SDK_PROMISE = new Promise((resolve) => { + resolveReady = resolve +}) + script.addEventListener('load', () => { - window.OI_SDK = new OpenInstallSdk() + window.OI_SDK = new OpenInstallSdk(resolveReady) }) class OpenInstallSdk { @@ -20,7 +25,9 @@ class OpenInstallSdk { public OI: Record // openinstall 实例 - constructor() { + public isReady = false + + constructor(private onReadyCallback?: (sdk: OpenInstallSdk) => void) { this.OI = {} this.urlQuery = window.OpenInstall.parseUrlParams() const id = getAllQueryString('id') @@ -34,47 +41,39 @@ class OpenInstallSdk { } async init() { + const self = this try { this.OI = new window.OpenInstall( { appKey: 'alf57p', onready: function () { // 初始化成功回调方法。当初始化完成后,会自动进入 - this.schemeWakeup() // 尝试使用scheme打开App(主要用于Android以及iOS的QQ环境中) + // 注意:此时的 this 是 OpenInstall 的原始实例对象 (m) const m = this + m.schemeWakeup() // 尝试使用scheme打开App(主要用于Android以及iOS的QQ环境中) + const button = document.getElementById('downloadButton_apple') const button_mac = document.getElementById('downloadButton_mac') const button1 = document.getElementById('downloadButton_android') const ic = getAllQueryString('ic') || 'uSSfg1Y1vt' - if (button) { - button.onclick = function () { - if (ic) { - m.wakeupOrInstall({ data: { platform: 'download', inviteCode: ic } }) - } else { - m.wakeupOrInstall() // 此方法为scheme、Universal Link唤醒以及引导下载的作用(必须调用且不可额外自行跳转下载) - } - return false + + const clickHandler = function () { + if (ic) { + m.wakeupOrInstall({ data: { platform: 'download', inviteCode: ic } }) + } else { + m.wakeupOrInstall() } + return false } - if (button_mac) { - button_mac.onclick = function () { - if (ic) { - m.wakeupOrInstall({ data: { platform: 'download', inviteCode: ic } }) - } else { - m.wakeupOrInstall() // 此方法为scheme、Universal Link唤醒以及引导下载的作用(必须调用且不可额外自行跳转下载) - } - return false - } - } - if (button1) { - button1.onclick = function () { - if (ic) { - m.wakeupOrInstall({ data: { platform: 'download', inviteCode: ic } }) - } else { - m.wakeupOrInstall() // 此方法为scheme、Universal Link唤醒以及引导下载的作用(必须调用且不可额外自行跳转下载) - } - return false - } + + if (button) button.onclick = clickHandler + if (button_mac) button_mac.onclick = clickHandler + if (button1) button1.onclick = clickHandler + + // 标记就绪并触发 Promise + self.isReady = true + if (self.onReadyCallback) { + self.onReadyCallback(self) } }, },