React实现购物车功能

news2025/1/19 2:27:11

今日学习React的useReducer,实现了一个购物车功能

文章目录

目录

效果展示

逻辑代码

CSS代码


效果展示

逻辑代码

import {useReducer} from "react";
import './index.css';
import {  message} from 'antd';

export function ShoppingCount(){
    // 初始化购物车数据,包含商品的基本信息如名称、图片、价格、数量和是否选中
    const initData = [
        {id: 1, name: "精匠传成摇椅躺椅懒人休闲家用加厚", image: "https://img14.360buyimg.com/jdcms/s460x460_jfs/t1/122203/6/41238/105007/65a546caF3decd4f8/ad98fa84a99bc4ec.jpg.avif", price: 10, count: 1, isFlag: true},
        {id: 2, name: "HUANWE2024新款旗舰手机", image: "https://img13.360buyimg.com/jdcms/s460x460_jfs/t1/246297/2/16373/35951/66c8a072Fc02c9f02/b2a6cefc921fcefd.jpg.avif", price: 1199, count: 1, isFlag: false},
        {id: 3, name: "四川爱媛38号果冻橙", image: "https://img20.360buyimg.com/jdcms/s460x460_jfs/t1/96363/40/52840/128397/67122eedF2da2fb38/46274ae050a78353.jpg.avif", price: 17.9, count: 1, isFlag: true}
    ];

    // 定义购物项类型,用于购物车中每个商品的信息结构
    type ShoppingItem = {
        isFlag: boolean; // 商品是否被选中
        id: number,     // 商品ID
        name: string,   // 商品名称
        image: string,  // 商品图片URL
        price: number,  // 商品价格
        count: number   // 商品数量
    }

    // 定义购物车操作接口,用于指定对购物车商品的操作类型和受影响的商品ID
    interface Action {
        type: "ADD" | "SUB" | 'DELETE' | 'isFlag' // 操作类型:添加、减少、删除商品或改变商品选中状态
        id: number                               // 操作针对的商品ID
    }


    /**
     * 购物项 reducer 函数,用于更新购物项状态
     * @param state 当前的购物项状态,是一个 ShoppingItem 数组
     * @param action 要处理的动作,包含类型和购物项 ID
     * @returns 返回新的购物项状态数组
     */
    const reducer = (state: ShoppingItem[], action: Action) => {
        // 查找与 action.id 匹配的购物项
        const item = state.find((item) => item.id === action.id);
        // 如果找不到匹配的购物项,则直接返回当前状态
        if (item == null) {
            return state;
        }

        // 根据 action.type 执行相应的操作
        switch (action.type) {
            case "ADD":
                // 增加购物项的数量
                item.count++;
                return [...state];
            case "SUB":
                // 减少购物项的数量,但至少保持为 1
                // 减少购物项的数量,但至少保持为 1
                if (item.count > 1) {
                    item.count--;
                }
                return [...state];
            case "DELETE":
                // 删除购物项
                return state.filter((item) => item.id !== action.id);
            case "isFlag":
                // 切换购物项的选中状态
                item.isFlag = !item.isFlag;
                return [...state];
            default:
                // 对于未知的操作,直接返回当前状态
                return state;
        }
    }

    const [state, dispatch] = useReducer(reducer, initData);
    //计算总价
    const totalPrice = state.filter(item => item.isFlag).reduce((acc, item) => acc + item.price * item.count, 0);

    return (
        <div className="cart-container">
            <h2>购物车</h2>
            <div className="cart-items">
                {
                    state.map((item: ShoppingItem) => {
                        return (
                            <div className="cart-item" key={item.id}>
                                <label className="custom-checkbox">
                                    <input type="checkbox" checked={item.isFlag} onChange={() => {
                                        dispatch({type: "isFlag", id: item.id});
                                    }}/>
                                    <span className="checkmark"></span>
                                </label>
                                <img src={item.image} alt={item.name}/>
                                <div className="item-details">
                                    <h3>{item.name}</h3>
                                    <p className="price">价格: ¥{item.price}</p>
                                    <div className="quantity-controls">
                                        //自减
                                        <button className="minus" onClick={() => {
                                            dispatch({type: "SUB", id: item.id});
                                        }}>-
                                        </button>
                                        //数量
                                        <span className="count">{item.count}</span>
                                        //自增
                                        <button className="plus" onClick={() => {
                                            dispatch({type: "ADD", id: item.id});
                                        }}>+
                                        </button>
                                    </div>
                                </div>
                                //删除按钮
                                <button className="remove-btn" onClick={() => dispatch({type: "DELETE", id: item.id})}>删除
                                </button>
                            </div>
                        )
                    })
                }
            </div>
            <div className="cart-summary">
                <p>总金额:
                    <span className="total-price">
                        ¥{totalPrice}
                    </span>
                </p>
                //结算按钮
                <button className="checkout-btn" onClick={() => {

                    message.success({
                        type: 'success',
                        content: '总价格为¥'+totalPrice,
                    });
                }}>去结算</button>
            </div>
        </div>
    )
}

