Compare commits

..

9 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
9 changed files with 305 additions and 97 deletions
+1
View File
@@ -10,6 +10,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@adjustcom/adjust-web-sdk": "^5.8.2",
"@tailwindcss/vite": "^4.1.18", "@tailwindcss/vite": "^4.1.18",
"axios": "^1.13.2", "axios": "^1.13.2",
"clsx": "^2.1.1", "clsx": "^2.1.1",
+16
View File
@@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
'@adjustcom/adjust-web-sdk':
specifier: ^5.8.2
version: 5.8.2
'@tailwindcss/vite': '@tailwindcss/vite':
specifier: ^4.1.18 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)) 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: packages:
'@adjustcom/adjust-web-sdk@5.8.2':
resolution: {integrity: sha512-6ov2EKKuPQi7i+0CdtsGZ7REIE8jRlg++muAjqpR4ySLjOXTgxVSmWjsTBohxnTVDDL2OGEMfXFUG6ZIhyvl9Q==}
'@babel/helper-string-parser@7.27.1': '@babel/helper-string-parser@7.27.1':
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@@ -85,6 +91,10 @@ packages:
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
hasBin: true hasBin: true
'@babel/runtime@7.29.2':
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
engines: {node: '>=6.9.0'}
'@babel/types@7.28.6': '@babel/types@7.28.6':
resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@@ -1094,6 +1104,10 @@ packages:
snapshots: snapshots:
'@adjustcom/adjust-web-sdk@5.8.2':
dependencies:
'@babel/runtime': 7.29.2
'@babel/helper-string-parser@7.27.1': {} '@babel/helper-string-parser@7.27.1': {}
'@babel/helper-validator-identifier@7.28.5': {} '@babel/helper-validator-identifier@7.28.5': {}
@@ -1102,6 +1116,8 @@ snapshots:
dependencies: dependencies:
'@babel/types': 7.28.6 '@babel/types': 7.28.6
'@babel/runtime@7.29.2': {}
'@babel/types@7.28.6': '@babel/types@7.28.6':
dependencies: dependencies:
'@babel/helper-string-parser': 7.27.1 '@babel/helper-string-parser': 7.27.1
+2 -1
View File
@@ -1,6 +1,7 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import './styles/index.css' import './styles/index.css'
import App from './App.vue' import App from './App.vue'
import '@/utils/openinstall.ts' import '@/utils/openinstall'
import '@/utils/adjust'
createApp(App).mount('#app') createApp(App).mount('#app')
+122 -34
View File
@@ -15,11 +15,27 @@
v-else v-else
:id="mainButton?.id" :id="mainButton?.id"
:aria-label="mainButton?.label" :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" 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" /> <component :is="mainButton?.mainIcon" class="h-auto w-full text-black transition-transform" />
</div> </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>
</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="w-full mt-2 md:mt-[35px]">
@@ -29,6 +45,7 @@
<div class="flex gap-4 items-center justify-center md:justify-start"> <div class="flex gap-4 items-center justify-center md:justify-start">
<template v-for="(item, index) in otherButtons" :key="index"> <template v-for="(item, index) in otherButtons" :key="index">
<div class="relative">
<a <a
v-if="item.link" v-if="item.link"
:href="item.link" :href="item.link"
@@ -42,10 +59,17 @@
v-else v-else
:id="item.id" :id="item.id"
:aria-label="item.label" :aria-label="item.label"
@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] md:h-[34px] text-white" /> <component :is="item.secondaryIcon" class="h-[24px] md:h-[34px] text-white" />
</div> </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> </template>
</div> </div>
</div> </div>
@@ -66,59 +90,123 @@ import request from '@/utils/request'
import {computed, ref, onMounted} from "vue"; import {computed, ref, onMounted} from "vue";
import {getAllQueryString} from "@/utils/url-utils.ts"; import {getAllQueryString} from "@/utils/url-utils.ts";
const downLoadWin = ref('')
const downLoadMac = ref('')
const currentPlatform = ref('win') 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(() => { onMounted(() => {
const ua = navigator.userAgent; const ua = navigator.userAgent;
const platform = navigator.platform || ''; // 辅助判断
// 1. 先判断 Android (通常比较稳定)
if (/Android/i.test(ua)) { if (/Android/i.test(ua)) {
currentPlatform.value = 'android'; currentPlatform.value = 'android';
} } else if (/iPhone|iPod/i.test(ua)) {
// 2. 判断 iOS 设备 (iPhone, iPod)
else if (/iPhone|iPod/i.test(ua)) {
currentPlatform.value = 'ios'; currentPlatform.value = 'ios';
} } else if (/Macintosh|Mac OS X/i.test(ua)) {
// 3. 核心改进:区分 Mac 和 iPad
else if (/Macintosh|Mac OS X/i.test(ua)) {
// iPadOS 桌面模式下,支持多点触控(通常 > 1)
// 而真正的 Mac 电脑通常 maxTouchPoints 为 0 或 undefined
if (navigator.maxTouchPoints > 1) { if (navigator.maxTouchPoints > 1) {
currentPlatform.value = 'ios'; // 归类为 iOS 端(iPad currentPlatform.value = 'ios'; // iPad
} else { } else {
currentPlatform.value = 'mac'; currentPlatform.value = 'mac';
} }
} } else {
// 4. 其他情况默认为 Windows
else {
currentPlatform.value = 'win'; currentPlatform.value = 'win';
} }
console.log('Detected Platform:', currentPlatform.value);
}); });
// request.get('/api/v1/common/client/download', { /**
// invite_code: getAllQueryString('ic'), * 核心下载处理函数
// platform: 'mac', * @param key 平台标识 (win, mac, ios, android)
// }).then((res: any) => { */
// downLoadMac.value = res.url const handleDownload = async (key: string) => {
// }) const ic = getAllQueryString('ic') || 'uSSfgY6oAP'
const isMain = key === currentPlatform.value;
request.get('/api/v1/common/client/download', { // 1. Windows 逻辑:点击时动态请求 API
invite_code: getAllQueryString('ic'), if (key === 'win') {
try {
const res: any = await request.get('/api/v1/common/client/download', {
invite_code: ic,
platform: 'windows', platform: 'windows',
}).then((res: any) => { })
downLoadWin.value = res.url 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(() => [ 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', link: `https://hifastvpn.go.link?adj_t=1xf6e7ru&inviteCode=${getAllQueryString('ic')}` }, key: 'win',
{ key: 'ios', mainIcon: Icon2, secondaryIcon: AppleIcon, label: 'iOS', link: `https://hifastvpn.go.link?adj_t=1xf6e7ru&inviteCode=${getAllQueryString('ic')}` }, mainIcon: Icon1,
{ key: 'android', mainIcon: Icon4, secondaryIcon: AndroidIcon, label: 'Android', link: `https://hifastvpn.go.link?adj_t=1xf6e7ru&inviteCode=${getAllQueryString('ic')}`, }, 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(() => { const mainButton = computed(() => {
@@ -59,7 +59,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'; import { ref, onMounted, onUnmounted, computed } from 'vue';
import { getAllQueryString } from '@/utils/url-utils.ts'; import { getAllQueryString } from '@/utils/url-utils';
// Import avatars // Import avatars
import avatar1 from './avatars/avatar1.png'; import avatar1 from './avatars/avatar1.png';
@@ -107,8 +107,8 @@ const currentIndex = ref(0);
const currentReview = computed(() => reviews[currentIndex.value]); const currentReview = computed(() => reviews[currentIndex.value]);
const reviewLink = computed(() => { const reviewLink = computed(() => {
const ic = getAllQueryString('ic'); const ic = getAllQueryString('ic') || 'uSSfgY6oAP';
return ic ? `https://hifastvpn.com/reviews?ic=${ic}` : 'https://hifastvpn.com/reviews'; return `https://hifastvpn.com/reviews?ic=${ic}`
}); });
let timer: any = null; let timer: any = null;
+1 -1
View File
@@ -77,7 +77,7 @@
> >
<p>最新加密协议-安全有保障</p> <p>最新加密协议-安全有保障</p>
<p>IEPL专线-纯净稳定</p> <p>IEPL专线-纯净稳定</p>
<p>不限/不限-网速多快Hi快多快</p> <p>不限流-网速多快Hi快多快</p>
<p>极速闪连-永远快人一步</p> <p>极速闪连-永远快人一步</p>
</div> </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() { constructor() {
this.OI = {} this.OI = {}
this.urlQuery = window.OpenInstall.parseUrlParams() this.urlQuery = window.OpenInstall.parseUrlParams()
const id = getAllQueryString('id') const ic = getAllQueryString('ic') || 'uSSfgY6oAP'
if (id) { if (ic) {
this.urlQuery = { this.urlQuery = {
platform: 'merchant', platform: 'download',
code: id, code: ic,
} }
} }
this.init() this.init()
@@ -38,42 +38,42 @@ class OpenInstallSdk {
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.schemeWakeup()// 尝试使用scheme打开App(主要用于Android以及iOS的QQ环境中)
const m = this // const m = this
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') // const ic = getAllQueryString('ic') || 'uSSfgY6oAP'
if (button) { // if (button) {
button.onclick = function () { // button.onclick = 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()// 此方法为scheme、Universal Link唤醒以及引导下载的作用(必须调用且不可额外自行跳转下载)
} // }
return false // return false
} // }
} // }
if(button_mac) { // if(button_mac) {
button_mac.onclick = function () { // button_mac.onclick = 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()// 此方法为scheme、Universal Link唤醒以及引导下载的作用(必须调用且不可额外自行跳转下载)
} // }
return false // return false
} // }
} // }
if (button1) { // if (button1) {
button1.onclick = function () { // button1.onclick = 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()// 此方法为scheme、Universal Link唤醒以及引导下载的作用(必须调用且不可额外自行跳转下载)
} // }
return false // return false
} // }
} // }
}, },
}, this.urlQuery)// 初始化时传入data,作为一键拉起/App传参安装时候的参数 }, this.urlQuery)// 初始化时传入data,作为一键拉起/App传参安装时候的参数
} catch (e) { } catch (e) {
+5 -1
View File
@@ -10,7 +10,11 @@
"noUnusedParameters": true, "noUnusedParameters": true,
"erasableSyntaxOnly": true, "erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true "noUncheckedSideEffectImports": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}, },
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
} }