当前位置: 首页 > article >正文

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);
}


http://www.kler.cn/news/367721.html

相关文章:

  • SwiftUI(三)- 渐变、实心形状和视图背景
  • 【版本管理】cmake 编译的 c++ 可执行文件输出 git commit 版本(即 hash 值)
  • 破解API加密逆向接口分析,看这篇就够了
  • 【知识科普】正则表达式深入解读
  • <Tauri>tauri2.0框架下,基于qwik(前端)和rust(后端)结合的桌面程序体验
  • AI智能爆发:从自动驾驶到智能家居,科技如何改变我们的日常?
  • 川渝地区软件工程考研择校分析
  • Pulsar mq 设置延迟消息模式 pulsar mq 发送延迟消息 pulsar如何发送消费延时消息
  • Django+MySQL接口开发完全指南
  • 深入解析 MySQL 数据库:数据库备份机制
  • list补充
  • ESP32C3的 USB 串行/JTAG 控制器
  • AListFlutter(手机alist)——一键安装,可在手机/电视上运行并挂载各个网盘
  • springboot-springboot官方文档架构
  • Android 判断手机放置的方向
  • 数组实例之三子棋的实现(C语言)
  • 【Git】解决分支冲突、分支合并、版本回退、版本管理
  • 其实程序和人生是一样:顺序中夹杂着循环,伴随一次次选择不断成长
  • 10.23工作感悟
  • 矩阵杂谈——矩阵的秩
  • UE5 源码学习 初始化
  • vscode python 如何不监视/不分析某个大型目录,以提高速度
  • vba学习系列(8)--指定列单元格时间按时间段计数
  • 如何提取视频文件中的音频(.mp4 to .mp3)
  • 自动发现-实现运维管理自动化
  • elementUI表达自定义校验,校验在v-for中