Compare commits

...

11 Commits

Author SHA1 Message Date
sepakeloudest 4ce76304cb 增加海外appID标识
site-dist-deploy / build-and-deploy (push) Failing after 16m17s
2026-06-15 15:29:29 +08:00
sepakeloudest 5e90f22f59 删除限速说明
site-dist-deploy / build-and-deploy (push) Successful in 1m48s
2026-05-19 22:33:32 +08:00
sepakeloudest a19ef7c7ab 增加下载默认参数
site-dist-deploy / build-and-deploy (push) Successful in 1m29s
2026-05-05 04:51:00 +03:00
sepakeloudest 4297d5fd29 处理下载页的平台判断
site-dist-deploy / build-and-deploy (push) Successful in 1m32s
2026-04-20 04:24:23 +03:00
sepakeloudest ee8ce4c559 添加默认邀请人,download@hifast.biz 'uSSfgY6oAP'
site-dist-deploy / build-and-deploy (push) Successful in 1m33s
2026-04-10 06:00:35 +03:00
sepakeloudest 1e26abf1d8 处理为openinstall
site-dist-deploy / build-and-deploy (push) Successful in 1m25s
2026-04-07 04:06:59 +03:00
sepakeloudest 5a0f82704d 增加adjust web sdk
site-dist-deploy / build-and-deploy (push) Successful in 1m41s
2026-04-04 03:23:19 +03:00
sepakeloudest d629b360a0 增加adjust web sdk
site-dist-deploy / build-and-deploy (push) Successful in 1m36s
2026-04-02 07:27:16 +03:00
sepakeloudest 681ad15228 邀请链接替换ios
site-dist-deploy / build-and-deploy (push) Successful in 1m47s
2026-03-30 06:36:30 +03:00
sepakeloudest 1d88c4ea6b 邀请链接替换ios
site-dist-deploy / build-and-deploy (push) Successful in 1m59s
2026-03-30 05:26:44 +03:00
sepakeloudest 0ca0a3fc4d 修改邀请链接 2026-03-26 04:22:47 +02:00
9 changed files with 309 additions and 95 deletions
+1
View File
@@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"@adjustcom/adjust-web-sdk": "^5.8.2",
"@tailwindcss/vite": "^4.1.18",
"axios": "^1.13.2",
"clsx": "^2.1.1",
+16
View File
@@ -8,6 +8,9 @@ importers:
.:
dependencies:
'@adjustcom/adjust-web-sdk':
specifier: ^5.8.2
version: 5.8.2
'@tailwindcss/vite':
specifier: ^4.1.18
version: 4.1.18(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2))
@@ -72,6 +75,9 @@ importers:
packages:
'@adjustcom/adjust-web-sdk@5.8.2':
resolution: {integrity: sha512-6ov2EKKuPQi7i+0CdtsGZ7REIE8jRlg++muAjqpR4ySLjOXTgxVSmWjsTBohxnTVDDL2OGEMfXFUG6ZIhyvl9Q==}
'@babel/helper-string-parser@7.27.1':
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'}
@@ -85,6 +91,10 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/runtime@7.29.2':
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
engines: {node: '>=6.9.0'}
'@babel/types@7.28.6':
resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==}
engines: {node: '>=6.9.0'}
@@ -1094,6 +1104,10 @@ packages:
snapshots:
'@adjustcom/adjust-web-sdk@5.8.2':
dependencies:
'@babel/runtime': 7.29.2
'@babel/helper-string-parser@7.27.1': {}
'@babel/helper-validator-identifier@7.28.5': {}
@@ -1102,6 +1116,8 @@ snapshots:
dependencies:
'@babel/types': 7.28.6
'@babel/runtime@7.29.2': {}
'@babel/types@7.28.6':
dependencies:
'@babel/helper-string-parser': 7.27.1
+2 -1
View File
@@ -1,6 +1,7 @@
import { createApp } from 'vue'
import './styles/index.css'
import App from './App.vue'
import '@/utils/openinstall.ts'
import '@/utils/openinstall'
import '@/utils/adjust'
createApp(App).mount('#app')
+139 -51
View File
@@ -15,12 +15,28 @@
v-else
:id="mainButton?.id"
:aria-label="mainButton?.label"
@click="handleDownload(mainButton.key)"
class="flex h-full w-full cursor-pointer items-center justify-center transition-transform hover:brightness-110 active:scale-95"
>
<component :is="mainButton?.mainIcon" class="h-auto w-full text-black transition-transform" />
</div>
<!-- 主按钮 Loading 状态 -->
<div v-if="loadingKey === mainButton?.key" class="absolute inset-0 flex items-center justify-center bg-[#ADFF5B]">
<div class="h-6 w-6 animate-spin rounded-full border-4 border-black/20 border-t-black md:h-8 md:w-8"></div>
</div>
</div>
<a
v-if="isApplePlatform"
:href="helpLink"
target="_blank"
rel="noopener noreferrer"
class="mx-auto mt-1 block w-[210px] text-center text-[16px] leading-[20px] font-semibold text-[#ADFF5B] underline decoration-solid underline-offset-2 text-shadow-[0_0_4.9px_rgba(0,0,0,0.58)] transition-opacity hover:opacity-85 active:opacity-70 md:ml-0 md:w-[300px] md:mt-4"
>
没有海外Apple ID
</a>
<!-- 其他版本下载 -->
<div class="w-full mt-2 md:mt-[35px]">
<div class="mb-3 text-center md:text-left">
@@ -29,22 +45,30 @@
<div class="flex gap-4 items-center justify-center md:justify-start">
<template v-for="(item, index) in otherButtons" :key="index">
<a
v-if="item.link"
:href="item.link"
target="_blank"
:aria-label="item.label"
class="transition-transform hover:brightness-110 active:scale-95"
>
<component :is="item.secondaryIcon" class="h-[24px] md:h-[34px] text-white" />
</a>
<div
v-else
:id="item.id"
:aria-label="item.label"
class="cursor-pointer transition-transform hover:brightness-110 active:scale-95"
>
<component :is="item.secondaryIcon" class="h-[24px] md:h-[34px] text-white" />
<div class="relative">
<a
v-if="item.link"
:href="item.link"
target="_blank"
:aria-label="item.label"
class="transition-transform hover:brightness-110 active:scale-95"
>
<component :is="item.secondaryIcon" class="h-[24px] md:h-[34px] text-white" />
</a>
<div
v-else
:id="item.id"
:aria-label="item.label"
@click="handleDownload(item.key)"
class="cursor-pointer transition-transform hover:brightness-110 active:scale-95"
>
<component :is="item.secondaryIcon" class="h-[24px] md:h-[34px] text-white" />
</div>
<!-- 次要按钮 Loading 状态 -->
<div v-if="loadingKey === item.key" class="absolute inset-0 flex items-center justify-center bg-black/40 rounded-lg">
<div class="h-4 w-4 animate-spin rounded-full border-2 border-white/20 border-t-white md:h-5 md:w-5"></div>
</div>
</div>
</template>
</div>
@@ -66,59 +90,123 @@ import request from '@/utils/request'
import {computed, ref, onMounted} from "vue";
import {getAllQueryString} from "@/utils/url-utils.ts";
const downLoadWin = ref('')
const downLoadMac = ref('')
const currentPlatform = ref('win')
const loadingKey = ref('')
// 固定下载链接常量
const FIXED_LINKS: Record<string, string> = {
android: 'https://api.hifast.biz/v1/common/client/download/file/Hi%E5%BF%ABVPN-android-1.0.0.apk',
ios: 'https://apps.apple.com/us/app/hi%E5%BF%ABvpn/id6755683167?l=zh-Hans-CN',
mac: 'https://apps.apple.com/us/app/hi%E5%BF%ABvpn/id6755683167?l=zh-Hans-CN',
}
const isAndroid = computed(() => currentPlatform.value === 'android')
const isIOS = computed(() => currentPlatform.value === 'ios')
const isApplePlatform = computed(() => currentPlatform.value === 'ios' || currentPlatform.value === 'mac')
const helpLink = computed(() => {
const ic = getAllQueryString('ic') || 'uSSfgY6oAP'
return `https://hifastvpn.com/help?ic=${ic}`
})
onMounted(() => {
const ua = navigator.userAgent;
const platform = navigator.platform || ''; // 辅助判断
// 1. 先判断 Android (通常比较稳定)
if (/Android/i.test(ua)) {
currentPlatform.value = 'android';
}
// 2. 判断 iOS 设备 (iPhone, iPod)
else if (/iPhone|iPod/i.test(ua)) {
} else if (/iPhone|iPod/i.test(ua)) {
currentPlatform.value = 'ios';
}
// 3. 核心改进:区分 Mac 和 iPad
else if (/Macintosh|Mac OS X/i.test(ua)) {
// iPadOS 桌面模式下,支持多点触控(通常 > 1)
// 而真正的 Mac 电脑通常 maxTouchPoints 为 0 或 undefined
} else if (/Macintosh|Mac OS X/i.test(ua)) {
if (navigator.maxTouchPoints > 1) {
currentPlatform.value = 'ios'; // 归类为 iOS 端(iPad
currentPlatform.value = 'ios'; // iPad
} else {
currentPlatform.value = 'mac';
}
}
// 4. 其他情况默认为 Windows
else {
} else {
currentPlatform.value = 'win';
}
console.log('Detected Platform:', currentPlatform.value);
});
// request.get('/api/v1/common/client/download', {
// invite_code: getAllQueryString('ic'),
// platform: 'mac',
// }).then((res: any) => {
// downLoadMac.value = res.url
// })
/**
* 核心下载处理函数
* @param key 平台标识 (win, mac, ios, android)
*/
const handleDownload = async (key: string) => {
const ic = getAllQueryString('ic') || 'uSSfgY6oAP'
const isMain = key === currentPlatform.value;
request.get('/api/v1/common/client/download', {
invite_code: getAllQueryString('ic'),
platform: 'windows',
}).then((res: any) => {
downLoadWin.value = res.url
})
// 1. Windows 逻辑:点击时动态请求 API
if (key === 'win') {
try {
const res: any = await request.get('/api/v1/common/client/download', {
invite_code: ic,
platform: 'windows',
})
if (res.url) {
window.open(res.url, '_blank')
}
} catch (error) {
console.error('Windows download failed', error)
}
return
}
// 2. 移动端主按钮逻辑:调用 OpenInstall 统计
if (isMain) {
if ((isAndroid.value && key === 'android') || (isIOS.value && (key === 'ios' || key === 'mac'))) {
triggerOpenInstall(ic, key)
return
}
}
// 3. 其他所有情况(次要按钮或非匹配平台):直接跳转固定链接
const url = FIXED_LINKS[key]
if (url) {
window.open(url, '_blank')
}
}
// 触发 OpenInstall 的统一方法
const triggerOpenInstall = (ic: string, key: string) => {
loadingKey.value = key
setTimeout(() => { loadingKey.value = '' }, 3000)
if (window.OI_SDK?.OI?.wakeupOrInstall) {
window.OI_SDK.OI.wakeupOrInstall({
data: { platform: 'download', inviteCode: ic }
})
} else {
console.warn('OpenInstall SDK not ready')
}
}
const allDownloadOptions = computed(() => [
{ key: 'win', mainIcon: Icon1, secondaryIcon: WinIcon, link: downLoadWin.value, label: 'Windows', id: 'downloadButton_win' },
{ key: 'mac', mainIcon: Icon3, secondaryIcon: MacIcon, label: 'macOS', id: 'downloadButton_mac' },
{ key: 'ios', mainIcon: Icon2, secondaryIcon: AppleIcon, label: 'iOS', id: 'downloadButton_apple' },
{ key: 'android', mainIcon: Icon4, secondaryIcon: AndroidIcon, label: 'Android', id: 'downloadButton_android' },
{
key: 'win',
mainIcon: Icon1,
secondaryIcon: WinIcon,
label: 'Windows',
link: '' // Windows 始终点击动态获取
},
{
key: 'mac',
mainIcon: Icon3,
secondaryIcon: MacIcon,
label: 'macOS',
link: currentPlatform.value === 'mac' ? '' : FIXED_LINKS.mac
},
{
key: 'ios',
mainIcon: Icon2,
secondaryIcon: AppleIcon,
label: 'iOS',
link: currentPlatform.value === 'ios' ? '' : FIXED_LINKS.ios
},
{
key: 'android',
mainIcon: Icon4,
secondaryIcon: AndroidIcon,
label: 'Android',
link: currentPlatform.value === 'android' ? '' : FIXED_LINKS.android
},
])
const mainButton = computed(() => {
@@ -6,7 +6,7 @@
>
<!-- Static More Reviews Button, 不受动画影响随时可点 -->
<a
href="https://hifastvpn.com/reviews"
:href="reviewLink"
target="_blank"
class="absolute cursor-pointer right-4 top-[18px] md:right-5 z-20 text-[10px] md:text-sm text-white hover:text-[#ADFF5B] transition-colors underline decoration-white underline-offset-4"
>
@@ -59,6 +59,7 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue';
import { getAllQueryString } from '@/utils/url-utils';
// Import avatars
import avatar1 from './avatars/avatar1.png';
@@ -105,6 +106,11 @@ const reviews = [
const currentIndex = ref(0);
const currentReview = computed(() => reviews[currentIndex.value]);
const reviewLink = computed(() => {
const ic = getAllQueryString('ic') || 'uSSfgY6oAP';
return `https://hifastvpn.com/reviews?ic=${ic}`
});
let timer: any = null;
const startCarousel = () => {
+1 -1
View File
@@ -77,7 +77,7 @@
>
<p>最新加密协议-安全有保障</p>
<p>IEPL专线-纯净稳定</p>
<p>不限/不限-网速多快Hi快多快</p>
<p>不限流-网速多快Hi快多快</p>
<p>极速闪连-永远快人一步</p>
</div>
+98
View File
@@ -0,0 +1,98 @@
import Adjust from '@adjustcom/adjust-web-sdk'
import { getAllQueryString } from '@/utils/url-utils'
/**
* Adjust Web SDK 工具类
* 用于初始化 SDK 及管理全局参数
*/
class AdjustUtil {
private static instance: AdjustUtil
private isInitialized: boolean = false
private appToken: string = 'vnxcjw75xyww'
private environment: 'production' | 'sandbox' = 'sandbox'
// Adjust 事件令牌映射表(请在此处替换为真实的 6 位令牌)
public static readonly EVENT_TOKENS = {
win: 'c6rntd',
mac: 'c6rntd',
ios: 'c6rntd',
android: 'c6rntd',
}
private constructor() {
this.init()
}
public static getInstance(): AdjustUtil {
if (!AdjustUtil.instance) {
AdjustUtil.instance = new AdjustUtil()
}
return AdjustUtil.instance
}
private init() {
try {
if (!this.isInitialized) {
Adjust.initSdk({
appToken: this.appToken,
environment: this.environment,
logLevel: 'verbose'
})
this.isInitialized = true
console.log('Adjust SDK Initialized (NPM)')
// 获取并设置 inviteCode 作为全局参数
const ic = getAllQueryString('ic') || 'uSSfgY6oAP'
if (ic) {
Adjust.addGlobalCallbackParameters([
{ key: 'inviteCode', value: ic }
])
console.log('Adjust Global Parameter Set: inviteCode =', ic)
}
}
} catch (e) {
console.error('Adjust SDK Initialization Error:', e)
}
}
/**
* 获取装饰后的 Adjust 链接(透传当前页面所有 URL 参数)
* @param baseUrl 基础 Adjust 链接,默认为 'https://hifastvpn.go.link'
* @param defaultAdjT 默认的 adj_t 值,默认为 '1xf6e7ru'
*/
public getDecoratedLink(baseUrl: string = 'https://hifastvpn.go.link/8KzXY', defaultAdjT: string = '1xf6e7ru'): string {
const currentParams = new URLSearchParams(window.location.search || window.location.hash.split('?')[1] || '')
const baseParams = new URLSearchParams()
// 设置默认 adj_t
baseParams.set('adj_t', defaultAdjT)
// 将当前页面的参数合并进来(当前页面的参数优先级更高,可覆盖默认的 adj_t)
currentParams.forEach((value, key) => {
if (key === 'ic') {
// 特殊处理:URL 上的 ic 映射为 Adjust 链接上的 inviteCode
baseParams.set('inviteCode', value)
} else {
baseParams.set(key, value)
}
})
// https://hifastvpn.go.link/8KzXY
const finalQuery = baseParams.toString()
return `${baseUrl}${baseUrl.includes('?') ? '&' : '?'}${finalQuery}`
}
/**
* 记录自定义事件
* @param eventNameOrToken 事件名称(win/ios等)或 Adjust Event Token6位令牌)
*/
public trackEvent(eventNameOrToken: string) {
if (this.isInitialized) {
// 优先从映射表中查找令牌,找不到则视为直接传入的令牌
const token = (AdjustUtil.EVENT_TOKENS as any)[eventNameOrToken] || eventNameOrToken
Adjust.trackEvent({ eventToken: token })
}
}
}
export default AdjustUtil.getInstance()
+40 -40
View File
@@ -23,11 +23,11 @@ class OpenInstallSdk {
constructor() {
this.OI = {}
this.urlQuery = window.OpenInstall.parseUrlParams()
const id = getAllQueryString('id')
if (id) {
const ic = getAllQueryString('ic') || 'uSSfgY6oAP'
if (ic) {
this.urlQuery = {
platform: 'merchant',
code: id,
platform: 'download',
code: ic,
}
}
this.init()
@@ -38,42 +38,42 @@ class OpenInstallSdk {
this.OI = new window.OpenInstall({
appKey: 'alf57p',
onready: function () { // 初始化成功回调方法。当初始化完成后,会自动进入
this.schemeWakeup()// 尝试使用scheme打开App(主要用于Android以及iOS的QQ环境中)
const m = this
const button = document.getElementById('downloadButton_apple')
const button_mac = document.getElementById('downloadButton_mac')
const button1 = document.getElementById('downloadButton_android')
const ic = getAllQueryString('ic')
if (button) {
button.onclick = function () {
if (ic) {
m.wakeupOrInstall({ data: { platform: 'download', inviteCode: ic } })
} else {
m.wakeupOrInstall()// 此方法为scheme、Universal Link唤醒以及引导下载的作用(必须调用且不可额外自行跳转下载)
}
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
}
}
// this.schemeWakeup()// 尝试使用scheme打开App(主要用于Android以及iOS的QQ环境中)
// const m = this
// const button = document.getElementById('downloadButton_apple')
// const button_mac = document.getElementById('downloadButton_mac')
// const button1 = document.getElementById('downloadButton_android')
// const ic = getAllQueryString('ic') || 'uSSfgY6oAP'
// if (button) {
// button.onclick = function () {
// if (ic) {
// m.wakeupOrInstall({ data: { platform: 'download', inviteCode: ic } })
// } else {
// m.wakeupOrInstall()// 此方法为scheme、Universal Link唤醒以及引导下载的作用(必须调用且不可额外自行跳转下载)
// }
// 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
// }
// }
},
}, this.urlQuery)// 初始化时传入data,作为一键拉起/App传参安装时候的参数
} catch (e) {
+5 -1
View File
@@ -10,7 +10,11 @@
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
"noUncheckedSideEffectImports": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}