50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | TestimonialBoxSwitcher(用户评价展示组件)

发布于:2025-08-04 ⋅ 阅读:(17) ⋅ 点赞:(0)

📅 我们继续 50 个小项目挑战!—— TestimonialBoxSwitcher组件

仓库地址:https://github.com/SunACong/50-vue-projects

项目预览地址:https://50-vue-projects.vercel.app/

在这里插入图片描述


使用 Vue 3 的组合式 API (<script setup>) 和 Tailwind CSS 构建一个优雅、自动轮播的客户评价(Testimonial)展示组件。这个组件非常适合用在个人作品集、公司官网或产品页面,能够动态展示客户的好评,提升可信度和吸引力。

我们将实现一个带有平滑进度条指示器的自动轮播功能,当用户悬停时暂停,并在图片加载失败时优雅降级。让我们开始吧!🚀


🎯 应用目标

  • 创建一个自动轮播的客户评价展示区域
  • 展示每位客户的头像、姓名、职位和评价文本
  • 添加一个从左到右流动的进度条,指示当前轮播状态
  • 实现自动切换(10秒/条),并在用户悬停时暂停
  • 处理头像图片加载失败的情况,显示默认占位图
  • 使用 Vue 3 的响应式系统和生命周期钩子管理定时器

🔧 技术实现点

技术点 描述
Vue 3 <script setup> 使用 ref, computed, watch, onMounted, onUnmounted
computed 计算属性 currentTestimonial 动态返回当前显示的评价
onMounted / onUnmounted 组件挂载时启动定时器,卸载时清除定时器,防止内存泄漏
setInterval 创建两个定时器:一个用于切换评价,一个用于更新进度条
watch 监听 currentIndex 变化,重置进度条(虽然逻辑上由 updateTestimonial 重置,但可增强健壮性)
@error 事件 图片加载失败时触发 handleImageError,替换为默认头像
Tailwind CSS transition-all duration-10000 ease-linear 为进度条 div 添加超长持续时间(10秒)的线性过渡,实现平滑流动效果
:style="{ width: progress + '%' }" 动态绑定进度条宽度

📚 核心数据与状态

1. 评价数据 (testimonials)

这是一个包含所有客户评价信息的数组,使用 ref 包裹使其响应式。每条评价包含:

  • name: 客户姓名
  • position: 客户职位
  • photo: 头像图片 URL
  • text: 评价文本

2. 响应式状态管理

状态变量 类型 初始值 作用
currentIndex ref(Number) 0 当前显示评价的索引
progress ref(Number) 0 进度条的百分比 (0-100)
intervalId let null 存储评价切换定时器的 ID
progressIntervalId let null 存储进度条更新定时器的 ID

3. 常量

常量 作用
DEFAULT_AVATAR 'https://via.placeholder.com/150?text=User' 当头像图片加载失败时使用的默认占位图 URL

🖌️ 组件实现

🎨 模板结构 <template>

<template>
    <div
        class="font-montserrat m-0 flex min-h-screen items-center justify-center overflow-hidden bg-gray-100 p-4">
        <div
            class="relative mx-auto w-full max-w-2xl rounded-lg bg-blue-600 p-8 text-white shadow-md">
            <!-- 进度条 -->
            <div
                class="absolute top-0 left-0 h-1 w-full origin-left bg-white transition-all duration-10000 ease-linear"
                :style="{ width: progress + '%' }"></div>

            <!-- 引用图标 -->
            <div class="absolute text-3xl text-white/30">
                <i class="fas fa-quote-right absolute top-6 left-150"></i>
                <i class="fas fa-quote-left absolute top-6 right-1"></i>
            </div>

            <!-- 推荐文本 -->
            <p class="testimonial mb-6 pt-4 text-justify leading-relaxed">
                {{ currentTestimonial.text }}
            </p>

            <!-- 用户信息 -->
            <div class="user flex items-center justify-center">
                <img
                    :src="currentTestimonial.photo"
                    :alt="currentTestimonial.name"
                    class="user-image mr-4 h-16 w-16 rounded-full object-cover"
                    @error="handleImageError" />
                <div class="user-details text-left">
                    <h4 class="username text-lg font-bold">{{ currentTestimonial.name }}</h4>
                    <p class="role text-white/80">{{ currentTestimonial.position }}</p>
                </div>
            </div>
        </div>
    </div>
</template>

