speakeloudest b6caf1c38b
All checks were successful
site-dist-deploy / build-and-deploy (push) Successful in 1m51s
调整细节
2026-03-25 09:42:52 +02:00

201 lines
5.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="review-carousel-container relative">
<div
class="review-card lucid-glass-bar relative flex items-center overflow-hidden rounded-2xl p-4 text-white md:p-5"
:style="{ height: isMobile ? '114px' : '130px' }"
>
<!-- Static More Reviews Button, 不受动画影响随时可点 -->
<router-link
to="/reviews"
class="absolute top-[18px] right-4 z-20 cursor-pointer text-[10px] text-white underline decoration-white underline-offset-4 transition-colors hover:text-[#ADFF5B] md:top-[24px] md:right-5 md:text-sm"
>
More Reviews
</router-link>
<!-- 动画应用在卡片内部的内容上 -->
<transition name="fade" mode="out-in">
<div v-if="currentReview" :key="currentIndex" class="flex h-full w-full items-center">
<!-- Avatar -->
<div class="mr-4 flex-shrink-0 md:mr-6">
<img
:src="currentReview.avatar"
alt="User Avatar"
class="h-[70px] w-[70px] rounded-full border-2 border-pink-300/30 object-cover md:h-[84px] md:w-[84px]"
/>
</div>
<!-- Content -->
<div class="min-w-0 flex-grow">
<div class="mb-1 flex items-start justify-between">
<h3 class="truncate pr-20 text-base font-bold md:pr-28 md:text-xl">
{{ currentReview.username }}
</h3>
</div>
<!-- Stars and Rating -->
<div class="mb-1 flex items-center gap-1 md:mb-2">
<div class="flex gap-0.5">
<svg
v-for="i in 5"
:key="i"
class="h-3 w-3 text-[#ADFF5B] md:h-4 md:w-4"
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>
<span class="ml-1 text-xs font-medium text-white/50 md:text-sm">4.9</span>
<span class="ml-2 text-xs text-white/30 md:text-sm"
>{{ currentReview.reviewCount }}k reviews</span
>
</div>
<!-- Comment Text -->
<p class="line-clamp-2 text-xs leading-[1.4] text-white/90 md:pr-4 md:text-sm">
{{ currentReview.comment }}
</p>
</div>
</div>
</transition>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'
// Import avatars
import avatar1 from './avatars/avatar1.png'
import avatar2 from './avatars/avatar2.png'
import avatar3 from './avatars/avatar3.png'
import avatar4 from './avatars/avatar4.png'
import avatar6 from './avatars/avatar6.png'
const isMobile = ref(false)
const reviews = [
{
username: '庄子不给',
avatar: avatar1,
comment: '真心不错,比我只前买的那个好用多了。网宿很给力。',
reviewCount: '5.7',
},
{
username: 'TechEnthusiast',
avatar: avatar6,
comment: 'Speed is incredible! The best VPN I have used in years for international streaming.',
reviewCount: '12.4',
},
{
username: '阿杰',
avatar: avatar2,
comment: '非常稳定的连接在高峰时段也没掉过线。UI设计很现代用着很舒服。',
reviewCount: '3.2',
},
{
username: 'Lina_Zhang',
avatar: avatar3,
comment: '客服响应速度很快,配置简单。推荐给需要长期稳定翻墙的朋友们。',
reviewCount: '8.9',
},
{
username: 'GlobalNomad',
avatar: avatar4,
comment: 'Perfect for my travels. Low latency and high security. A must-have for privacy.',
reviewCount: '6.1',
},
]
const currentIndex = ref(0)
const currentReview = computed(() => reviews[currentIndex.value])
let timer: any = null
const startCarousel = () => {
timer = setInterval(() => {
currentIndex.value = (currentIndex.value + 1) % reviews.length
}, 5000)
}
const handleResize = () => {
isMobile.value = window.innerWidth < 768
}
onMounted(() => {
handleResize()
window.addEventListener('resize', handleResize)
startCarousel()
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
if (timer) clearInterval(timer)
})
</script>
<style scoped>
.review-carousel-container {
width: 100%;
}
@media (min-width: 768px) {
.review-carousel-container {
max-width: 400px;
}
}
.fade-enter-active,
.fade-leave-active {
transition:
opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}
.fade-enter-from {
opacity: 0;
transform: translateY(20px);
}
.fade-leave-to {
opacity: 0;
transform: translateY(-20px);
}
.review-card {
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
transition: all 0.3s ease;
border-radius: 16px !important;
}
.review-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: inherit;
padding: 1px;
background: linear-gradient(135deg, rgba(173, 255, 91, 0.1), transparent 50%);
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
}
.line-clamp-2 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
}
</style>