目录
1.效果展示
商品详情页
商品搜索页
购物车页
购物车详情页
结算页
2.分步演示
在实际开发前要对我们所需要做的整体页面和其中所需要的各个组件有一个整体构思,此项目一共有三个页面:商品页面,购物车页面,结算界面。其中商品数据是每个页面都需要用到的所以将商品数据存于store里便于使用,其次使用React createPortal() 创建创建模态窗口或对话框之类的场景,即遮罩层和结算页面组件。最后对于移动端项目要设置视口的宽度确保在不同设备下完整显示。
视口宽度设置:
// 除以多少视口的宽度就是多少rem,现在设置视口的总宽度为750rem
document.documentElement.style.fontSize=100/750 + 'vw';
store下的商品数据:
import React from "react";
const CartContext=React.createContext({
items:[],
totalAmount:0,
totalPrice:0,
addItem:()=>{
},
removeItem:()=>{
},
clearCart:()=>{
}
});
export default CartContext;
index.html:
<body>
<noscript>您的浏览器不支持JS</noscript>
<div id="root"></div>
<div id="backdrop-root"></div>
<div id="checkout-root"></div>
</body>
遮罩层Backdrop:
const backdropRoot=document.getElementById('backdrop-root')
const Backdrop=(props)=> {
return ReactDOM.createPortal(<div
{...props}
className={`${classes.Backdrop} ${props.className}`}>
{props.children}
</div>,backdropRoot)
}
商品详情页面
Meal.js:
页面的结构显示
const Meal=(props)=> {
return (
<div className={classes.Meal}>
<div className={classes.ImgBox}>
<img src={props.meal.img}/>
</div>
<div className={classes.BoxDesc}>
<h2 className={classes.Title}>{props.meal.title}</h2>
{props.noDesc?null:<p className={classes.Desc}>{props.meal.desc}</p>}
<div className={classes.PiceWrap}>
<span className={classes.Pice}>{props.meal.price}</span>
<Counter
meal={props.meal}
/>
</div>
</div>
</div>
)
}
Meals.js:
const Meals=(props)=> {
return (
<div className={classes.Meals}>
{props.mealsData.map(item=>
<Meal
key={item.id}
meal={item}
/>)}
</div>
)
}
Count.js:
商品计数加减
const Counter=(props)=> {
// 获取cartContext
const ctx=useContext(CartContext);
// 添加购物车的函数
const addButtonHandler=()=>{
ctx.addItem(props.meal)
}
const lessButtonHandler=()=>{
ctx.removeItem(props.meal)
}
return (
<div className={classes.Counter}>
{
(props.meal.amount && props.meal.amount !==0) ? (
<>
<button
onClick={lessButtonHandler}
className={classes.Less}> <FontAwesomeIcon icon={faMinus}/></button>
<span className={classes.Count}>{props.meal.amount}</span>
</>
) : null
}
<button
onClick={addButtonHandler}
className={classes.Add}>
<FontAwesomeIcon icon={faPlus}/>
</button>
</div>
)
};
App.js:
const App = () => {
// 创建一个state存储食物列表
const [mealsData, setMealsData] = useState(MEALS_DATA)
// 创建一个state,用来存储购物车的数据
const [cartData, setCartData] = useState({
// 商品
items: [],
// 商品总数
totalAmount: 0,
// 商品总价
totalPrice: 0
});
// 创建过滤Meals的函数
const filterHandler=(keyword)=>{
const newMealsData= MEALS_DATA.filter(item=>item.title.indexOf(keyword)!==- 1)
setMealsData(newMealsData)
}
// 想购物车中添加数据
const addItem = (meal) => {
// 对购物车进行复制
const newCart = { ...cartData };
// 判断购物车中是否有商品
if (newCart.items.indexOf(meal) === -1) {
// 将meal添加进购物车中
newCart.items.push(meal);
// 修改商品数量
meal.amount = 1
} else {
// 增加商品数量
meal.amount += 1;
}
// 增加总数
newCart.totalAmount += 1;
// 增加总金额
newCart.totalPrice += meal.price
setCartData(newCart)
}
// 减少商品数量
const removeItem = (meal) => {
const newCart = { ...cartData }
meal.amount -= 1;
// 检查商品数量是否为零
if (meal.amount === 0) {
// 从购物车中移除商品
newCart.items.splice(newCart.items.indexOf(meal), 1)
}
// 修改商品总数和总金额
newCart.totalAmount -= 1;
newCart.totalPrice -= meal.price;
setCartData(newCart)
}
// 清空购物车
const clearCart=()=>{
const newCart={...cartData}
// 将购物车中商品清零
newCart.items.forEach(item=>delete item.amount);
newCart.items=[];
newCart.totalAmount=0;
newCart.totalPrice=0;
setCartData(newCart)
}
};
搜索页
FilterMeals.js:
const FilterMeals=(props)=> {
const inputChangeHandler=e=>{
const keyword=e.target.value.trim();
props.onFilter(keyword)
}
return (
<div className={classes.FilterMeals}>
<div className={classes.SearchOuter}>
<input
onChange={inputChangeHandler}
className={classes.SearchInput}
type='text' placeholder={'请输入秘制关键字'}/>
<FontAwesomeIcon
className={classes.SearchIcon}
icon={faSearch}/>
</div>
</div>
)
}
购物车页
cart.js:
购物车显示功能
// 购物车的组件
const Cart=()=> {
const ctx=useContext(CartContext);
// 添加一个state来设置详情页是否显示
const [showDetails,setShowDetails]=useState(false);
// 添加一个state设置结账页的显示与隐藏
const[showCheckout,setShowCheckout]=useState(false)
// 在组件每次重新渲染的时候,检查一下商品的总数量。如果数量为0,则修改showDetails为false
// 组件每次重新渲染,组件的函数体就会执行
useEffect(()=>{
if(ctx.totalAmount===0){
setShowDetails(false);
setShowCheckout(false)
}
},[ctx])
// 添加一个显示详情页的函数
const toggleDetailsHandler=()=>{
if(ctx.totalAmount===0) {
setShowDetails(false);
return
};
setShowDetails(preState=>!preState)
}
const showCheckoutHandler=()=>{
if(ctx.totalAmount===0) return;
setShowCheckout(true)
}
const hideCheckoutHandler=()=>{
setShowCheckout(false)
}
}
Confirm.js:
购物车商品清除功能
const Confirm=(props)=> {
return (
<Backdrop
onClick={props.cancelHandler}
className={classes.ConfirmOuter}>
<div className={classes.Confirm}>
<p className={classes.ConfirmText}>确认放弃秘制小汉堡吗?</p>
<div>
<button
onClick={(e) => { props.onCancel(e) }}
className={classes.Cancel}>取消</button>
<button
onClick={(e) => { props.onOk(e) }}
className={classes.Ok}>确认</button>
</div>
</div>
</Backdrop>
)
}
CartDetails.js:
购物车结构显示
const CartDetails=()=> {
const ctx=useContext(CartContext)
// 设置state控制确认框的显示
const[showConfirm,setShowConfirm]=useState(false)
// 添加函数显示确认窗口
const showConfirmHandler=()=>{
setShowConfirm(true)
}
const cancelHandler=(e)=>{
e.stopPropagation();
setShowConfirm(false)
}
const OkHandler= ()=>{
// 清空购物车
ctx.clearCart();
}
return (
<Backdrop >
{showConfirm && <Confirm
onCancel={cancelHandler}
onOk={OkHandler}
/>}
<div
className={classes.CartDetails}
onClick={e=>e.stopPropagation()}
>
<header className={classes.Header}>
<h2 className={classes.Title}>餐品详情</h2>
<div
onClick={showConfirmHandler}
className={classes.Clear}>
<FontAwesomeIcon icon={faTrash}/>
<span >清空购物车</span>
</div>
</header>
<div className={classes.MealList}>
{
ctx.items.map(item=>
<Meal noDesc key={item.id} meal={item}/>
)
}
</div>
</div>
</Backdrop>
)
}
结算页面
Bar.js:
底部栏的设置
const Bar=(props)=> {
return (
<div className={classes.Bar}>
<div className={classes.TotalPrice}>{props.totalPrice}</div>
<button className={classes.Button}>去支付</button>
</div>
)
}
CheckoutItem.js:
结算页商品详情数据
const CheckoutItem=(props)=> {
return (
<div className={classes.CheckoutItem}>
<div className={classes.MealImg}>
<img src={props.meal.img}/>
</div>
<div className={classes.Desc}>
<h2 className={classes.Title}>{props.meal.title}</h2>
<div className={classes.PriceOuter}>
<Counter meal={props.meal}/>
<div className={classes.Price}>{props.meal.price*props.meal.amount}</div>
</div>
</div>
</div>
)
}
Checkout.js:
结算页面整体实现
const checkoutRoot=document.getElementById('checkout-root')
const Checkout=(props)=> {
const ctx=useContext(CartContext)
return ReactDOM.createPortal(
<div className={classes.Checkout}>
<div className={classes.Close}>
<FontAwesomeIcon
onClick={()=>props.onHide()}
icon={faXmark}/>
</div>
<div className={classes.MealsDesc}>
<header className={classes.Hearder}>
<h2 className={classes.Title}>
餐品详情
</h2>
</header>
<div className={classes.Meals}>
{ctx.items.map(item=><CheckoutItem key={item.id} meal={item}/>)}
</div>
<footer className={classes.Footer}>
<p className={classes.TotalPrice}>{ctx.totalPrice}</p>
</footer>
</div>
<Bar totalPrice={ctx.totalPrice}/>
</div>
,checkoutRoot)
}