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

仓库地址:https://gitee.com/hhm-hhm/50days50projects.git

​​​​

构建一个美观且交互性强的用户满意度评分组件。这个组件允许用户通过点击表情图标来选择他们的满意度等级,并在提交后显示感谢信息,非常适合用于收集客户反馈、产品评价或服务评分。

让我们开始吧!🚀

🌀 组件目标

  • 实现可点击的评分选项,支持视觉反馈(悬停、选中效果)
  • 展示提交前后的不同界面状态
  • 利用 Tailwind CSS 快速构建现代化、响应式的 UI
  • 提供流畅的用户体验和动画效果

🔧 FeedbackUiDesign.tsx组件实现

import React, { useState } from 'react'

// 定义评分选项类型
interface RatingOption {
    id: number
    imgUrl: string
    label: string
    value: string
}

const FeedbackUiDesign: React.FC = () => {
    // 状态管理
    const [selectedRating, setSelectedRating] = useState<string>('Satisfied') // 默认选中
    const [isSubmitted, setIsSubmitted] = useState<boolean>(false)

    // 评分选项数据
    const ratings: RatingOption[] = [
        {
            id: 1,
            imgUrl: 'https://img.icons8.com/external-neu-royyan-wijaya/64/000000/external-emoji-neumojis-smiley-neu-royyan-wijaya-17.png',
            label: 'Unhappy',
            value: 'Unhappy',
        },
        {
            id: 2,
            imgUrl: 'https://img.icons8.com/external-neu-royyan-wijaya/64/000000/external-emoji-neumojis-smiley-neu-royyan-wijaya-3.png',
            label: 'Neutral',
            value: 'Neutral',
        },
        {
            id: 3,
            imgUrl: 'https://img.icons8.com/external-neu-royyan-wijaya/64/000000/external-emoji-neumojis-smiley-neu-royyan-wijaya-30.png',
            label: 'Satisfied',
            value: 'Satisfied',
        },
    ]

    // 选择评分
    const selectRating = (value: string) => {
        setSelectedRating(value)
    }

    // 提交反馈
    const submitFeedback = () => {
        setIsSubmitted(true)
    }

    return (
        <div className="flex min-h-screen items-center justify-center bg-gray-900 p-4 font-sans">
            {/* 主面板(未提交) */}
            {!isSubmitted ? (
                <div className="w-full max-w-md rounded-lg bg-white p-8 text-center shadow-md">
                    <strong className="leading-relaxed text-gray-800">
                        How satisfied are you with our
                        <br />
                        customer support performance?
                    </strong>

                    {/* 评分选项容器 */}
                    <div className="my-8 flex justify-center">
                        {ratings.map((rating) => (
                            <div
                                key={rating.id}
                                onClick={() => selectRating(rating.value)}
                                className={`flex cursor-pointer flex-col items-center rounded-md p-5 transition-all duration-200 ${
                                    selectedRating === rating.value
                                        ? 'scale-110 bg-white shadow-lg'
                                        : 'hover:bg-gray-50'
                                }`}
                                role="button"
                                tabIndex={0}
                                onKeyDown={(e) => {
                                    if (e.key === 'Enter' || e.key === ' ') {
                                        selectRating(rating.value)
                                    }
                                }}
                                aria-pressed={selectedRating === rating.value}
                                aria-label={`Select ${rating.label} rating`}>
                                <img
                                    src={rating.imgUrl}
                                    alt={rating.label}
                                    className="mb-3 h-10 w-10"
                                    loading="lazy"
                                />
                                <small className="text-gray-600">{rating.label}</small>
                            </div>
                        ))}
                    </div>

                    {/* 提交按钮 */}
                    <button
                        onClick={submitFeedback}
                        className="cursor-pointer rounded-md bg-gray-800 px-8 py-3 text-white transition-all duration-200 hover:bg-gray-700 focus:ring-2 focus:ring-gray-600 focus:ring-offset-2 focus:outline-none active:scale-98">
                        Send Review
                    </button>
                </div>
            ) : (
                /* 提交后的感谢面板 */
                <div className="w-full max-w-md rounded-lg bg-white p-8 text-center shadow-md">
                    {/* 使用 Heroicons 或内联 SVG 替代 Font Awesome(避免额外依赖) */}
                    <div className="mb-4 text-4xl text-red-500">❤️</div>
                    <strong className="mb-1 block text-xl text-gray-800">Thank You!</strong>
                    <strong className="mb-2 text-gray-700">Feedback: {selectedRating}</strong>
                    <p className="text-sm text-gray-500">
                        We'll use your feedback to improve our customer support
                    </p>
                </div>
            )}
            <div className="fixed right-20 bottom-5 z-100 text-2xl text-red-500">
                CSDN@Hao_Harrision
            </div>
        </div>
    )
}

export default FeedbackUiDesign

🔄 关键差异总结

🔄 关键差异总结

功能 Vue 3 React + TS
状态 ref() useState()
条件渲染 v-if {condition ? A : B}
列表渲染 v-for .map()
动态类 :class 对象/数组 模板字符串
事件传参 @click="fn(val)" onClick={() => fn(val)}
类型安全 interface + 泛型
无障碍 手动添加 显式增强(推荐)

🔁 转换说明

1. 状态管理:ref → useState

Vue React
const selectedRating = ref('Satisfied') const [selectedRating, setSelectedRating] = useState('Satisfied')
const isSubmitted = ref(false) const [isSubmitted, setIsSubmitted] = useState(false)

✅ 使用 useState 实现响应式状态。


2. 数据结构:静态数组 → TypeScript 接口

  • 定义 RatingOption 接口确保类型安全;
  • ratings 不需要响应式,直接定义为常量数组。
