React实现购物车功能

今日学习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);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

去哪儿旅行携手 HarmonyOS SDK | 告别繁琐,常用信息秒级填充

背景 去哪儿旅行作为行业内领先的一站式在线旅游平台&#xff0c;多年来在日益加剧的市场竞争中积极寻求创新&#xff0c;凭借其优质的服务深受消费者青睐。2024年&#xff0c;去哪儿旅行适配HarmonyOS NEXT版本&#xff0c; 升级用户服务体验。 当前&#xff0c;去哪儿旅行应…

HTML+JavaScript 贪吃蛇游戏实现与详解

在网页开发的领域中&#xff0c;利用 HTML 和 JavaScript 能够创造出各种引人入胜的互动游戏。其中&#xff0c;贪吃蛇作为一款经典之作&#xff0c;以其简单易玩的特性和紧张刺激的挑战&#xff0c;一直深受玩家的喜爱。本文将详细阐述如何运用 HTML 和 JavaScript 来打造一个…

OPPO携手比亚迪共同探索手机与汽车互融新时代

10月23日&#xff0c;OPPO与比亚迪宣布签订战略合作协议&#xff0c;双方将共同推进手机与汽车的互融合作&#xff0c;这一合作也标志着两大行业巨头在技术创新和产业融合上迈出了重要一步&#xff0c;为手机与汽车的深度融合探索新的可能。 OPPO创始人兼首席执行官陈明永、OP…

鸿蒙网络编程系列3-TCP客户端通讯示例

1. TCP简介 TCP协议是传输层最重要的协议&#xff0c;提供了可靠、有序的数据传输&#xff0c;是多个广泛使用的表示层协议的运行基础&#xff0c;相对于UDP来说&#xff0c;TCP需要经过三次握手后才能建立连接&#xff0c;建立连接后才能进行数据传输&#xff0c;所以效率差了…

【PDF文件】默认被某种软件打开,如何进行修改?

当有时下载某种软件后&#xff0c;电脑中的PDF文件就默认由该种软件打开&#xff0c;每次需要右键选择打开方式才能选择需要的其他软件打开。如下图所示。 修改方法&#xff1a; &#xff08;1&#xff09;点击电脑的“设置”&#xff0c;选择应用 &#xff08;2&#xff09;…

Java一站式校园社区外卖系统小程序源码

一站式校园社区外卖系统&#xff0c;让校园生活更便捷&#xff01; &#x1f3eb; 开篇&#xff1a;校园生活的“新宠儿” 嘿&#xff0c;小伙伴们&#xff01;今天要和大家分享一个超级实用的校园生活神器——“一站式校园社区外卖系统”&#xff01;在这个快节奏的时代&…

一个开源可私有化部署的没有任何广告的网易云

优点 ✅ 使用 Vue.js 全家桶开发&#x1f534; 网易云账号登录&#xff08;扫码/手机/邮箱登录&#xff09;&#x1f4fa; 支持 MV 播放&#x1f4c3; 支持歌词显示&#x1f4fb; 支持私人 FM / 每日推荐歌曲&#x1f6ab;&#x1f91d; 无任何社交功能&#x1f30e;️ 海外用…

歌手如何建立抖音百科?塑造个人形象!

在抖音这个充满无限可能的舞台上&#xff0c;明星们以独特的魅力吸引着亿万粉丝的目光。而抖音百科&#xff0c;作为明星们展示自我、塑造形象的又一重要窗口&#xff0c;正逐渐成为连接明星与粉丝的桥梁。 创建明星人物抖音百科&#xff0c;不仅是对明星过往成就的总结与回顾&…

WRB Hidden Gap,WRB隐藏缺口,MetaTrader 免费公式!(指标教程)

WRB Hidden Gap MetaTrader 指标用于检测和标记宽范围的柱体&#xff08;非常长的柱体&#xff09;或宽范围的烛身&#xff08;具有非常长实体的阴阳烛&#xff09;。此指标可以识别WRB中的隐藏跳空&#xff0c;并区分显示已填补和未填补的隐藏跳空&#xff0c;方便用户一眼识别…

uniapp移动端优惠券! 附源码!!!!

本文为常见的移动端uniapp优惠券&#xff0c;共有6种优惠券样式&#xff08;参考了常见的优惠券&#xff09;&#xff0c;文本内容仅为示例&#xff0c;您可在此基础上调整为你想要的文本 预览效果 通过模拟数据&#xff0c;实现点击使用优惠券让其变为灰色的效果&#xff08;模…

