前端-胡立永 2 days ago
parent
commit
18cda05c8f
5 changed files with 845 additions and 55 deletions
  1. +2
    -1
      .env.development
  2. +1
    -1
      .env.production
  3. +1
    -0
      .gitignore
  4. +824
    -0
      src/components/CustomerReviews.vue
  5. +17
    -53
      src/views/pages/Home.vue

+ 2
- 1
.env.development View File

@ -2,4 +2,5 @@
# 开发环境
VITE_APP_BASE_API = '/dev-api'
# VITE_APP_BASE_API = '/dev-api'
VITE_APP_BASE_API = 'https://pcadmin.hhlm1688.com/prod'

+ 1
- 1
.env.production View File

@ -1,3 +1,3 @@
# 生产环境
VITE_APP_BASE_API = '/prod-api'
VITE_APP_BASE_API = 'https://pcadmin.hhlm1688.com/prod'

+ 1
- 0
.gitignore View File

@ -22,3 +22,4 @@ dist-ssr
*.njsproj
*.sln
*.sw?
dist.*

+ 824
- 0
src/components/CustomerReviews.vue View File

@ -0,0 +1,824 @@
<template>
<div class="customer-reviews">
<div class="reviews-header">
<h2><i class="fas fa-comments"></i> 客户评价</h2>
<div class="separator"></div>
<p class="subtitle">真实的客户反馈见证我们的专业服务</p>
</div>
<div class="reviews-container">
<!-- 评价卡片轮播 -->
<div class="reviews-carousel" ref="carouselRef">
<div class="reviews-track" :style="{ transform: `translateX(-${currentIndex * cardWidth}px)` }">
<div
v-for="(review, index) in reviews"
:key="index"
class="review-card"
@click="openModal(review)"
>
<div class="review-header">
<div class="customer-info">
<div class="avatar">
<img :src="review.avatar" :alt="review.customerName" />
</div>
<div class="customer-details">
<h4>{{ review.customerName }}</h4>
<p class="company">{{ review.company }}</p>
<div class="rating">
<i v-for="n in 5" :key="n"
class="fas fa-star"
:class="{ active: n <= review.rating }">
</i>
</div>
</div>
</div>
<div class="review-date">{{ formatDate(review.date) }}</div>
</div>
<div class="review-content">
<p class="review-text">{{ review.content }}</p>
<!-- 聊天截图预览 -->
<div v-if="review.chatScreenshots && review.chatScreenshots.length > 0" class="chat-preview">
<div class="screenshot-grid">
<div
v-for="(screenshot, sIndex) in review.chatScreenshots.slice(0, 3)"
:key="sIndex"
class="screenshot-item"
:class="{ 'more-indicator': sIndex === 2 && review.chatScreenshots.length > 3 }"
>
<img :src="screenshot.thumbnail || screenshot.url" :alt="`聊天截图 ${sIndex + 1}`" />
<div v-if="sIndex === 2 && review.chatScreenshots.length > 3" class="more-overlay">
<span>+{{ review.chatScreenshots.length - 3 }}</span>
</div>
</div>
</div>
<div class="view-more">
<i class="fas fa-search-plus"></i>
点击查看完整聊天记录
</div>
</div>
</div>
<div class="review-tags">
<span v-for="tag in review.tags" :key="tag" class="tag">{{ tag }}</span>
</div>
</div>
</div>
</div>
<!-- 轮播控制 -->
<div class="carousel-controls">
<button class="control-btn prev" @click="prevSlide" :disabled="currentIndex === 0">
<i class="fas fa-chevron-left"></i>
</button>
<div class="indicators">
<span
v-for="(_, index) in totalSlides"
:key="index"
class="indicator"
:class="{ active: index === currentIndex }"
@click="goToSlide(index)"
></span>
</div>
<button class="control-btn next" @click="nextSlide" :disabled="currentIndex >= totalSlides - 1">
<i class="fas fa-chevron-right"></i>
</button>
</div>
</div>
<!-- 详情弹窗 -->
<div v-if="showModal" class="modal-overlay" @click="closeModal">
<div class="modal-content" @click.stop>
<div class="modal-header">
<div class="customer-info">
<div class="avatar">
<img :src="selectedReview.avatar" :alt="selectedReview.customerName" />
</div>
<div class="customer-details">
<h3>{{ selectedReview.customerName }}</h3>
<p class="company">{{ selectedReview.company }}</p>
<div class="rating">
<i v-for="n in 5" :key="n"
class="fas fa-star"
:class="{ active: n <= selectedReview.rating }">
</i>
</div>
</div>
</div>
<button class="close-btn" @click="closeModal">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-body">
<div class="review-content">
<h4>客户评价</h4>
<p>{{ selectedReview.content }}</p>
</div>
<!-- 完整聊天截图展示 -->
<div v-if="selectedReview.chatScreenshots && selectedReview.chatScreenshots.length > 0" class="chat-screenshots">
<h4>聊天记录</h4>
<div class="screenshots-grid">
<div
v-for="(screenshot, index) in selectedReview.chatScreenshots"
:key="index"
class="screenshot-full"
@click="openImageViewer(screenshot.url)"
>
<img :src="screenshot.url" :alt="`聊天截图 ${index + 1}`" />
<div class="screenshot-overlay">
<i class="fas fa-search-plus"></i>
</div>
<div v-if="screenshot.description" class="screenshot-desc">
{{ screenshot.description }}
</div>
</div>
</div>
</div>
<div class="review-meta">
<div class="tags">
<span v-for="tag in selectedReview.tags" :key="tag" class="tag">{{ tag }}</span>
</div>
<div class="review-date">评价时间{{ formatDate(selectedReview.date) }}</div>
</div>
</div>
</div>
</div>
<!-- 图片查看器 -->
<div v-if="showImageViewer" class="image-viewer-overlay" @click="closeImageViewer">
<div class="image-viewer-content">
<img :src="currentImage" alt="聊天截图" />
<button class="close-btn" @click="closeImageViewer">
<i class="fas fa-times"></i>
</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue'
//
const currentIndex = ref(0)
const cardWidth = ref(400)
const carouselRef = ref(null)
const showModal = ref(false)
const selectedReview = ref({})
const showImageViewer = ref(false)
const currentImage = ref('')
//
const reviews = ref([
{
customerName: '张总',
company: '科技有限公司',
avatar: 'https://randomuser.me/api/portraits/men/32.jpg',
rating: 5,
date: '2024-01-15',
content: '合作非常愉快!团队专业度很高,项目按时交付,质量超出预期。特别是在沟通过程中,响应速度很快,问题解决及时。',
tags: ['专业', '高效', '优质服务'],
chatScreenshots: [
{
url: 'https://via.placeholder.com/400x300/3498db/ffffff?text=项目需求讨论',
thumbnail: 'https://via.placeholder.com/200x150/3498db/ffffff?text=讨论',
description: '项目需求讨论'
},
{
url: 'https://via.placeholder.com/400x300/2ecc71/ffffff?text=进度汇报',
thumbnail: 'https://via.placeholder.com/200x150/2ecc71/ffffff?text=汇报',
description: '进度汇报'
},
{
url: 'https://via.placeholder.com/400x300/e74c3c/ffffff?text=最终确认',
thumbnail: 'https://via.placeholder.com/200x150/e74c3c/ffffff?text=确认',
description: '最终确认'
}
]
},
{
customerName: '李经理',
company: '贸易集团',
avatar: 'https://randomuser.me/api/portraits/women/44.jpg',
rating: 5,
date: '2024-01-10',
content: '服务态度很好,技术实力强,解决了我们很多技术难题。整个合作过程很顺畅,推荐!',
tags: ['技术强', '服务好', '值得推荐'],
chatScreenshots: [
{
url: 'https://via.placeholder.com/400x300/9b59b6/ffffff?text=技术方案讨论',
thumbnail: 'https://via.placeholder.com/200x150/9b59b6/ffffff?text=方案',
description: '技术方案讨论'
},
{
url: 'https://via.placeholder.com/400x300/f39c12/ffffff?text=问题解决过程',
thumbnail: 'https://via.placeholder.com/200x150/f39c12/ffffff?text=解决',
description: '问题解决过程'
}
]
},
{
customerName: '王总监',
company: '制造企业',
avatar: 'https://randomuser.me/api/portraits/men/67.jpg',
rating: 4,
date: '2024-01-05',
content: '项目完成度很高,团队配合默契。在遇到突发问题时,能够快速响应并提供解决方案。',
tags: ['高完成度', '快速响应', '团队协作'],
chatScreenshots: [
{
url: 'https://via.placeholder.com/400x300/1abc9c/ffffff?text=紧急问题处理',
thumbnail: 'https://via.placeholder.com/200x150/1abc9c/ffffff?text=紧急',
description: '紧急问题处理'
},
{
url: 'https://via.placeholder.com/400x300/34495e/ffffff?text=解决方案确认',
thumbnail: 'https://via.placeholder.com/200x150/34495e/ffffff?text=方案',
description: '解决方案确认'
},
{
url: 'https://via.placeholder.com/400x300/e67e22/ffffff?text=客户满意度反馈',
thumbnail: 'https://via.placeholder.com/200x150/e67e22/ffffff?text=反馈',
description: '客户满意度反馈'
},
{
url: 'https://via.placeholder.com/400x300/8e44ad/ffffff?text=后续合作讨论',
thumbnail: 'https://via.placeholder.com/200x150/8e44ad/ffffff?text=合作',
description: '后续合作讨论'
}
]
}
])
//
const totalSlides = computed(() => {
const cardsPerSlide = Math.floor(window.innerWidth / cardWidth.value) || 1
return Math.ceil(reviews.value.length / cardsPerSlide)
})
//
const formatDate = (dateStr) => {
const date = new Date(dateStr)
return date.toLocaleDateString('zh-CN')
}
const prevSlide = () => {
if (currentIndex.value > 0) {
currentIndex.value--
}
}
const nextSlide = () => {
if (currentIndex.value < totalSlides.value - 1) {
currentIndex.value++
}
}
const goToSlide = (index) => {
currentIndex.value = index
}
const openModal = (review) => {
selectedReview.value = review
showModal.value = true
document.body.style.overflow = 'hidden'
}
const closeModal = () => {
showModal.value = false
document.body.style.overflow = 'auto'
}
const openImageViewer = (imageUrl) => {
currentImage.value = imageUrl
showImageViewer.value = true
}
const closeImageViewer = () => {
showImageViewer.value = false
currentImage.value = ''
}
const updateCardWidth = () => {
if (window.innerWidth >= 1200) {
cardWidth.value = 400
} else if (window.innerWidth >= 768) {
cardWidth.value = 350
} else {
cardWidth.value = 300
}
}
//
onMounted(() => {
updateCardWidth()
window.addEventListener('resize', updateCardWidth)
})
onUnmounted(() => {
window.removeEventListener('resize', updateCardWidth)
document.body.style.overflow = 'auto'
})
</script>
<style lang="scss" scoped>
.customer-reviews {
padding: 80px 0;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
}
.reviews-header {
text-align: center;
margin-bottom: 60px;
h2 {
font-size: 2.5rem;
color: #2c3e50;
margin-bottom: 20px;
font-weight: 700;
i {
color: #3498db;
margin-right: 15px;
}
}
.separator {
width: 80px;
height: 4px;
background: linear-gradient(90deg, #3498db, #2980b9);
margin: 0 auto 20px;
border-radius: 2px;
}
.subtitle {
font-size: 1.1rem;
color: #7f8c8d;
max-width: 600px;
margin: 0 auto;
line-height: 1.6;
}
}
.reviews-container {
max-width: 1400px;
margin: 0 auto;
padding: 0 20px;
position: relative;
}
.reviews-carousel {
overflow: hidden;
margin-bottom: 40px;
}
.reviews-track {
display: flex;
transition: transform 0.5s ease;
gap: 30px;
}
.review-card {
flex: 0 0 400px;
background: white;
border-radius: 20px;
padding: 30px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid #e9ecef;
&:hover {
transform: translateY(-10px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
}
@media (max-width: 768px) {
flex: 0 0 300px;
padding: 20px;
}
}
.review-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20px;
}
.customer-info {
display: flex;
align-items: center;
gap: 15px;
}
.avatar {
width: 60px;
height: 60px;
border-radius: 50%;
overflow: hidden;
border: 3px solid #3498db;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.customer-details {
h4, h3 {
margin: 0 0 5px 0;
color: #2c3e50;
font-weight: 600;
}
.company {
color: #7f8c8d;
font-size: 0.9rem;
margin: 0 0 8px 0;
}
.rating {
.fa-star {
color: #ddd;
font-size: 0.9rem;
margin-right: 2px;
&.active {
color: #f39c12;
}
}
}
}
.review-date {
color: #95a5a6;
font-size: 0.85rem;
}
.review-content {
margin-bottom: 20px;
.review-text {
color: #34495e;
line-height: 1.6;
margin-bottom: 20px;
}
}
.chat-preview {
.screenshot-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-bottom: 15px;
}
.screenshot-item {
position: relative;
aspect-ratio: 16/9;
border-radius: 8px;
overflow: hidden;
border: 2px solid #e9ecef;
img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
&:hover img {
transform: scale(1.05);
}
&.more-indicator .more-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 1.2rem;
}
}
.view-more {
text-align: center;
color: #3498db;
font-size: 0.9rem;
font-weight: 500;
i {
margin-right: 5px;
}
}
}
.review-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
.tag {
background: linear-gradient(135deg, #3498db, #2980b9);
color: white;
padding: 4px 12px;
border-radius: 15px;
font-size: 0.8rem;
font-weight: 500;
}
}
.carousel-controls {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
.control-btn {
width: 50px;
height: 50px;
border: none;
background: #3498db;
color: white;
border-radius: 50%;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
&:hover:not(:disabled) {
background: #2980b9;
transform: scale(1.1);
}
&:disabled {
background: #bdc3c7;
cursor: not-allowed;
}
}
.indicators {
display: flex;
gap: 10px;
.indicator {
width: 12px;
height: 12px;
border-radius: 50%;
background: #bdc3c7;
cursor: pointer;
transition: all 0.3s ease;
&.active {
background: #3498db;
transform: scale(1.2);
}
}
}
}
//
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 20px;
}
.modal-content {
background: white;
border-radius: 20px;
max-width: 800px;
width: 100%;
max-height: 90vh;
overflow-y: auto;
position: relative;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30px 30px 20px;
border-bottom: 1px solid #e9ecef;
.close-btn {
width: 40px;
height: 40px;
border: none;
background: #e74c3c;
color: white;
border-radius: 50%;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: #c0392b;
transform: scale(1.1);
}
}
}
.modal-body {
padding: 30px;
h4 {
color: #2c3e50;
margin-bottom: 15px;
font-weight: 600;
}
.review-content p {
color: #34495e;
line-height: 1.6;
margin-bottom: 30px;
}
}
.chat-screenshots {
margin-bottom: 30px;
.screenshots-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-top: 20px;
}
.screenshot-full {
position: relative;
border-radius: 12px;
overflow: hidden;
cursor: pointer;
border: 2px solid #e9ecef;
transition: all 0.3s ease;
&:hover {
transform: scale(1.02);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
}
img {
width: 100%;
height: auto;
display: block;
}
.screenshot-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
i {
color: white;
font-size: 1.5rem;
}
}
&:hover .screenshot-overlay {
opacity: 1;
}
.screenshot-desc {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
color: white;
padding: 15px 10px 10px;
font-size: 0.9rem;
text-align: center;
}
}
}
.review-meta {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 20px;
border-top: 1px solid #e9ecef;
.tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.review-date {
color: #7f8c8d;
font-size: 0.9rem;
}
}
//
.image-viewer-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.95);
display: flex;
align-items: center;
justify-content: center;
z-index: 1100;
padding: 20px;
}
.image-viewer-content {
position: relative;
max-width: 90vw;
max-height: 90vh;
img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
border-radius: 8px;
}
.close-btn {
position: absolute;
top: -50px;
right: 0;
width: 40px;
height: 40px;
border: none;
background: #e74c3c;
color: white;
border-radius: 50%;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: #c0392b;
transform: scale(1.1);
}
}
}
//
@media (max-width: 768px) {
.customer-reviews {
padding: 40px 0;
}
.reviews-header h2 {
font-size: 2rem;
}
.reviews-track {
gap: 20px;
}
.modal-content {
margin: 10px;
border-radius: 15px;
}
.modal-header,
.modal-body {
padding: 20px;
}
.chat-screenshots .screenshots-grid {
grid-template-columns: 1fr;
}
.review-meta {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
}
</style>

+ 17
- 53
src/views/pages/Home.vue View File

@ -14,6 +14,8 @@ import ServiceSection from '../../components/home/ServiceSection.vue';
import CaseSection from '@/components/home/CaseSection.vue';
// CTA
import CallToAction from '../../components/CallToAction.vue';
//
import CustomerReviews from '../../components/CustomerReviews.vue';
//
const router = useRouter();
@ -87,7 +89,19 @@ onMounted(() => {
<h2 style="color: #fff;">我们的优势</h2>
<p>为什么选择瀚海黎明</p>
</div>
<div class="advantages-grid" data-aos="fade-up" data-aos-delay="100">
<div class="advantage-card" data-aos="fade-right"
v-for="(item, index) in serviceProcess"
:key="item.id"
:data-aos-delay="100 * (index + 1)">
<div class="advantage-number">{{ item.num }}</div>
<h3>{{ item.title }}</h3>
<p>{{ item.info }}</p>
</div>
</div>
<!-- <div class="advantages-grid" data-aos="fade-up" data-aos-delay="100">
<div class="advantage-card" data-aos="fade-right" data-aos-delay="100">
<div class="advantage-number">01</div>
<h3>专业团队</h3>
@ -118,63 +132,13 @@ onMounted(() => {
<h3>售后支持</h3>
<p>提供全面的售后支持和维护服务确保系统稳定运行</p>
</div>
</div>
</div> -->
</div>
</div>
</section>
<!-- 客户评价视差背景 -->
<section class="testimonials-section parallax-container">
<div class="parallax-bg" :style="{ backgroundImage: `url(${configParams.home_testimonials_bg})` }" data-depth="0.1"></div>
<div class="section-content">
<div class="section-header" data-aos="fade-up">
<h2>客户评价</h2>
<p>听听我们的客户怎么说</p>
</div>
<div class="testimonials-slider" data-aos="fade-up">
<div class="testimonial-card">
<div class="testimonial-rating">
<span></span><span></span><span></span><span></span><span></span>
</div>
<p class="testimonial-text">"瀚海黎明团队的专业素养和技术能力给我们留下了深刻印象。他们不仅按时交付了高质量的产品,还提供了持续的技术支持。"</p>
<div class="testimonial-author">
<img src="https://randomuser.me/api/portraits/men/32.jpg" alt="张总" class="author-avatar" />
<div class="author-info">
<h4>张总</h4>
<p>某科技有限公司 CEO</p>
</div>
</div>
</div>
<div class="testimonial-card">
<div class="testimonial-rating">
<span></span><span></span><span></span><span></span><span></span>
</div>
<p class="testimonial-text">"与瀚海黎明的合作非常愉快。他们理解我们的业务需求,并将其转化为了一个功能强大且用户友好的系统。"</p>
<div class="testimonial-author">
<img src="https://randomuser.me/api/portraits/women/44.jpg" alt="李经理"
class="author-avatar" />
<div class="author-info">
<h4>李经理</h4>
<p>某医疗集团 IT总监</p>
</div>
</div>
</div>
<div class="testimonial-card">
<div class="testimonial-rating">
<span></span><span></span><span></span><span></span><span></span>
</div>
<p class="testimonial-text">"瀚海黎明团队的响应速度和解决问题的能力令人印象深刻。他们不仅是技术提供商,更是我们业务发展的战略伙伴。"</p>
<div class="testimonial-author">
<img src="https://randomuser.me/api/portraits/men/67.jpg" alt="王董事" class="author-avatar" />
<div class="author-info">
<h4>王董事</h4>
<p>某金融服务公司 董事长</p>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 客户评价 -->
<CustomerReviews />
<!-- 合作流程视差滚动 -->
<section class="process-section" data-aos="fade-up">


Loading…
Cancel
Save