模板结构解析:

  1. 外层容器

    • flex min-h-screen items-center justify-center:使用 Flexbox 将评价卡片在视口中完美居中。
    • bg-gray-100:浅灰色背景,营造干净的氛围。
    • font-montserrat:使用 Montserrat 字体(需引入)。
  2. 评价卡片 (div)

    • relative:为内部的绝对定位元素(进度条、引用图标)提供定位上下文。
    • bg-blue-600:深蓝色背景,专业且醒目。
    • text-white:白色文字,确保在深色背景上清晰可读。
    • rounded-lg / shadow-md:圆角和阴影,提升视觉层次。
    • p-8:内部留白。
  3. 核心元素

    • 进度条 (div)
      • absolute top-0 left-0:定位在卡片顶部。
      • h-1:高度为 1。
      • w-full:宽度为父容器的 100%。
      • origin-left:设置变换原点为左端,确保 width 从左向右增长。
      • transition-all duration-10000 ease-linear关键! 这个超长的 duration-10000 (10秒) 过渡,配合 JavaScript 每 10ms 更新一次 progress,实现了非常平滑的线性流动效果,完美匹配 10秒的轮播周期。
      • :style="{ width: progress + '%' }":动态控制宽度。
    • 引用图标 (i)
      • 使用 Font Awesome 图标 (fas fa-quote-right, fas fa-quote-left)。
      • absolute 定位,放置在卡片左右上角。
      • text-white/30:白色,30% 透明度,作为装饰性背景。
    • 评价文本 (p)
      • text-justify:文本两端对齐,看起来更整齐。
      • leading-relaxed:增加行高,提升可读性。
      • pt-4:顶部内边距,避免与进度条重叠。
    • 用户信息 (div)
      • flex items-center justify-center:水平居中用户头像和信息。
      • img 使用 object-cover 确保图片填充容器且不被拉伸。
      • @error="handleImageError":监听图片加载错误。

💻 脚本逻辑 <script setup>

<script setup>
    import { ref, onMounted, onUnmounted, computed, watch } from 'vue'

    // 默认头像 - 当图片加载失败时使用
    const DEFAULT_AVATAR = 'https://via.placeholder.com/150?text=User'

    // 推荐语数据
    const testimonials = ref([
        // ... 评价数据
    ])

    // 当前推荐语索引
    const currentIndex = ref(0)
    // 进度条百分比
    const progress = ref(0)
    // 定时器ID
    let intervalId = null
    let progressIntervalId = null

    // 当前推荐语
    const currentTestimonial = computed(() => testimonials.value[currentIndex.value])

    // 处理图片加载错误
    const handleImageError = (e) => {
        e.target.src = DEFAULT_AVATAR
    }

    // 更新推荐语
    const updateTestimonial = () => {
        currentIndex.value = (currentIndex.value + 1) % testimonials.value.length
        progress.value = 0 // 重置进度条
    }

    // 启动定时器
    const startTimer = () => {
        // 10秒切换一次推荐语
        intervalId = setInterval(updateTestimonial, 10000)

        // 更新进度条
        progressIntervalId = setInterval(() => {
            progress.value += 0.1 // 每10ms增加0.1%,10秒 = 10000ms,10000/10 = 1000次,100/1000 = 0.1
            if (progress.value >= 100) progress.value = 100
        }, 10)
    }

    // 组件挂载时启动定时器
    onMounted(startTimer)

    // 组件卸载时清除定时器
    onUnmounted(() => {
        clearInterval(intervalId)
        clearInterval(progressIntervalId)
    })

    // 当currentIndex变化时重置进度条动画
    // (虽然 updateTestimonial 已重置,但此 watch 可确保万无一失)
    watch(currentIndex, () => {
        progress.value = 0
    })
</script>

脚本逻辑详解:

  • startTimer 方法

    • 创建两个独立的 setInterval
      1. intervalId:每 10000 毫秒(10秒)调用一次 updateTestimonial,切换到下一条评价。
      2. progressIntervalId:每 10 毫秒将 progress 增加 0.1。10秒内总共增加 10000ms / 10ms * 0.1% = 100%,完美匹配轮播周期。
    • 使用两个定时器是为了获得比单个 10000ms 定时器更平滑的进度条动画(1000次更新 vs 1次更新)。
  • updateTestimonial 方法

    • 使用模运算 (currentIndex.value + 1) % testimonials.value.length 实现索引的循环递增。
    • 关键一步:在切换评价后立即将 progress.value 重置为 0,为下一次进度条动画做准备。
  • 生命周期钩子

    • onMounted(startTimer):组件挂载后立即启动轮播。
    • onUnmounted至关重要!在组件卸载(如页面跳转)时清除所有定时器,防止内存泄漏和不必要的后台运行。
  • watch 监听器

    • 虽然 updateTestimonial 已经重置了 progress,但添加 watch 可以确保在任何情况下 currentIndex 变化时进度条都会被重置,增加了代码的健壮性。
  • handleImageError 方法

    • <img>src 加载失败时触发。
    • e.target.src = DEFAULT_AVATAR:将出错的 img 元素的 src 替换为预设的默认头像,保证 UI 不会因图片 404 而出现破图。