CSS代码

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: Arial, sans-serif;
}

body {
    background-color: #f4f4f4;
    padding: 20px;
}

.cart-container {
    max-width: 800px;
    margin: 0 auto;
    background-color: #fff;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

h2 {
    text-align: center;
    margin-bottom: 20px;
    font-size: 24px;
}

.cart-items {
    margin-bottom: 20px;
}

.cart-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 15px 0;
    border-bottom: 1px solid #ddd;
}

.cart-item img {
    width: 100px;
    height: 100px;
    object-fit: cover;
    border-radius: 8px;
    margin-right: 20px;
}

.item-details {
    flex-grow: 1;
}

.item-details h3 {
    font-size: 18px;
    margin-bottom: 10px;
}

.price {
    font-size: 16px;
    color: #333;
    margin-bottom: 10px;
}

.quantity-controls {
    display: flex;
    align-items: center;
}

.quantity-controls button {
    width: 30px;
    height: 30px;
    background-color: #f4f4f4;
    border: 1px solid #ccc;
    border-radius: 4px;
    font-size: 18px;
    line-height: 30px;
    cursor: pointer;
}

.quantity-controls .count {
    margin: 0 10px;
    font-size: 16px;
}

.remove-btn {
    background-color: #ff4c4c;
    color: white;
    border: none;
    padding: 10px 20px;
    border-radius: 4px;
    cursor: pointer;
}

.remove-btn:hover {
    background-color: #e60000;
}

.cart-summary {
    text-align: right;
    padding-top: 10px;
}

.cart-summary .total-price {
    font-weight: bold;
    font-size: 18px;
    color: #333;
}

.checkout-btn {
    background-color: #ff8c00;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    margin-top: 10px;
}

.checkout-btn:hover {
    background-color: #ff6500;
}

/* 自定义单选框样式 */
.custom-checkbox {
    position: relative;
    padding-left: 25px;
    margin-right: 20px;
    cursor: pointer;
}

.custom-checkbox input {
    position: absolute;
    opacity: 0;
    cursor: pointer;
}

.custom-checkbox .checkmark {
    position: absolute;
    top: 0;
    left: 0;
    height: 20px;
    width: 20px;
    background-color: #ccc;
    border-radius: 50%;
}

.custom-checkbox input:checked ~ .checkmark {
    background-color: #4CAF50;
}

.custom-checkbox .checkmark:after {
    content: "";
    position: absolute;
    display: none;
}

.custom-checkbox input:checked ~ .checkmark:after {
    display: block;
}

