分析说明:
- 注意:React 脚手架默认支持
sass
,但是需要自己手动安装sass
依赖包(用来解析 sass 语法)安装命令:yarn/npm add sass
步骤:
-
根据模板搭建基本页面结构
-
安装解析 sass 的包:
yarn add sass
,重启项目 -
安装 bootstrap:
yarn add bootstrap@4.5.0
,并导入 bootstrap 样式文件 -
安装 axios : npm add axios
实现功能:
全选、单选、总价计算、数量增减、总数
模拟数据data.json:
{
"goodsList": [
{
"id": 1,
"goods_name": "班俏BANQIAO超火ins潮卫衣女士2020秋季新款韩版宽松慵懒风薄款外套带帽上衣",
"goods_img": "https://www.escook.cn/vuebase/pics/1.png",
"goods_price": 108,
"goods_count": 1,
"goods_state": true
},
{
"id": 2,
"goods_name": "嘉叶希连帽卫衣女春秋薄款2020新款宽松bf韩版字母印花中长款外套ins潮",
"goods_img": "https://www.escook.cn/vuebase/pics/2.png",
"goods_price": 129,
"goods_count": 1,
"goods_state": true
},
{
"id": 3,
"goods_name": "思蜜怡2020休闲运动套装女春秋季新款时尚大码宽松长袖卫衣两件套",
"goods_img": "https://www.escook.cn/vuebase/pics/3.png",
"goods_price": 198,
"goods_count": 1,
"goods_state": false
},
{
"id": 4,
"goods_name": "思蜜怡卫衣女加绒加厚2020秋冬装新款韩版宽松上衣连帽中长款外套",
"goods_img": "https://www.escook.cn/vuebase/pics/4.png",
"goods_price": 99,
"goods_count": 1,
"goods_state": false
},
{
"id": 5,
"goods_name": "幂凝早秋季卫衣女春秋装韩版宽松中长款假两件上衣薄款ins盐系外套潮",
"goods_img": "https://www.escook.cn/vuebase/pics/5.png",
"goods_price": 156,
"goods_count": 1,
"goods_state": true
},
{
"id": 6,
"goods_name": "ME&CITY女装冬季新款针织抽绳休闲连帽卫衣女",
"goods_img": "https://www.escook.cn/vuebase/pics/6.png",
"goods_price": 142.8,
"goods_count": 1,
"goods_state": true
},
{
"id": 7,
"goods_name": "幂凝假两件女士卫衣秋冬女装2020年新款韩版宽松春秋季薄款ins潮外套",
"goods_img": "https://www.escook.cn/vuebase/pics/7.png",
"goods_price": 219,
"goods_count": 2,
"goods_state": true
},
{
"id": 8,
"goods_name": "依魅人2020休闲运动衣套装女秋季新款秋季韩版宽松卫衣 时尚两件套",
"goods_img": "https://www.escook.cn/vuebase/pics/8.png",
"goods_price": 178,
"goods_count": 1,
"goods_state": true
},
{
"id": 9,
"goods_name": "芷臻(zhizhen)加厚卫衣2020春秋季女长袖韩版宽松短款加绒春秋装连帽开衫外套冬",
"goods_img": "https://www.escook.cn/vuebase/pics/9.png",
"goods_price": 128,
"goods_count": 1,
"goods_state": false
},
{
"id": 10,
"goods_name": "Semir森马卫衣女冬装2019新款可爱甜美大撞色小清新连帽薄绒女士套头衫",
"goods_img": "https://www.escook.cn/vuebase/pics/10.png",
"goods_price": 153,
"goods_count": 1,
"goods_state": false
}
]
}
*注意文中接口地址是通过json-server本地模拟的(在安装了npm情况下):
npm i json-server -g
npx json-server data.json --port 8888 //在data.json目录下打开终端
一、index.js 中引入bootstrap:
import 'bootstrap/dist/css/bootstrap.css';
二、在src目录下创建文件夹Component,在component目录下分别建立三个文件夹(Carheader、CarFooter、GoodsItem)并在这三个目录下分别建立index.jsx,index.scss
三、在src目录下找到App.css文件加入一下代码(完成布局)
.app {
padding-top: 45px;
padding-bottom: 50px;
}
四、ctrl C/ctrl V
App.js
import logo from './logo.svg';
import './App.css';
import axios from 'axios';
import { CarHeader } from './components/Carwu/CarHeader'; //这里是自己下对应的文件夹下index.jsx文件
import { CarFooter } from './components/Carwu/CarFooter';
import { GoodsItem } from './components/Carwu/GoodsItem';
import { useState,useEffect } from 'react';
function App() {
const [list,setlist]=useState([])
const [checkAll,setcheckAll]=useState(false)
const getlist=async ()=>{
const {data}=await axios.get('http://localhost:8888/goodsList')
// console.log(data);
setlist(data)
setcheckAll(!data.some((item)=>!item.goods_state))
}
//全选
const updtasetcheckAll=async (state)=>{
setcheckAll(state)
//本地修改
setlist(
list.map(item=>
{
return {
...item,
goods_state:state
}
})
)
//接口修改
await Promise.all(list.map(item=>{
axios.patch(`http://localhost:8888/goodsList/${item.id}`,{goods_state:state})
}))
}
const getState=async (id,goods_state)=>{
// console.log(id,state);
list.find(item=>item.id==id).goods_state=goods_state
setlist([...list])
setcheckAll(!list.some((item)=>!item.goods_state))
await axios.patch(`http://localhost:8888/goodsList/${id}`,{goods_state})
}
useEffect(()=>{
getlist()
},[])
//数量增加
const addNum=(id,count)=>{
//处理本地
list.find(item=>item.id==id).goods_count=count+1
setlist([...list])
// 接口同步
const newcount=list.find(item=>item.id==id).goods_count
axios.patch(`http://localhost:8888/goodsList/${id}`,{goods_count:newcount})
}
//数量减少
const reduceNum=async (id,count)=>{
// console.log(id,count);
if(count>1){
//处理本地
list.find(item=>item.id==id).goods_count=count-1
setlist([...list])
// 接口同步
const newcount=list.find(item=>item.id==id).goods_count
await axios.patch(`http://localhost:8888/goodsList/${id}`,{goods_count:newcount})
}
else if(count<=1){
if(window.confirm("是否想要删除")){
await axios.delete(`http://localhost:8888/goodsList/${id}`)
getlist()
}
}
}
//计算总数量
const totalCount=list.reduce((count,item)=>{
if(item.goods_state){
return count+ item.goods_count
}
return count
},0)
//计算总价格
const totalPrice=list.reduce((price,item)=>{
if(item.goods_state){
return price+ item.goods_count * item.goods_price
}
return price
},0)
return (
<div className="app">
<CarHeader>购物车</CarHeader>
{
list.map(item=><GoodsItem key={item.id} {...item} getState={getState} addNum={addNum} reduceNum={reduceNum}></GoodsItem>)
}
<CarFooter checkAll={checkAll} setcheckAll={updtasetcheckAll} totalCount={totalCount} totalPrice={totalPrice}></CarFooter>
</div>
);
}
export default App;
CarHeader下的index.jsx文件:
import './index.css'
export const CarHeader=(props)=>{
return (
<div className="cart-header">{props.children}</div>
)
}
CarHeader下的index.scss文件:
.cart-header {
z-index: 999;
height: 45px;
line-height: 45px;
text-align: center;
background-color: #1d7bff;
color: #fff;
position: fixed;
top: 0;
left: 0;
width: 100%;
}
GoodsItem下的index.jsx文件:
import './index.scss'
export const GoodsItem =({id,goods_name,goods_img,goods_price,goods_count,goods_state,getState,addNum,reduceNum})=>{
return (
<div className="cart-goods-item">
<div className="left">
<div className="custom-control custom-checkbox">
<input type="checkbox" checked={goods_state} onChange={()=>{}} className="custom-control-input" onChange={(e)=>getState(id,e.target.checked)} id={`"input${id}"`} />
<label className="custom-control-label" htmlFor={`"input${id}"`}>
<img
src={goods_img}
alt=""
/>
</label>
</div>
</div>
<div className="right">
<div className="top">{goods_name}</div>
<div className="bottom">
<span className="price">¥ {goods_price*goods_count}</span>
<span>
<button onClick={()=>addNum(id,goods_count)}>+</button>
<button>{goods_count}</button>
<button onClick={()=>reduceNum(id,goods_count)}>-</button>
</span>
</div>
</div>
</div>
)
}
GoodsItem下的index.scss文件:
.cart-goods-item {
display: flex;
padding: 10px;
border-bottom: 1px solid #ccc;
.left {
img {
width: 120px;
height: 120px;
margin-right: 8px;
border-radius: 10px;
}
.custom-control-label::before,
.custom-control-label::after {
top: 50px;
}
}
.right {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.bottom {
display: flex;
justify-content: space-between;
padding: 5px 0;
.price {
color: red;
font-weight: bold;
}
}
}
}
CarFooter下的index.jsx文件:
import './index.scss'
export const CarFooter=({setcheckAll,checkAll,totalPrice,totalCount})=>{
return (
<div className="cart-footer">
<div className="custom-control custom-checkbox">
<input
type="checkbox"
className="custom-control-input"
id="footerCheck"
checked={checkAll}
onChange={(e)=>setcheckAll(e.target.checked)}
/>
<label className="custom-control-label" htmlFor="footerCheck">
全选
</label>
</div>
<div>
<span>合计:</span>
<span className="price">¥ {totalPrice}</span>
</div>
<button type="button" className="footer-btn btn btn-primary">
结算 ({totalCount})
</button>
</div>
)
}
CarFooter下的indexscss文件:
.cart-footer {
z-index: 999;
position: fixed;
bottom: 0;
width: 100%;
height: 50px;
border-top: 1px solid #ccc;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10px;
background: #fff;
.price {
color: red;
font-weight: bold;
font-size: 15px;
}
.footer-btn {
min-width: 80px;
height: 30px;
line-height: 24px;
border-radius: 25px;
padding: 0;
}
}