🎨 Tailwind CSS 样式重点

类名 作用
font-montserrat 使用 Montserrat 字体
m-0 / p-4 / p-8 / pt-4 / mb-6 / mr-4 外边距和内边距
flex / items-center / justify-center / justify-between Flexbox 布局
min-h-screen 最小高度为视口高度
overflow-hidden 隐藏溢出内容
bg-gray-100 / bg-blue-600 背景颜色
w-full / max-w-2xl / mx-auto 宽度和居中
overflow-hidden 隐藏溢出
rounded-lg 圆角
text-white / text-white/30 / text-white/80 / text-lg / text-3xl 文字颜色、透明度和大小
font-bold / font-medium 字体粗细
shadow-md 阴影
transition-all duration-10000 ease-linear 核心! 为进度条提供10秒的平滑线性过渡
h-1 / h-16 / w-16 / w-full 高度和宽度
origin-left 设置变换原点为左端
absolute / relative / top-0 / left-0 / top-6 / left-150 / right-1 定位
text-justify / leading-relaxed 文本对齐和行高
rounded-full 圆形(头像)
object-cover 图片填充容器且不拉伸
cursor-pointer (可选)如果添加了手动切换按钮

⚠️ 注意事项与潜在问题

  1. Font Awesome 图标:模板中使用了 <i class="fas fa-quote-right">,这需要在项目中正确引入 Font Awesome CSS 文件,否则图标不会显示。可以考虑使用 SVG 图标或 Tailwind 的 @apply 定义图标类来替代。
  2. left-150:Tailwind 默认没有 left-150 这个类(除非自定义了 theme.spacing)。可能需要使用 left-[600px](如果需要 600px)或一个存在的类如 left-10
  3. 性能progressIntervalId 每 10ms 触发一次,虽然对于现代浏览器来说通常不是问题,但在低端设备上可能有轻微影响。可以考虑增加间隔(如 50ms 或 100ms)并相应调整 progress 的增加值。
  4. 暂停功能:当前代码没有实现“鼠标悬停暂停”的功能。可以通过在卡片上添加 @mouseenter@mouseleave 事件监听器,在悬停时 clearInterval 并在离开时重新 setInterval 来实现。

📁 常量定义 + 组件路由

constants/index.js 添加组件预览常量:

{
        id: 47,
        title: 'TestimonialBoxSwitcher',
        image: 'https://50projects50days.com/img/projects-img/47-testimonial-box-switcher.png',
        link: 'TestimonialBoxSwitcher',
    },

router/index.js 中添加路由选项:

{
        path: '/TestimonialBoxSwitcher',
        name: 'TestimonialBoxSwitcher',
        component: () => import('@/projects/TestimonialBoxSwitcher.vue'),
    },

🏁 总结

我们成功构建了一个功能完善、视觉效果出色的自动轮播客户评价组件。通过巧妙地结合 Vue 3 的响应式系统、生命周期钩子和 Tailwind CSS 的实用类(特别是 transition-all duration-10000 的妙用),我们实现了流畅的用户体验。

你还可以扩展以下功能:

  • 添加手动控制:增加“上一条”/“下一条”按钮,让用户可以手动切换。
  • 指示点:在卡片底部添加小圆点,指示当前是第几条评价,并可点击跳转。
  • 悬停暂停:如上所述,实现鼠标悬停时暂停轮播和进度条。
  • 动画效果:为评价文本的切换添加淡入淡出或滑动动画。
  • 响应式调整:在小屏幕上调整字体大小、间距和头像尺寸。
  • 更多数据:从 API 动态加载评价数据。
  • 评分星级:在用户信息旁添加评分星级(如 5颗星)。

👉 下一篇,我们将完成RandomImageGenerator组件,一个随机图片生成器组件。🚀

感谢阅读,欢迎点赞、收藏和分享 😊


网站公告

今日签到

点亮在社区的每一天
去签到