react + redux 之 美团案例

news/2024/7/15 17:02:00 标签: react.js, 前端, 前端框架

1.案例展示

![](https://img-blog.csdnimg.cn/direct/b7a9604e5d274504ad630427a996aa8b.png
在这里插入图片描述

2.环境搭建

  1. 克隆项目到本地(内置了基础静态组件和模版)
git clone http://git.itcast.cn/heimaqianduan/redux-meituan.git 
  1. 安装所有依赖
npm i 
  1. 启动mock服务(内置了json-server)
npm run serve 
  1. 启动前端服务
npm run start 

3.分类和商品列表渲染

在这里插入图片描述
1.store modules 下 takeaway.js文件

// 编写store
import { createSlice } from "@reduxjs/toolkit"
import axios from "axios"

const foodsStore = createSlice({
  name: 'foods',
  initialState: {
    // 商品列表
    foodsList: [],a
  },
  reducers: {
    // 更改商品列表
    setFoodsList (state, action) {
      state.foodsList = action.payload
    }
  }
})

// 异步获取部分
const { setFoodsList} = foodsStore.actions
const fetchFoodsList = () => {
  return async (dispatch) => {
    // 编写异步逻辑
    const res = await axios.get('http://localhost:3004/takeaway')
    // 调用dispatch函数提交action
    dispatch(setFoodsList(res.data))
  }
}

export { fetchFoodsList }

const reducer = foodsStore.reducer
export default reducer

2.store下index.js文件

import foodsReducer from './modules/takeaway'
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({
  reducer: {
    foods: foodsReducer
  }
})
export default store

3.app.js

import { useDispatch, useSelector } from 'react-redux'
import { fetchFoodsList } from './store/modules/takeaway'
import { useEffect } from 'react'
// 触发action执行
  // 1. useDispatch -> dispatch 2. actionCreater导入进来 3.useEffect
  const dispatch = useDispatch()
  useEffect(() => {
    dispatch(fetchFoodsList())
  }, [dispatch])
  // 获取foodsList渲染数据列表
  // 1. useSelector
  const { foodsList } = useSelector(state => state.foods)
   {/* 外卖商品列表 */}
              {foodsList.map((item, index) => {
                return (
                 <FoodsCategory
                    key={item.tag}
                    // 列表标题
                    name={item.name}
                    // 列表商品
                    foods={item.foods}
                  />
                )
              })}

4.menu.js

import { useDispatch,useSelector } from 'react-redux'
const dispatch = useDispatch()
const {foodsList} = useSelector(state=>state.foods) 

5.index.js

// 注入store
import { Provider } from 'react-redux'
import store from './store'
const root = createRoot(document.getElementById('root'))
root.render(
  <Provider store={store}>
    <App />
  </Provider>
)

4.点击分类激活实现

在这里插入图片描述
1.store modules下 takeaway.js文件


// 编写store
import { createSlice } from "@reduxjs/toolkit"
import axios from "axios"
const foodsStore = createSlice({
  name: 'foods',
  initialState: {
    // 商品列表
    foodsList: [],
    //激活index
    activeIndex:0,
  },
  reducers: {
    // 更改商品列表
    setFoodsList (state, action) {
      state.foodsList = action.payload
    },
    //更改activeIndex
    changeActiveIndex(state,action){
        state.activeIndex = action.payload
    }
  }
})

// 异步获取部分
const { setFoodsList,changeActiveIndex} = foodsStore.actions

2.menu.js

import classNames from 'classnames'
import './index.scss'

import { useDispatch,useSelector } from 'react-redux'
import { changeActiveIndex} from '../../store/modules/takeaway'
const Menu = () => {
  const dispatch = useDispatch()
  const {foodsList,activeIndex} = useSelector(state=>state.foods) 
  const menus = foodsList.map(item => ({ tag: item.tag, name: item.name }))
  return (
    <nav className="list-menu">
      {/* 添加active类名会变成激活状态 */}
      {menus.map((item, index) => {
        return (
          <div
          onClick={() => dispatch(changeActiveIndex(index))}
            key={item.tag}
            className={classNames(
              'list-menu-item',
              activeIndex === index && 'active'
            )}
          >
            {item.name}
          </div>
        )
      })}
    </nav>
  )
}

export default Menu

3.app.js

const { foodsList , activeIndex} = useSelector(state => state.foods)
<div className="goods-list">
              {/* 外卖商品列表 */}
              {foodsList.map((item, index) => {
                return (
                  activeIndex==index && <FoodsCategory
                    key={item.tag}
                    // 列表标题
                    name={item.name}
                    // 列表商品
                    foods={item.foods}
                  />
                )
              })}
            </div>

5.添加购物车

在这里插入图片描述
1.takeaway.js

// 编写store

import { createSlice } from "@reduxjs/toolkit"
import axios from "axios"

const foodsStore = createSlice({
  name: 'foods',
  initialState: {
    // 商品列表
    foodsList: [],
    // 菜单激活下标值
    activeIndex: 0,
    // 购物车列表
    cartList: []
  },
  reducers: {
    // 更改商品列表
    setFoodsList (state, action) {
      state.foodsList = action.payload
    },
    // 更改activeIndex
    changeActiveIndex (state, action) {
      state.activeIndex = action.payload
    },
    // 添加购物车
    addCart (state, action) {
      // 是否添加过?以action.payload.id去cartList中匹配 匹配到了 添加过
      const item = state.cartList.find(item => item.id === action.payload.id)
      if (item) {
        item.count++
      } else {
        state.cartList.push(action.payload)
      }
    },
  }
})

const { setFoodsList, changeActiveIndex, addCart} = foodsStore.actions
export { fetchFoodsList, changeActiveIndex, addCart}

2.foodItem下index.js文件

import { useDispatch } from 'react-redux'
import { setCarlist } from '../../../store/modules/takeaway'
const dispatch = useDispatch()
 <div className="goods-count">
            <span className="plus" onClick={() => dispatch(setCarlist({
              id,
              picture,
              name,
              unit,
              description,
              food_tag_list,
              month_saled,
              like_ratio_desc,
              price,
              tag,
              count
            }))}></span>
          </div>

6.统计区域功能实现

在这里插入图片描述
在这里插入图片描述
1.cart下面index.js

import classNames from 'classnames'
import { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import Count from '../Count'
import './index.scss'


const Cart = () => {
  const { carList } = useSelector(state => state.foods)
  // 计算总价 
  const totalPrice = carList.reduce((a, c) => a + c.price * c.count, 0)
  return (
    <div className="cartContainer">
      <div className="cart">
        {/* fill 添加fill类名购物车高亮*/}
        {/* 购物车数量 */}
        <div  className={classNames('icon', carList.length > 0 && 'fill')}>
          {carList.length > 0 && <div className="cartCornerMark">{carList.length}</div>}
        </div>
        {/* 购物车价格 */}
        <div className="main">
          <div className="price">
            <span className="payableAmount">
              <span className="payableAmountUnit">¥</span>
              {totalPrice.toFixed(2)}
            </span>
          </div>
          <span className="text">预估另需配送费 ¥5</span>
        </div>
        {/* 结算 or 起送 */}
        {carList.length > 0 ? (
          <div className="goToPreview">去结算</div>
        ) : (
          <div className="minFee">1元起送</div>
        )}
      </div>
      {/* 添加visible类名 div会显示出来 */}
      <div className={classNames('cartPanel')}>
        <div className="header">
          <span className="text">购物车</span>
          <span className="clearCart">
            清空购物车
          </span>
        </div>

        {/* 购物车列表 */}
        <div className="scrollArea">
          {carList.map(item => {
            return (
              <div className="cartItem" key={item.id}>
                <img className="shopPic" src={item.picture} alt="" />
                <div className="main">
                  <div className="skuInfo">
                    <div className="name">{item.name}</div>
                  </div>
                  <div className="payableAmount">
                    <span className="yuan">¥</span>
                    <span className="price">{item.price}</span>
                  </div>
                </div>
                <div className="skuBtnWrapper btnGroup">
                  {/* 数量组件 */}
                  <Count
                    count={item.count}
                 
                  />
                </div>
              </div>
            )
          })}
        </div>
      </div>
    </div>
  )
}

export default Cart


7.购物车列表功能实现

在这里插入图片描述
在这里插入图片描述
1.takeaway.js

// 编写store

import { createSlice } from "@reduxjs/toolkit"
import axios from "axios"

const foodsStore = createSlice({
  name: 'foods',
  initialState: {
    // 商品列表
    foodsList: [],
    //激活index
    activeIndex:0,
    //汽车
    carList:[]
  },
  reducers: {
    // 更改商品列表
    setFoodsList (state, action) {
      state.foodsList = action.payload
    },
    //更改activeIndex
    changeActiveIndex(state,action){
        state.activeIndex = action.payload
    },
    setCarlist(state,action){
      // 是否添加过?以action.payload.id去cartList中匹配 匹配到了 添加过
      const item = state.carList.find(item => item.id === action.payload.id)
      if (item) {
        item.count++
      } else {
        state.carList.push(action.payload)
      }
    },
    increCount(state,action){
      const item = state.carList.find(item => item.id === action.payload.id)
      item.count++
    },
    decreCount(state,action){
        const item = state.carList.find(item => item.id === action.payload.id)
        if(item.count===0){
         return
        }
      item.count--
    },
     // 清除购物车
     clearCart (state) {
        state.carList = []
      }
    
}
})

// 异步获取部分
const { setFoodsList,changeActiveIndex,setCarlist,increCount,decreCount,clearCart} = foodsStore.actions
const fetchFoodsList = () => {
  return async (dispatch) => {
    // 编写异步逻辑
    const res = await axios.get('http://localhost:3004/takeaway')
    // 调用dispatch函数提交action
    dispatch(setFoodsList(res.data))
  }
}

export { fetchFoodsList ,changeActiveIndex,setCarlist,increCount,decreCount,clearCart}

const reducer = foodsStore.reducer

export default reducer

2.cart下index文件

import classNames from 'classnames'
import { useDispatch, useSelector } from 'react-redux'
import Count from '../Count'
import './index.scss'
import {increCount,decreCount,clearCart} from '../../store/modules/takeaway'


const Cart = () => {
  const { carList } = useSelector(state => state.foods)
  // 计算总价 
  const totalPrice = carList.reduce((a, c) => a + c.price * c.count, 0)

  const dispatch = useDispatch()

  return (
    <div className="cartContainer">
      <div className="cart">
        {/* fill 添加fill类名购物车高亮*/}
        {/* 购物车数量 */}
        <div  className={classNames('icon', carList.length > 0 && 'fill')}>
          {carList.length > 0 && <div className="cartCornerMark">{carList.length}</div>}
        </div>
        {/* 购物车价格 */}
        <div className="main">
          <div className="price">
            <span className="payableAmount">
              <span className="payableAmountUnit">¥</span>
              {totalPrice.toFixed(2)}
            </span>
          </div>
          <span className="text">预估另需配送费 ¥5</span>
        </div>
        {/* 结算 or 起送 */}
        {carList.length > 0 ? (
          <div className="goToPreview">去结算</div>
        ) : (
          <div className="minFee">1元起送</div>
        )}
      </div>
      {/* 添加visible类名 div会显示出来 */}
      <div className={classNames('cartPanel',carList.length>0&&'visible')} >
        <div className="header">
          <span className="text">购物车</span>
          <span className="clearCart" onClick={()=>dispatch(clearCart())}>
            清空购物车
          </span>
        </div>

        {/* 购物车列表 */}
        <div className="scrollArea">
          {carList.map(item => {
            return (
              <div className="cartItem" key={item.id}>
                <img className="shopPic" src={item.picture} alt="" />
                <div className="main">
                  <div className="skuInfo">
                    <div className="name">{item.name}</div>
                  </div>
                  <div className="payableAmount">
                    <span className="yuan">¥</span>
                    <span className="price">{item.price}</span>
                  </div>
                </div>
                <div className="skuBtnWrapper btnGroup">
                  {/* 数量组件 */}
                  <Count
                    count={item.count}
                    onPlus={()=>dispatch(increCount({id:item.id}))}
                    onMinus={()=>dispatch(decreCount({id:item.id}))}
                  />
                </div>
              </div>
            )
          })}
        </div>
      </div>
    </div>
  )
}

export default Cart

8.控制购物车显示和隐藏

在这里插入图片描述

在这里插入图片描述
1.cart文件下index.js文件

import classNames from 'classnames'
import { useDispatch, useSelector} from 'react-redux'
import { useState } from 'react'
import Count from '../Count'
import './index.scss'
import {increCount,decreCount,clearCart} from '../../store/modules/takeaway'


const Cart = () => {
  const { carList } = useSelector(state => state.foods)
  // 计算总价 
  const totalPrice = carList.reduce((a, c) => a + c.price * c.count, 0)
  const [visible,setVisible]= useState(false)
  const dispatch = useDispatch()

  const onShow = () => {
    if (carList.length > 0) {
      setVisible(true)
    }
  }
  return (
    <div className="cartContainer">
       {/* 遮罩层 添加visible类名可以显示出来 */}
       <div
        className={classNames('cartOverlay', visible && 'visible')}
        onClick={() => setVisible(false)}
      />
      <div className="cart">
        {/* fill 添加fill类名购物车高亮*/}
        {/* 购物车数量 */}
        <div onClick={onShow}  className={classNames('icon', carList.length > 0 && 'fill')}>
          {carList.length > 0 && <div className="cartCornerMark">{carList.length}</div>}
        </div>
        {/* 购物车价格 */}
        <div className="main">
          <div className="price">
            <span className="payableAmount">
              <span className="payableAmountUnit">¥</span>
              {totalPrice.toFixed(2)}
            </span>
          </div>
          <span className="text">预估另需配送费 ¥5</span>
        </div>
        {/* 结算 or 起送 */}
        {carList.length > 0 ? (
          <div className="goToPreview">去结算</div>
        ) : (
          <div className="minFee">1元起送</div>
        )}
      </div>
      {/* 添加visible类名 div会显示出来 */}
      <div className={classNames('cartPanel',visible &&'visible')} >
        <div className="header">
          <span className="text">购物车</span>
          <span className="clearCart" onClick={()=>dispatch(clearCart())}>
            清空购物车
          </span>
        </div>

        {/* 购物车列表 */}
        <div className="scrollArea">
          {carList.map(item => {
            return (
              <div className="cartItem" key={item.id}>
                <img className="shopPic" src={item.picture} alt="" />
                <div className="main">
                  <div className="skuInfo">
                    <div className="name">{item.name}</div>
                  </div>
                  <div className="payableAmount">
                    <span className="yuan">¥</span>
                    <span className="price">{item.price}</span>
                  </div>
                </div>
                <div className="skuBtnWrapper btnGroup">
                  {/* 数量组件 */}
                  <Count
                    count={item.count}
                    onPlus={()=>dispatch(increCount({id:item.id}))}
                    onMinus={()=>dispatch(decreCount({id:item.id}))}
                  />
                </div>
              </div>
            )
          })}
        </div>
      </div>
    </div>
  )
}

export default Cart


http://www.niftyadmin.cn/n/5297550.html

相关文章

[python]基于faster whisper实时语音识别语音转文本

语音识别转文本相信很多人都用过&#xff0c;不管是手机自带&#xff0c;还是腾讯视频都附带有此功能&#xff0c;今天简单说下&#xff1a; faster whisper地址&#xff1a; https://github.com/SYSTRAN/faster-whisperhttps://link.zhihu.com/?targethttps%3A//github.com…

《深入理解JAVA虚拟机笔记》OutOfMemoryError 异常

在《Java 虚拟机规范》的规定里&#xff0c;除了程序计数器外&#xff0c;虚拟机内存的其他几个运行时区域都有发生 OutOfMemoryError &#xff08;下文称 OOM&#xff09;异常的可能。 Java堆溢出 Java 堆用于储存对象实例&#xff0c;我们只要不断地创建对象&#xff0c;并…

MFC随对话框大小改变同时改变控件大小

先看一下效果; 初始; 窗口变大,控件也变大; 二个也可以; 窗口变大,控件变大; 默认生成的对话框没有WM_SIZE消息的处理程序;打开类向导,选中WM_SIZE消息,对CxxxDlg类添加该消息的处理程序;默认生成的函数名是OnSize; 添加了以后代码中会有三处变化; 在对话框类的…

redis—List列表

目录 前言 1.常见命令 2.使用场景 前言 列表类型是用来存储多个有序的字符串&#xff0c;如图2-19所示&#xff0c;a、b、C、d、e五个元素从左到右组成 了一个有序的列表&#xff0c;列表中的每个字符串称为元素(element) &#xff0c;一个列表最多可以存储2^32 - 1 个元素…

「实验记录」CS144 Lab1 StreamReassembler

目录 一、Motivation二、SolutionsS1 - StreamReassembler的对外接口S2 - push_substring序列写入ByteStream 三、Result四、My Code五、Reference 一、Motivation 我们都知道 TCP 是基于字节流的传输方式&#xff0c;即 Receiver 收到的数据应该和 Sender 发送的数据是一样的…

PythonAnywhere中运行GitHub代码步骤

假设您已经有一个GitHub仓库&#xff0c;并且您的PythonAnywhere项目目录名为 spb。 在GitHub上提交代码&#xff1a; 在本地终端中&#xff0c;确认所有的更改都已经提交&#xff1a; git add . git commit -m "Your commit message" git push origin main 登录Pyt…

Golang简单实现IO操作

在任何语言的开发中&#xff0c;输入/输出 (I/O) 操作都是非常关键的一部分。Golang 提供了 io 和 io/ioutil 标准库&#xff0c;帮助我们进行方便、高效的I/O操作。通过这篇文章&#xff0c;你将了解io库如何提供了对IO原语的基本接口&#xff0c;而io/ioutil库则提供了一些更…

Android 10.0 mtp模式下PC上显示两个内部存储的问题解决办法

1. 前言 在10.0的系统产品开发中,在mtp模式下,有些时候会在pc端显示两个手机内部存储空间,所以这时候显得特别不友好,出于对产品 的完善,所以要求解决这个问题,接下来分析下这个问题 2.mtp模式下PC上显示两个内部存储的问题解决办法的核心类 frameworks\base\media\jav…