This commit is contained in:
parent
73afc3c435
commit
784cabaddc
@ -4,8 +4,15 @@
|
||||
<div
|
||||
class="mx-auto h-[60px] w-[210px] rounded-full bg-[#ADFF5B] md:-ml-2 md:h-[100px] md:w-[360px]"
|
||||
>
|
||||
<router-link
|
||||
v-if="mainButton?.link && !mainButton.link.startsWith('http')"
|
||||
:to="mainButton.link"
|
||||
class="block transition-transform hover:brightness-110 active:scale-95"
|
||||
>
|
||||
<component :is="mainButton.mainIcon" class="h-full text-black" />
|
||||
</router-link>
|
||||
<a
|
||||
v-if="mainButton?.link"
|
||||
v-else-if="mainButton?.link"
|
||||
:href="mainButton.link"
|
||||
target="_blank"
|
||||
:aria-label="mainButton.label"
|
||||
@ -31,8 +38,15 @@
|
||||
|
||||
<div class="flex items-center justify-center gap-4 md:justify-start">
|
||||
<template v-for="(item, index) in otherButtons" :key="index">
|
||||
<router-link
|
||||
v-if="item.link && !item.link.startsWith('http')"
|
||||
:to="item.link"
|
||||
class="transition-transform hover:brightness-110 active:scale-95"
|
||||
>
|
||||
<component :is="item.secondaryIcon" class="h-[24px] text-white md:h-[34px]" />
|
||||
</router-link>
|
||||
<a
|
||||
v-if="item.link"
|
||||
v-else-if="item.link"
|
||||
:href="item.link"
|
||||
target="_blank"
|
||||
:aria-label="item.label"
|
||||
@ -68,8 +82,6 @@ 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')
|
||||
|
||||
onMounted(() => {
|
||||
@ -109,29 +121,36 @@ onMounted(() => {
|
||||
// downLoadMac.value = res.url
|
||||
// })
|
||||
|
||||
request
|
||||
.get('/api/v1/common/client/download', {
|
||||
invite_code: getAllQueryString('ic'),
|
||||
platform: 'windows',
|
||||
})
|
||||
.then((res: any) => {
|
||||
downLoadWin.value = res.url
|
||||
})
|
||||
const ADJ_BASE_URL = 'https://hifastvpn.go.link/?adj_t=1xf6e7ru'
|
||||
const getTrackedUrl = (target: string) => {
|
||||
if (!target) return ''
|
||||
return `${ADJ_BASE_URL}&adj_redirect=${encodeURIComponent(target)}`
|
||||
}
|
||||
|
||||
const allDownloadOptions = computed(() => [
|
||||
{
|
||||
key: 'win',
|
||||
mainIcon: Icon1,
|
||||
secondaryIcon: WinIcon,
|
||||
link: downLoadWin.value,
|
||||
link: getTrackedUrl(
|
||||
'https://api.hifast.biz/v1/common/client/download/file/Hi快VPN-windows-1.0.0.exe',
|
||||
),
|
||||
label: 'Windows',
|
||||
id: 'downloadButton_win',
|
||||
},
|
||||
{ key: 'mac', mainIcon: Icon3, secondaryIcon: MacIcon, label: 'macOS', id: 'downloadButton_mac' },
|
||||
{
|
||||
key: 'mac',
|
||||
mainIcon: Icon3,
|
||||
secondaryIcon: MacIcon,
|
||||
link: getTrackedUrl('https://apps.apple.com/us/app/hi%E5%BF%ABvpn/id6755683167'),
|
||||
label: 'macOS',
|
||||
id: 'downloadButton_mac',
|
||||
},
|
||||
{
|
||||
key: 'ios',
|
||||
mainIcon: Icon2,
|
||||
secondaryIcon: AppleIcon,
|
||||
link: '/help',
|
||||
label: 'iOS',
|
||||
id: 'downloadButton_apple',
|
||||
},
|
||||
@ -139,6 +158,9 @@ const allDownloadOptions = computed(() => [
|
||||
key: 'android',
|
||||
mainIcon: Icon4,
|
||||
secondaryIcon: AndroidIcon,
|
||||
link: getTrackedUrl(
|
||||
'https://api.hifast.biz/v1/common/client/download/file/Hi%E5%BF%ABVPN-android-1.0.0.apk',
|
||||
),
|
||||
label: 'Android',
|
||||
id: 'downloadButton_android',
|
||||
},
|
||||
|
||||
@ -20,13 +20,12 @@
|
||||
<div class="flex-grow min-w-0">
|
||||
<div class="flex justify-between items-start mb-1">
|
||||
<h3 class="text-base md:text-xl font-bold truncate pr-2">{{ currentReview.username }}</h3>
|
||||
<a
|
||||
href="https://hifastvpn.com"
|
||||
target="_blank"
|
||||
<router-link
|
||||
to="/reviews"
|
||||
class="more-reviews text-[10px] md:text-sm text-white hover:text-[#ADFF5B] transition-colors underline decoration-white underline-offset-4"
|
||||
>
|
||||
More Reviews
|
||||
</a>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<!-- Stars and Rating -->
|
||||
|
||||
BIN
src/pages/Reviews/image1.png
Normal file
BIN
src/pages/Reviews/image1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
src/pages/Reviews/image2.png
Normal file
BIN
src/pages/Reviews/image2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
279
src/pages/Reviews/index.vue
Normal file
279
src/pages/Reviews/index.vue
Normal file
@ -0,0 +1,279 @@
|
||||
<template>
|
||||
<div class="reviews-page min-h-screen overflow-x-hidden bg-black font-sans text-white">
|
||||
<!-- Header -->
|
||||
<div class="h-[80px] md:h-[114px]">
|
||||
<div class="fixed z-50 w-full bg-black pt-[20px] pb-[20px] md:pt-[34px]">
|
||||
<div class="container">
|
||||
<header
|
||||
class="flex h-[40px] items-center justify-between rounded-full bg-[#ADFF5B] pr-[30px] pl-5 md:h-[60px] md:pr-[58px] md:pl-[41px]"
|
||||
>
|
||||
<router-link to="/" class="flex items-center gap-2">
|
||||
<!-- Desktop Logo -->
|
||||
<!-- <Logo :src="Logo" alt="Hi快VPN" class="hidden h-10 w-auto text-black md:block" />-->
|
||||
<!-- Mobile Logo -->
|
||||
<MobileLogo alt="Hi快VPN" class="block h-[22px] text-black md:h-[36px]" />
|
||||
</router-link>
|
||||
<RightText class="block h-[12px] text-black md:h-[24px]" />
|
||||
</header>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto max-w-2xl px-4 py-8">
|
||||
<img src="./image2.png" class="hidden md:block" />
|
||||
<img src="./image1.png" class="md:hidden" />
|
||||
<div class="my-[40px] h-[1px] w-full bg-[#757575]" />
|
||||
<div class="space-y-8">
|
||||
<div
|
||||
v-for="(review, index) in displayReviews"
|
||||
:key="index"
|
||||
class="review-card"
|
||||
:style="{ '--index': index % 10 }"
|
||||
>
|
||||
<div class="mb-3 flex items-start justify-between">
|
||||
<div class="flex flex-wrap items-baseline gap-x-3 gap-y-1">
|
||||
<h2 class="text-lg leading-tight font-bold">{{ review.username }}</h2>
|
||||
<span class="text-sm text-white/40">{{ review.date }}</span>
|
||||
</div>
|
||||
<!-- Stars -->
|
||||
<div class="mt-1 flex shrink-0 gap-0.5">
|
||||
<svg
|
||||
v-for="i in 5"
|
||||
:key="i"
|
||||
class="h-4 w-4"
|
||||
:class="i <= (review.stars || 5) ? 'text-[#ADFF5B]' : 'text-white/10'"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mb-8 text-[15px] leading-relaxed text-white/80 md:text-base">
|
||||
{{ review.comment }}
|
||||
</p>
|
||||
|
||||
<div class="w-full border-b border-[#757575]"></div>
|
||||
</div>
|
||||
|
||||
<!-- Loading state -->
|
||||
<div v-if="isLoading" class="flex justify-center py-10">
|
||||
<div
|
||||
class="h-6 w-6 animate-spin rounded-full border-2 border-[#ADFF5B]/30 border-t-[#ADFF5B]"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- No more data -->
|
||||
<div v-if="!hasMore && !isLoading" class="py-10 text-center text-sm text-white/30">
|
||||
暂无更多
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useInfiniteScroll } from '@vueuse/core'
|
||||
import MobileLogo from '@/pages/Home/mobile-logo.svg?component'
|
||||
import RightText from './right-text.svg?component'
|
||||
const router = useRouter()
|
||||
const isLoading = ref(false)
|
||||
const hasMore = ref(true)
|
||||
|
||||
// Fixed 20 random reviews based on the provided design
|
||||
const allReviews = [
|
||||
{
|
||||
username: 'Alex Chen',
|
||||
date: 'Oct.12',
|
||||
stars: 5,
|
||||
comment:
|
||||
'真的是极简主义的胜利。没有多余的按钮,打开软件的瞬间就已经连接成功了。抛弃了传统VPN繁琐的线路选择,这种“无感”的体验让人非常舒服。Netflix 4K 拖拽完全没有缓冲。',
|
||||
},
|
||||
{
|
||||
username: 'Fjafw****@gmail.com',
|
||||
date: 'Feb. 2',
|
||||
stars: 5,
|
||||
comment:
|
||||
'IEPL专线稳定性确实不是普通机场能比的。晚高峰打竞技游戏延迟依然很低,几乎感觉不到身在海外。UI设计长在我的审美上,黑绿配色极其克制且高级。',
|
||||
},
|
||||
{
|
||||
username: 'Sarah Lin',
|
||||
date: 'Sep. 19',
|
||||
stars: 4,
|
||||
comment:
|
||||
'速度和稳定性都没得说,是目前用过最省心的软件。唯一扣掉的一星是希望能在未来加入一些特定地区冷门节点的备选方案,虽然目前的智能分配已经足够好用。总体强烈推荐。',
|
||||
},
|
||||
{
|
||||
username: 'Michael W.',
|
||||
date: 'Jan. 15',
|
||||
stars: 5,
|
||||
comment:
|
||||
'I have tried many VPNs, but HiFast is by far the fastest. The connection is instantaneous and the bandwidth is impressive.',
|
||||
},
|
||||
{
|
||||
username: '张晓明',
|
||||
date: 'Dec. 5',
|
||||
stars: 5,
|
||||
comment: '非常好用,连接速度快,看YouTube 4K一点都不卡。',
|
||||
},
|
||||
{
|
||||
username: 'David K.',
|
||||
date: 'Nov. 22',
|
||||
stars: 5,
|
||||
comment:
|
||||
'The UI is clean and the performance is top-notch. Highly recommended for professionals.',
|
||||
},
|
||||
{
|
||||
username: 'Emily Chen',
|
||||
date: 'Mar. 10',
|
||||
stars: 5,
|
||||
comment: 'Best VPN for gaming! Low ping and no packet loss. Love it.',
|
||||
},
|
||||
{
|
||||
username: '李华',
|
||||
date: 'Feb. 28',
|
||||
stars: 5,
|
||||
comment: '客服态度很好,遇到问题解决得很快。软件本身也非常稳定。',
|
||||
},
|
||||
{
|
||||
username: 'Robert J.',
|
||||
date: 'May. 14',
|
||||
stars: 5,
|
||||
comment: 'Solid performance. I use it for my remote work and it never fails me.',
|
||||
},
|
||||
{
|
||||
username: '王大力',
|
||||
date: 'Jun. 20',
|
||||
stars: 4,
|
||||
comment: '很不错的VPN,虽然价格稍微贵一点,但物有所值。',
|
||||
},
|
||||
{
|
||||
username: 'Jessica S.',
|
||||
date: 'Aug. 3',
|
||||
stars: 5,
|
||||
comment: 'So easy to use. Just one click and I am protected. Great job!',
|
||||
},
|
||||
{
|
||||
username: '陈伟',
|
||||
date: 'Jul. 12',
|
||||
stars: 5,
|
||||
comment: '在国外用这个看国内视频非常流畅,没有任何限制。',
|
||||
},
|
||||
{
|
||||
username: 'Kevin L.',
|
||||
date: 'Sep. 30',
|
||||
stars: 5,
|
||||
comment: 'The encryption makes me feel safe. High speed is a bonus.',
|
||||
},
|
||||
{
|
||||
username: '周杰',
|
||||
date: 'Apr. 18',
|
||||
stars: 5,
|
||||
comment: '支持多设备同时在线,非常方便。家里几个人共用一个账号。',
|
||||
},
|
||||
{
|
||||
username: 'Anna B.',
|
||||
date: 'Oct. 5',
|
||||
stars: 5,
|
||||
comment: 'Love the dark mode UI. It looks so modern and sleek.',
|
||||
},
|
||||
{
|
||||
username: '赵三',
|
||||
date: 'Dec. 25',
|
||||
stars: 4,
|
||||
comment: '整体感觉不错,连接速度挺快的,偶尔会断线。',
|
||||
},
|
||||
{
|
||||
username: 'Thomas M.',
|
||||
date: 'Nov. 8',
|
||||
stars: 5,
|
||||
comment: 'Great service. I have recommended it to all my friends.',
|
||||
},
|
||||
{
|
||||
username: '刘红',
|
||||
date: 'Jan. 22',
|
||||
stars: 5,
|
||||
comment: '非常感谢Hi快VPN,让我能方便地学习国外的课程。',
|
||||
},
|
||||
{
|
||||
username: 'Steven H.',
|
||||
date: 'Feb. 14',
|
||||
stars: 5,
|
||||
comment: 'Excellent bandwidth. Streaming 4K is like watching local videos.',
|
||||
},
|
||||
{
|
||||
username: '吴优',
|
||||
date: 'Mar. 25',
|
||||
stars: 5,
|
||||
comment: '用过最简单的VPN,即使是不懂技术的人也能轻松上手。',
|
||||
},
|
||||
]
|
||||
|
||||
const displayReviews = ref(allReviews.slice(0, 5))
|
||||
const currentIndex = ref(5)
|
||||
|
||||
const loadMore = () => {
|
||||
if (isLoading.value || !hasMore.value) return
|
||||
isLoading.value = true
|
||||
|
||||
// Simulate network delay
|
||||
setTimeout(() => {
|
||||
const nextBatch = allReviews.slice(currentIndex.value, currentIndex.value + 3)
|
||||
if (nextBatch.length > 0) {
|
||||
displayReviews.value.push(...nextBatch)
|
||||
currentIndex.value += nextBatch.length
|
||||
}
|
||||
|
||||
if (currentIndex.value >= allReviews.length) {
|
||||
hasMore.value = false
|
||||
}
|
||||
isLoading.value = false
|
||||
}, 300)
|
||||
}
|
||||
|
||||
useInfiniteScroll(
|
||||
window,
|
||||
() => {
|
||||
loadMore()
|
||||
},
|
||||
{ distance: 200 },
|
||||
)
|
||||
|
||||
const goBack = () => {
|
||||
router.push('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.reviews-page {
|
||||
background-color: #000;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.review-card {
|
||||
animation: slideUp 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Staggered animation for initial items */
|
||||
.review-card {
|
||||
animation-delay: calc(var(--index, 0) * 0.05s);
|
||||
}
|
||||
</style>
|
||||
3
src/pages/Reviews/right-text.svg
Normal file
3
src/pages/Reviews/right-text.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.4 KiB |
@ -8,6 +8,11 @@ const router = createRouter({
|
||||
name: 'home',
|
||||
component: () => import('../pages/Home/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/reviews',
|
||||
name: 'reviews',
|
||||
component: () => import('../pages/Reviews/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/help',
|
||||
name: 'help',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user