This commit is contained in:
parent
3313b95ab1
commit
6c296dbaaf
@ -73,9 +73,18 @@
|
|||||||
<div class="mx-auto h-[101px] w-[247px] md:h-[202px] md:w-[494px]">
|
<div class="mx-auto h-[101px] w-[247px] md:h-[202px] md:w-[494px]">
|
||||||
<div
|
<div
|
||||||
@click="handleDownload('ios')"
|
@click="handleDownload('ios')"
|
||||||
class="block h-full w-full cursor-pointer transition-transform active:scale-95"
|
class="relative block h-full w-full cursor-pointer transition-transform active:scale-95"
|
||||||
>
|
>
|
||||||
<img src="./Group%20100.png" class="h-full w-full" alt="下载" />
|
<img src="./Group%20100.png" class="h-full w-full" alt="下载" />
|
||||||
|
<!-- Loading Overlay -->
|
||||||
|
<div
|
||||||
|
v-if="isLoadingIos"
|
||||||
|
class="absolute inset-0 flex items-center justify-center rounded-[20px] bg-black/40"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="h-8 w-8 animate-spin rounded-full border-4 border-[#ADFF5B] border-t-transparent md:h-12 md:w-12"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -109,11 +118,21 @@
|
|||||||
<template v-for="client in otherClients" :key="client.type">
|
<template v-for="client in otherClients" :key="client.type">
|
||||||
<div
|
<div
|
||||||
@click="handleDownload(client.type)"
|
@click="handleDownload(client.type)"
|
||||||
class="cursor-pointer transition-transform hover:brightness-110 active:scale-95"
|
class="relative cursor-pointer transition-transform hover:brightness-110 active:scale-95"
|
||||||
>
|
>
|
||||||
<MacosIcon v-if="client.type === 'mac'" />
|
<MacosIcon v-if="client.type === 'mac'" />
|
||||||
<WindowsIcon v-if="client.type === 'window'" />
|
<WindowsIcon v-if="client.type === 'window'" />
|
||||||
<AndroidIcon v-if="client.type === 'android'" />
|
<AndroidIcon v-if="client.type === 'android'" />
|
||||||
|
|
||||||
|
<!-- Loading Overlay for Android -->
|
||||||
|
<div
|
||||||
|
v-if="client.type === 'android' && isLoadingAndroid"
|
||||||
|
class="absolute inset-0 flex items-center justify-center rounded-lg bg-black/40"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="h-6 w-6 animate-spin rounded-full border-2 border-[#ADFF5B] border-t-transparent"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -132,6 +151,7 @@ import WindowsIcon from './windows.svg?component'
|
|||||||
import AndroidIcon from './android.svg?component'
|
import AndroidIcon from './android.svg?component'
|
||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
import { getAllQueryString } from '@/utils/url-utils.ts'
|
import { getAllQueryString } from '@/utils/url-utils.ts'
|
||||||
|
import { toast } from 'vue-sonner'
|
||||||
|
|
||||||
import DownloadMethodList from './DownloadMethodList/DownloadMethodList.vue'
|
import DownloadMethodList from './DownloadMethodList/DownloadMethodList.vue'
|
||||||
import FAQAccordion from './FAQAccordion/index.vue'
|
import FAQAccordion from './FAQAccordion/index.vue'
|
||||||
@ -147,13 +167,46 @@ const handleDownload = async (key: string) => {
|
|||||||
const platform = platformMap[key] || 'windows'
|
const platform = platformMap[key] || 'windows'
|
||||||
const ic = getAllQueryString('ic') || sessionStorage.getItem('ic') || 'uSSfg1Y1vt'
|
const ic = getAllQueryString('ic') || sessionStorage.getItem('ic') || 'uSSfg1Y1vt'
|
||||||
|
|
||||||
if (platform === 'ios') {
|
// 设备环境检测
|
||||||
if (window.OI_SDK && window.OI_SDK.OI) {
|
const ua = navigator.userAgent
|
||||||
window.OI_SDK.OI.wakeupOrInstall({ data: { platform: 'download', inviteCode: ic } })
|
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
|
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 {
|
try {
|
||||||
const res: any = await request.get('/api/v1/common/client/download', {
|
const res: any = await request.get('/api/v1/common/client/download', {
|
||||||
invite_code: ic,
|
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 activeIndex = ref(0)
|
||||||
|
const isLoadingIos = ref(false)
|
||||||
|
const isLoadingAndroid = ref(false)
|
||||||
|
|
||||||
const otherClients = computed(() => {
|
const otherClients = computed(() => {
|
||||||
return [
|
return [
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
v-else
|
v-else
|
||||||
:id="mainButton?.id"
|
:id="mainButton?.id"
|
||||||
:aria-label="mainButton?.label"
|
: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"
|
class="flex h-full w-full cursor-pointer items-center justify-center transition-transform hover:brightness-110 active:scale-95"
|
||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
@ -68,7 +68,7 @@
|
|||||||
v-else
|
v-else
|
||||||
:id="item.id"
|
:id="item.id"
|
||||||
:aria-label="item.label"
|
:aria-label="item.label"
|
||||||
@click="item.key === 'win' ? handleDownload(item.key) : null"
|
@click="handleDownload(item.key)"
|
||||||
class="cursor-pointer transition-transform hover:brightness-110 active:scale-95"
|
class="cursor-pointer transition-transform hover:brightness-110 active:scale-95"
|
||||||
>
|
>
|
||||||
<component :is="item.secondaryIcon" class="h-[24px] text-white md:h-[34px]" />
|
<component :is="item.secondaryIcon" class="h-[24px] text-white md:h-[34px]" />
|
||||||
@ -142,10 +142,47 @@ const handleDownload = async (key: string) => {
|
|||||||
ios: 'ios',
|
ios: 'ios',
|
||||||
}
|
}
|
||||||
const platform = platformMap[key] || 'windows'
|
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 {
|
try {
|
||||||
const res: any = await request.get('/api/v1/common/client/download', {
|
const res: any = await request.get('/api/v1/common/client/download', {
|
||||||
invite_code: getAllQueryString('ic') || sessionStorage.getItem('ic') || 'uSSfg1Y1vt',
|
invite_code: ic,
|
||||||
platform: platform,
|
platform: platform,
|
||||||
})
|
})
|
||||||
if (res.url) {
|
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(() => [
|
const allDownloadOptions = computed(() => [
|
||||||
{
|
{
|
||||||
key: 'win',
|
key: 'win',
|
||||||
|
|||||||
@ -11,8 +11,13 @@ script.charset = 'UTF-8'
|
|||||||
script.src = 'https://web.cdn.openinstall.io/openinstall.js'
|
script.src = 'https://web.cdn.openinstall.io/openinstall.js'
|
||||||
document.head.appendChild(script)
|
document.head.appendChild(script)
|
||||||
|
|
||||||
|
let resolveReady: (sdk: OpenInstallSdk) => void
|
||||||
|
window.OI_SDK_PROMISE = new Promise((resolve) => {
|
||||||
|
resolveReady = resolve
|
||||||
|
})
|
||||||
|
|
||||||
script.addEventListener('load', () => {
|
script.addEventListener('load', () => {
|
||||||
window.OI_SDK = new OpenInstallSdk()
|
window.OI_SDK = new OpenInstallSdk(resolveReady)
|
||||||
})
|
})
|
||||||
|
|
||||||
class OpenInstallSdk {
|
class OpenInstallSdk {
|
||||||
@ -20,7 +25,9 @@ class OpenInstallSdk {
|
|||||||
|
|
||||||
public OI: Record<string, any> // openinstall 实例
|
public OI: Record<string, any> // openinstall 实例
|
||||||
|
|
||||||
constructor() {
|
public isReady = false
|
||||||
|
|
||||||
|
constructor(private onReadyCallback?: (sdk: OpenInstallSdk) => void) {
|
||||||
this.OI = {}
|
this.OI = {}
|
||||||
this.urlQuery = window.OpenInstall.parseUrlParams()
|
this.urlQuery = window.OpenInstall.parseUrlParams()
|
||||||
const id = getAllQueryString('id')
|
const id = getAllQueryString('id')
|
||||||
@ -34,47 +41,39 @@ class OpenInstallSdk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
|
const self = this
|
||||||
try {
|
try {
|
||||||
this.OI = new window.OpenInstall(
|
this.OI = new window.OpenInstall(
|
||||||
{
|
{
|
||||||
appKey: 'alf57p',
|
appKey: 'alf57p',
|
||||||
onready: function () {
|
onready: function () {
|
||||||
// 初始化成功回调方法。当初始化完成后,会自动进入
|
// 初始化成功回调方法。当初始化完成后,会自动进入
|
||||||
this.schemeWakeup() // 尝试使用scheme打开App(主要用于Android以及iOS的QQ环境中)
|
// 注意:此时的 this 是 OpenInstall 的原始实例对象 (m)
|
||||||
const m = this
|
const m = this
|
||||||
|
m.schemeWakeup() // 尝试使用scheme打开App(主要用于Android以及iOS的QQ环境中)
|
||||||
|
|
||||||
const button = document.getElementById('downloadButton_apple')
|
const button = document.getElementById('downloadButton_apple')
|
||||||
const button_mac = document.getElementById('downloadButton_mac')
|
const button_mac = document.getElementById('downloadButton_mac')
|
||||||
const button1 = document.getElementById('downloadButton_android')
|
const button1 = document.getElementById('downloadButton_android')
|
||||||
const ic = getAllQueryString('ic') || 'uSSfg1Y1vt'
|
const ic = getAllQueryString('ic') || 'uSSfg1Y1vt'
|
||||||
if (button) {
|
|
||||||
button.onclick = function () {
|
const clickHandler = function () {
|
||||||
if (ic) {
|
if (ic) {
|
||||||
m.wakeupOrInstall({ data: { platform: 'download', inviteCode: ic } })
|
m.wakeupOrInstall({ data: { platform: 'download', inviteCode: ic } })
|
||||||
} else {
|
} else {
|
||||||
m.wakeupOrInstall() // 此方法为scheme、Universal Link唤醒以及引导下载的作用(必须调用且不可额外自行跳转下载)
|
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
|
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)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user