diff --git a/package.json b/package.json
index 71e91a5..60503b6 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2959cbd..601ad55 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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
diff --git a/src/main.ts b/src/main.ts
index 94336c5..83193db 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -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')
diff --git a/src/pages/Home/components/DownloadButton.vue b/src/pages/Home/components/DownloadButton.vue
index 10a684a..07d73ab 100644
--- a/src/pages/Home/components/DownloadButton.vue
+++ b/src/pages/Home/components/DownloadButton.vue
@@ -8,6 +8,7 @@
target="_blank"
:aria-label="mainButton.label"
class="flex h-full w-full items-center justify-center transition-transform hover:brightness-110 active:scale-95"
+ @click.prevent="handleDownload(mainButton)"
>
@@ -16,6 +17,7 @@
:id="mainButton?.id"
:aria-label="mainButton?.label"
class="flex h-full w-full cursor-pointer items-center justify-center transition-transform hover:brightness-110 active:scale-95"
+ @click="handleDownload(mainButton)"
>
@@ -35,6 +37,7 @@
target="_blank"
:aria-label="item.label"
class="transition-transform hover:brightness-110 active:scale-95"
+ @click.prevent="handleDownload(item)"
>
@@ -43,6 +46,7 @@
:id="item.id"
:aria-label="item.label"
class="cursor-pointer transition-transform hover:brightness-110 active:scale-95"
+ @click="handleDownload(item)"
>
@@ -64,10 +68,9 @@ import AndroidIcon from './AndroidIcon.svg?component'
import request from '@/utils/request'
import {computed, ref, onMounted} from "vue";
-import {getAllQueryString} from "@/utils/url-utils";
+import {getAllQueryString} from "../../../utils/url-utils";
+import AdjustUtil from "../../../utils/adjust";
-const downLoadWin = ref('')
-const downLoadMac = ref('')
const currentPlatform = ref('win')
onMounted(() => {
@@ -100,30 +103,38 @@ onMounted(() => {
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
-// })
+const handleDownload = (item: any) => {
+ console.log('Downloading:', item.label, item.link);
+ // 记录点击事件(通过 key 自动映射令牌)
+ AdjustUtil.trackEvent(item.key);
-request.get('/api/v1/common/client/download', {
- invite_code: getAllQueryString('ic'),
- platform: 'windows',
-}).then((res: any) => {
- downLoadWin.value = res.url
-})
+ if (item.key === 'win') {
+ // Windows 平台:点击时请求下载地址
+ request.get('/api/v1/common/client/download', {
+ invite_code: getAllQueryString('ic'),
+ platform: 'windows',
+ }).then((res: any) => {
+ if (res.url) {
+ window.open(res.url, '_blank');
+ }
+ }).catch((err: any) => {
+ console.error('Failed to fetch windows download link:', err);
+ });
+ } else if (item.link) {
+ // 其他平台:直接打开链接
+ window.open(item.link, '_blank');
+ }
+};
const allDownloadOptions = computed(() => {
- const ic = getAllQueryString('ic')
const androidLink = currentPlatform.value === 'android'
- ? `https://hifastvpn.go.link?adj_t=1xf6e7ru&inviteCode=${ic}`
+ ? AdjustUtil.getDecoratedLink()
: 'https://api.hifast.biz/v1/common/client/download/file/Hi%E5%BF%ABVPN-android-1.0.0.apk'
return [
- { 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=${ic}` },
- { key: 'ios', mainIcon: Icon2, secondaryIcon: AppleIcon, label: 'iOS', link: `https://hifastvpn.go.link?adj_t=1xf6e7ru&inviteCode=${ic}` },
+ { key: 'win', mainIcon: Icon1, secondaryIcon: WinIcon, link: '', label: 'Windows', id: 'downloadButton_win' },
+ { key: 'mac', mainIcon: Icon3, secondaryIcon: MacIcon, label: 'macOS', link: AdjustUtil.getDecoratedLink() },
+ { key: 'ios', mainIcon: Icon2, secondaryIcon: AppleIcon, label: 'iOS', link: AdjustUtil.getDecoratedLink() },
{ key: 'android', mainIcon: Icon4, secondaryIcon: AndroidIcon, label: 'Android', link: androidLink },
]
})
diff --git a/src/utils/adjust.ts b/src/utils/adjust.ts
new file mode 100644
index 0000000..a8a4b56
--- /dev/null
+++ b/src/utils/adjust.ts
@@ -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' = 'production'
+
+ // 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')
+ 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', 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)
+ }
+ })
+
+ const finalQuery = baseParams.toString()
+ return `${baseUrl}${baseUrl.includes('?') ? '&' : '?'}${finalQuery}`
+ }
+
+ /**
+ * 记录自定义事件
+ * @param eventNameOrToken 事件名称(win/ios等)或 Adjust Event Token(6位令牌)
+ */
+ public trackEvent(eventNameOrToken: string) {
+ if (this.isInitialized) {
+ // 优先从映射表中查找令牌,找不到则视为直接传入的令牌
+ const token = (AdjustUtil.EVENT_TOKENS as any)[eventNameOrToken] || eventNameOrToken
+ Adjust.trackEvent({ eventToken: token })
+ }
+ }
+}
+
+export default AdjustUtil.getInstance()
diff --git a/tsconfig.app.json b/tsconfig.app.json
index 8d16e42..8adf728 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -10,7 +10,11 @@
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
+ "noUncheckedSideEffectImports": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}