.custom-checkbox .checkmark:after {
    left: 7px;
    top: 4px;
    width: 5px;
    height: 10px;
    border: solid white;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2221670.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

钡铼技术边缘计算2DIN2DO工业无线路由器R40A

R40A不仅具备了传统工业无线路由器的基本功能&#xff0c;如4G网络连接、稳定的数据传输等&#xff0c;还创新性地整合了可编程逻辑控制器&#xff08;PLC&#xff09;功能、多种工业协议转换能力以及数据采集终端的功能。 强大的边缘计算能力 随着物联网技术的发展&#xff…

STM32_实验5_中断实验

通过外部中断来检测四个按键按下的状态&#xff1a; WK_UP 控制蜂鸣器响和停 KEY0 控制 LED_R 互斥点亮 KEY1 控制 LED_G 互斥点亮 KEY2 控制 LED_B 互斥点亮。 中断的基本概念&#xff1a; 中断请求&#xff08;IRQ&#xff09;&#xff1a; 当发生某个特定事件&#xff08;例…

如何通过谷歌外推占据搜索引擎首页?

外贸企业在推广过程中&#xff0c;如何在谷歌搜索引擎中占据有利位置&#xff0c;获取更多曝光&#xff0c;GLB谷歌霸屏服务就可以派上用场。它通过高效的品牌外推策略&#xff0c;可以让你的企业信息在谷歌中实现“霸屏”效果&#xff0c;特别是长尾关键词的全面覆盖 很多企业…

如何实现安川MP3300运动控制器与西门子1200系列PLC进行ModbusTCP通讯

在工业自动化中&#xff0c;实现不同品牌、不同型号设备之间的通讯是确保生产流程顺畅、高效运行的关键。本文详细介绍了安川MP3300运动控制器与西门子1200系列PLC进行ModbusTCP通讯的具体方法。 一&#xff0e;软硬件需求 1.一台安川MP3300CPU301&#xff0c;其IP地址是192.…

android11 usb摄像头添加多分辨率支持

部分借鉴于&#xff1a;https://blog.csdn.net/weixin_45639314/article/details/142210634 目录 一、需求介绍 二、UVC介绍 三、解析 四、补丁修改 1、预览的限制主要存在于hal层和framework层 2、添加所需要的分辨率&#xff1a; 3、hal层修改 4、frameworks 5、备…

OceanBase 首席科学家阳振坤:大模型时代的数据库思考

2024年 OceanBase 年度大会 即将于10月23日&#xff0c;在北京举行。 欢迎到现场了解更多“SQL AI ” 的探讨与分享&#xff01; 近期&#xff0c;2024年金融业数据库技术大会在北京圆满举行&#xff0c;聚焦“大模型时代下数据库的创新发展”议题&#xff0c;汇聚了国内外众多…

Java的评论大冒险:用代码征服API数据

在一个充满数字奥秘的虚拟世界里&#xff0c;Java勇士正准备踏上他的新征程&#xff1a;获取商品评论的API数据。这不仅是一次技术的挑战&#xff0c;更是一次与时间赛跑的较量。Java勇士&#xff0c;这位编程界的探险家&#xff0c;打开了他的IDE&#xff0c;准备开始这场冒险…

什么是感知与计算融合?

感知与计算融合&#xff08;Perception-Computing Fusion&#xff09;是指将感知技术&#xff08;如传感器、摄像头等&#xff09;与计算技术&#xff08;如数据处理、人工智能等&#xff09;有机结合&#xff0c;以实现对环境的更深层次理解和智能反应的过程。该技术广泛应用于…

进程间通信大总结Linux

目录 进程间通信介绍 进程间通信目的 进程间通信发展 进程间通信分类 管道 System V IPC POSIX IPC 管道 什么是管道 匿名管道 用fork来共享管道原理 站在文件描述符角度-深度理解管道 管道读写规则 管道特点 命名管道 创建一个命名管道 匿名管道与命名管道的区…

【leetcode|哈希表、动态规划】最长连续序列、最大子数组和

目录 最长连续序列 解法一&#xff1a;暴力枚举 复杂度 解法二&#xff1a;优化解法一省去二层循环中不必要的遍历 复杂度 最大子数组和 解法一&#xff1a;暴力枚举 复杂度 解法二&#xff1a;贪心 复杂度 解法三&#xff1a;动态规划 复杂度 最长连续序列 输入输…

长短期记忆网络(Long Short-Term Memory,LSTM)

简介&#xff1a;个人学习分享&#xff0c;如有错误&#xff0c;欢迎批评指正。 长短期记忆网络&#xff08;Long Short-Term Memory&#xff0c;简称LSTM&#xff09;是一种特殊的循环神经网络&#xff08;Recurrent Neural Network&#xff0c;简称RNN&#xff09;架构&#…

网络安全中的日志审计:为何至关重要?

在数字化时代&#xff0c;网络安全已成为企业和组织不可忽视的重要议题。随着网络攻击手段的不断进化&#xff0c;保护信息系统和数据安全变得日益复杂和具有挑战性。在这种背景下&#xff0c;日志审计作为一种关键的信息安全和网络管理工具&#xff0c;发挥着至关重要的作用。…

RHCE——例行性工作 at、crontab

一.单一执行的列行型工作&#xff1a;仅处理执行一次就结束了 1.at命令的工作过程 &#xff08;1&#xff09;/etc/at.allow&#xff0c;写在该文件的人可以使用at命令 &#xff08;2&#xff09;/etc/at.deny&#xff0c;黑名单 &#xff08;3&#xff09;两个文件如果都…

【Spring篇】Spring的Aop详解

&#x1f9f8;安清h&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;【计算机网络】【Mybatis篇】【Spring篇】 &#x1f6a6;作者简介&#xff1a;一个有趣爱睡觉的intp&#xff0c;期待和更多人分享自己所学知识的真诚大学生。 目录 &#x1f3af;初始Sprig AOP及…

SVM(支持向量机)

SVM&#xff08;支持向量机&#xff09; 引言 支持向量机(Support Vector Machine,SVM)&#xff0c;可以用来解答二分类问题。支持向量(Support Vector)&#xff1a;把划分数据的决策边界叫做超平面&#xff0c;点到超平面的距离叫做间隔。在SVM中&#xff0c;距离超平面最近…

京东笔试题

和谐敏感词 &#x1f517; 题目地址 &#x1f389; 模拟 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner new Scanner(System.in);int n scanner.nextInt();String s scanner.next();String[] words new String[…

Mapbox GL 加载GeoServer底图服务器的WMS source

貌似加载有点慢啊&#xff01;&#xff01; 1 这是底图 2 这是加载geoserver中的地图效果 3源码 3.1 geoserver中的网络请求 http://192.168.10.10:8080/geoserver/ne/wms?SERVICEWMS&VERSION1.1.1&REQUESTGetMap&formatimage/png&TRANSPARENTtrue&STYL…

Linux--epoll(ET)实现Reactor模式

Linux–多路转接之epoll Reactor反应堆模式 Reactor反应堆模式是一种事件驱动的设计模式&#xff0c;通常用于处理高并发的I/O操作&#xff0c;尤其是在服务器或网络编程中。 基本概念 Reactor模式又称之为响应器模式&#xff0c;基于事件多路复用机制&#xff0c;使得单个…

网络与信息安全工程师最新报考介绍(工信部教育与考试中心)

文章目录 前言 网络与信息安全工程师职业介绍主要的工作内容职业技能要求网络与信息安全工程师职业前景怎么样网络与信息安全工程师工作方向网络与信息安全工程师适学人群 如何入门学习网络安全 【----帮助网安学习&#xff0c;以下所有学习资料文末免费领取&#xff01;----】…

solidworks(sw)右侧资源栏变成英文,无法点击

sw右侧资源栏变成英文&#xff0c;无法点击&#xff0c;如图 使用xxclean 的扩展功能 SW右侧栏是英文 toolbox配置无效 这个按钮 修复完成之后重新打开软件查看是否变成中文。