interface RatingOption {
  id: number;
  imgUrl: string;
  label: string;
  value: string;
}

3. 条件渲染:v-if → 三元表达式或逻辑与

{!isSubmitted ? <MainPanel /> : <ThankYouPanel />}

✅ 清晰表达两种 UI 状态。


4. 事件处理:@click → onClick

  • Vue: @click="selectRating(rating.value)"
  • React: onClick={() => selectRating(rating.value)}

✅ 注意:必须使用箭头函数传递参数,否则会立即执行。


5. 动态类名::class → 模板字符串或 clsx

使用模板字符串实现条件类:

className={`... ${selectedRating === rating.value ? 'scale-110 bg-white shadow-lg' : 'hover:bg-gray-50'}`}

若项目复杂,可引入 clsxtailwind-merge,但此处简单场景无需依赖。


6. 无障碍支持(a11y)增强

  • 添加 role="button" 和 tabIndex={0} 使 div 可聚焦;
  • onKeyDown 支持键盘交互(Enter/Space);
  • aria-pressed 表示当前选中状态;
  • aria-label 描述操作意图;
  • 按钮添加 focus:ring 样式。

✅ 提升可访问性,符合 WCAG 标准。


7. 图标处理:移除 Font Awesome 依赖

  • 原 Vue 使用 <i class="fas fa-heart">(需引入 Font Awesome);
  • React 版本改用 原生 emoji ❤️ 或可替换为 SVG;
  • 避免在简单项目中引入额外 CSS 库。

如需保持一致性,可安装 @heroicons/react 并使用 <HeartIcon />


8. 图片优化

  • 添加 loading="lazy" 提升性能;
  • alt 属性使用 label,语义清晰。

✅ 注意事项

  1. 不要直接绑定函数调用onClick={selectRating(rating.value)} 会立即执行 ❌;
  2. key 使用唯一 IDrating.id 是稳定 key;
  3. 默认选中状态:初始化 selectedRating 为 'Satisfied' 与 Vue 一致;
  4. 无副作用:此组件纯 UI,无需 useEffect

💡 可选优化

  • 使用 useCallback 包裹 selectRating(非必需,因无子组件 memo);
  • 将评分项提取为独立组件 <RatingOption />
  • 添加动画过渡(如 framer-motion)。

🎨 TailwindCSS 样式重点讲解

🎯 TailwindCSS 样式说明
类名 作用
flex 启用 Flexbox 布局
min-h-screen 最小高度为视口高度,确保内容垂直居中
items-center / justify-center Flex 项目在主轴和交叉轴上居中
bg-gray-100 浅灰色背景
p-4 内边距
font-sans 无衬线字体
w-full max-w-md 宽度占满,但最大宽度限制为 md (28rem)
rounded-lg 中等圆角
bg-white 白色背景
p-8 较大的内边距
text-center 文本居中
shadow-md 中等阴影
leading-relaxed 较宽松的行高
text-gray-800 / text-gray-600 / text-gray-500 / text-gray-700 不同深浅的灰色文字
my-8 上下外边距
justify-center Flex 容器内项目居中对齐
cursor-pointer 鼠标指针变为手型
flex-col Flex 方向为垂直
p-5 内边距
transition-all duration-200 所有属性变化在 200ms 内平滑过渡
scale-110 放大到 110%
bg-white shadow-lg 选中时的背景和更强阴影
hover:bg-gray-50 悬停时的浅灰色背景
mb-3 下边距
h-10 w-10 图标固定大小
rounded-md 按钮圆角
bg-gray-800 / bg-gray-700 按钮背景色及悬停色
px-8 py-3 按钮内边距
text-white 白色文字
hover:bg-gray-700 悬停时按钮颜色变浅
active:scale-98 按下时轻微缩小
text-4xl 大号文字(爱心图标)
text-red-500 红色文字
block 块级元素
text-xl 大号文字

🦌 路由组件 + 常量定义

router/index.tsx 中 children数组中添加子路由

{
    path: '/',
    element: <App />,
    children: [
       ...
        {
                path: '/FeedbackUiDesign',
                lazy: () =>
                    import('@/projects/FeedbackUiDesign').then((mod) => ({
                        Component: mod.default,
                    })),
            },
    ],
 },
constants/index.tsx 添加组件预览常量
import demo44Img from '@/assets/pic-demo/demo-44.png'
省略部分....
export const projectList: ProjectItem[] = [
    省略部分....
      {
        id: 44,
        title: 'Feedback Ui Design',
        image: demo44Img,
        link: 'FeedbackUiDesign',
    },
]

🚀 小结

通过这篇文章,我们使用 React19 和 TailwindCSS 创建了一个直观、美观且交互性良好的用户反馈评分组件。

想要让你的反馈组件更加强大?考虑以下扩展功能:

✅ 提交到后端:在 submitFeedback 函数中添加 fetch 或 axios 请求,将 selectedRating 发送到服务器保存。
✅ 输入框收集详细反馈:在提交前,增加一个 <textarea> 让用户输入更详细的反馈意见。
✅ 动画效果:为面板切换或图标添加更丰富的动画(例如使用 vue-transition)。
✅ 自定义图标:使用本地 SVG 图标或不同的图标库替换现有的表情图片。
✅ 评分重置:在感谢面板添加一个“再次评分”按钮,允许用户重新选择。
✅ 本地存储:使用 localStorage 记住用户之前的评分(如果需要)。

📅 明日预告: 我们将完成NetflixMobileNavigation组件,一个模仿Netflix移动端的导航样式。🚀

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


原文链接:https://blog.csdn.net/qq_44808710/article/details/149782861

每天造一个轮子,码力暴涨不是梦!🚀

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