使用Dask在多块AMD GPU上加速XGBoost

Accelerating XGBoost with Dask using multiple AMD GPUs — ROCm Blogs 2024年1月26日 由Clint Greene撰写。 XGBoost 是一个用于分布式梯度提升的优化库。它已经成为解决回归和分类问题的领先机器学习库。如果您想深入了解梯度提升的工作原理&#xff0c;推荐阅读 Introduc…

Maven入门到实践:从安装到项目构建与IDEA集成

目录 1. Maven的概念 1.1 什么是Maven 1.2 什么是依赖管理 1.3 什么是项目构建 1.4 Maven的应用场景 1.5 为什么使用Maven 1.6 Maven模型 2.初识Maven 2.1 Maven安装 2.1.1 安装准备 2.1.2 Maven安装目录分析 2.1.3 Maven的环境变量 2.2 Maven的第一个项目 2.2.1…

深度学习Pytorch-Tensor函数

深度学习Pytorch-Tensor函数 Tensor的三角函数Tensor中其他的数学函数Tensor中统计学相关的函数&#xff08;维度&#xff0c;对于二维数据&#xff1a;dim0 按列&#xff0c;dim1 按行&#xff0c;默认 dim1&#xff09;Tensor的torch.distributions(分布函数)Tensor中的随机抽…

图论day62|拓扑排序理论基础、117.软件构建(卡码网)、最短路径之dijkstra理论基、47.参加科学大会(卡码网 第六期模拟笔试)

图论day62|拓扑排序理论基础、117.软件构建&#xff08;卡码网&#xff09;、最短路径之dijkstra理论基、47.参加科学大会&#xff08;卡码网 第六期模拟笔试&#xff09; 拓扑排序理论基础117.软件构建&#xff08;卡码网&#xff09;最短路径之dijkstra理论基础47.参加科学大…

IDEA 安装热部署 JRebel -新版-亲测有效

由于采用直接从idea 下载的插件会出现版本不适配&#xff0c;激活不成功 下载地址&#xff1a;https://note.youdao.com/web/#/file/recent/note/WEB0e3010b4015162dc6a11d6c0ab11f750/ 导入刚才下载的插件 其中&#xff0c;Team URL可以使用在线GUID地址在线生成GUID 拿到GUID…

Node.js 模块化

1. 介绍 1.1 什么是模块化与模块 ? 将一个复杂的程序文件依据一定规则&#xff08;规范&#xff09;拆分成多个文件的过程称之为 模块化其中拆分出的 每个文件就是一个模块 &#xff0c;模块的内部数据是私有的&#xff0c;不过模块可以暴露内部数据以便其他模块使用 1.2 什…

蓝桥杯注意事项

蓝桥杯注意事项 比赛注意事项 能暴力枚举就暴力枚举&#xff0c;能用简单的思路做就尽量用简单的思路做。认真审核题目的题意和输入输出的要求&#xff0c;避免因为误解题意而导致题目错误。对于提供多组测试样例或者需要对一个过程重复进行循环的代码&#xff0c;要时刻记住…

第四范式发布AI Data Foundry,加速大模型训练及应用

产品上新 Product Release 今日&#xff0c;第四范式发布AI Data Foundry&#xff0c;提供基于AI技术&#xff0c;融合人类专家反馈的高质量、丰富可扩展、多样化的数据集&#xff0c;大幅提升模型效果。同时&#xff0c;通过模型评估系统及工具&#xff0c;对模型效果进行有效…

w外链如何跳转微信小程序

要创建外链跳转微信小程序&#xff0c;主要有以下几种方法&#xff1a; 使用第三方工具生成跳转链接&#xff1a; 注册并登录第三方外链平台&#xff1a;例如 “W外链” 等工具。前往该平台的官方网站&#xff0c;使用手机号、邮箱等方式进行注册并登录账号。选择创建小程序外…

windows SVN 忘记账号密码

一、本地登录过且记录未清空 1、打开C:\Users\用户名\AppData\Roaming\Subversion\auth\svn.simple目录 2、下载SvnPwd.exe文件 链接地址&#xff1a;TortoiseSVN Password Decrypter 复制SvnPwd.exe到 C:\Users\用户名\AppData\Roaming\Subversion\auth\svn.simple目录下 3、运…