时里院子市集
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

350 lines
10 KiB

import {useEffect, useState} from "react";
import Taro, {useShareAppMessage, useDidShow} from '@tarojs/taro';
import {
NavBar,
Checkbox,
Image,
InputNumber,
Button,
Empty,
Divider,
ConfigProvider
} from '@nutui/nutui-react-taro';
import {ArrowLeft, Del} from '@nutui/icons-react-taro';
import {View} from '@tarojs/components';
import {CartItem, useCart} from "@/hooks/useCart";
import './cart.scss';
function Cart() {
const [statusBarHeight, setStatusBarHeight] = useState<number>(0);
const [selectedItems, setSelectedItems] = useState<number[]>([]);
const [isAllSelected, setIsAllSelected] = useState(false);
const {
cartItems,
cartCount,
updateQuantity,
removeFromCart,
clearCart,
loadCartFromStorage
} = useCart();
// InputNumber 主题配置
const customTheme = {
nutuiInputnumberButtonWidth: '28px',
nutuiInputnumberButtonHeight: '28px',
nutuiInputnumberInputWidth: '40px',
nutuiInputnumberInputHeight: '28px',
nutuiInputnumberInputBorderRadius: '4px',
nutuiInputnumberButtonBorderRadius: '4px',
}
useShareAppMessage(() => {
return {
title: '购物车 - 时里院子市集',
success: function () {
console.log('分享成功');
},
fail: function () {
console.log('分享失败');
}
};
});
// 页面显示时刷新购物车数据
useDidShow(() => {
loadCartFromStorage();
});
useEffect(() => {
Taro.getSystemInfo({
success: (res) => {
setStatusBarHeight(res.statusBarHeight || 0);
},
});
// 设置导航栏背景色
Taro.setNavigationBarColor({
backgroundColor: '#ffffff',
frontColor: 'black',
});
}, []);
// 根据购物车状态动态设置页面背景色
useEffect(() => {
if (cartItems.length === 0) {
// 购物车为空时设置透明背景
Taro.setBackgroundColor({
backgroundColor: 'transparent'
});
} else {
// 有商品时恢复默认背景
Taro.setBackgroundColor({
backgroundColor: '#f5f5f5'
});
}
}, [cartItems.length]);
// 处理单个商品选择
const handleItemSelect = (goodsId: number, checked: boolean) => {
if (checked) {
setSelectedItems([...selectedItems, goodsId]);
} else {
setSelectedItems(selectedItems.filter(id => id !== goodsId));
setIsAllSelected(false);
}
};
// 处理全选
const handleSelectAll = (checked: boolean) => {
setIsAllSelected(checked);
if (checked) {
setSelectedItems(cartItems.map((item: CartItem) => item.goodsId));
} else {
setSelectedItems([]);
}
};
// 更新商品数量
const handleQuantityChange = (goodsId: number, value: number) => {
updateQuantity(goodsId, value);
};
// 删除商品
const handleRemoveItem = (goodsId: number) => {
Taro.showModal({
title: '确认删除',
content: '确定要删除这个商品吗?',
success: (res) => {
if (res.confirm) {
removeFromCart(goodsId);
setSelectedItems(selectedItems.filter(id => id !== goodsId));
}
}
});
};
// 计算选中商品的总价
const getSelectedTotalPrice = () => {
return cartItems
.filter((item: CartItem) => selectedItems.includes(item.goodsId))
.reduce((total: number, item: CartItem) => total + (parseFloat(item.price) * item.quantity), 0)
.toFixed(2);
};
// 去结算
const handleCheckout = () => {
if (selectedItems.length === 0) {
Taro.showToast({
title: '请选择要结算的商品',
icon: 'none'
});
return;
}
// 获取选中的商品
const selectedCartItems = cartItems.filter((item: CartItem) =>
selectedItems.includes(item.goodsId)
);
// 将选中的商品信息存储到本地,供结算页面使用
Taro.setStorageSync('checkout_items', JSON.stringify(selectedCartItems));
// 跳转到购物车结算页面
Taro.navigateTo({
url: '/shop/orderConfirmCart/index'
});
};
// 检查是否全选
useEffect(() => {
if (cartItems.length > 0 && selectedItems.length === cartItems.length) {
setIsAllSelected(true);
} else {
setIsAllSelected(false);
}
}, [selectedItems, cartItems]);
if (cartItems.length === 0) {
return (
<>
<NavBar
fixed={true}
style={{marginTop: `${statusBarHeight}px`}}
left={<ArrowLeft onClick={() => Taro.navigateBack()}/>}
right={
cartItems.length > 0 && (
<Button
size="small"
type="primary"
fill="none"
onClick={() => {
Taro.showModal({
title: '确认清空',
content: '确定要清空购物车吗?',
success: (res) => {
if (res.confirm) {
clearCart();
setSelectedItems([]);
}
}
});
}}
>
</Button>
)
}
>
<span className="text-lg">({cartCount})</span>
</NavBar>
{/* 垂直居中的空状态容器 */}
<View
className="flex items-center justify-center"
style={{
height: `calc(100vh - ${statusBarHeight + 150}px)`,
paddingTop: `${statusBarHeight + 50}px`,
backgroundColor: 'transparent'
}}
>
<Empty
description="购物车空空如也"
actions={[{ text: '去逛逛' }]}
style={{
backgroundColor: 'transparent'
}}
onClick={() => Taro.switchTab({ url: '/pages/index/index' })}
/>
</View>
</>
)
}
return (
<>
<View style={{backgroundColor: '#f6f6f6', height: `${statusBarHeight}px`}}
className="fixed z-10 top-0 w-full"></View>
<NavBar
fixed={true}
style={{marginTop: `${statusBarHeight}px`}}
left={<ArrowLeft onClick={() => Taro.navigateBack()}/>}
right={
cartItems.length > 0 && (
<Button
size="small"
type="primary"
fill="none"
onClick={() => {
Taro.showModal({
title: '确认清空',
content: '确定要清空购物车吗?',
success: (res) => {
if (res.confirm) {
clearCart();
setSelectedItems([]);
}
}
});
}}
>
</Button>
)
}
>
<span className="text-lg">({cartCount})</span>
</NavBar>
{/* 购物车内容 */}
<View className="pt-24">
{/* 商品列表 */}
<View className="bg-white">
{cartItems.map((item: CartItem, index: number) => (
<View key={item.goodsId}>
<View className="bg-white px-4 py-3 flex items-center gap-3">
{/* 选择框 */}
<Checkbox
checked={selectedItems.includes(item.goodsId)}
onChange={(checked) => handleItemSelect(item.goodsId, checked)}
/>
{/* 商品图片 */}
<Image
src={item.image}
width="80"
height="80"
lazyLoad={false}
radius="8"
className="flex-shrink-0"
/>
{/* 商品信息 */}
<View className="flex-1 min-w-0">
<View className="text-lg font-bold text-gray-900 truncate mb-1">
{item.name}
</View>
<View className="flex items-center justify-between">
<View className={'flex text-red-500 text-xl items-baseline'}>
<span className={'text-xs'}></span>
<span className={'font-bold text-lg'}>{item.price}</span>
</View>
<ConfigProvider theme={customTheme}>
<InputNumber
value={item.quantity}
min={1}
onChange={(value) => handleQuantityChange(item.goodsId, Number(value))}
/>
</ConfigProvider>
<Del className={'text-red-500'} size={14} onClick={() => handleRemoveItem(item.goodsId)}/>
</View>
</View>
</View>
{index < cartItems.length - 1 && <Divider/>}
</View>
))}
</View>
{/* 底部结算栏 */}
<View
className="fixed z-50 bottom-0 left-0 right-0 bg-white border-t border-gray-200 px-4 py-3 safe-area-bottom">
<View className="flex items-center justify-between">
<View className="flex items-center gap-3">
<Checkbox
checked={isAllSelected}
onChange={handleSelectAll}
>
</Checkbox>
<View className="text-sm text-gray-600">
{selectedItems.length}
</View>
</View>
<View className="flex items-center gap-4">
<View className="text-right">
<View className="text-xs text-gray-500">:</View>
<div className={'flex text-red-500 text-xl items-baseline'}>
<span className={'text-xs'}></span>
<span className={'font-bold text-lg'}>{getSelectedTotalPrice()}</span>
</div>
</View>
<Button
type="primary"
size="large"
disabled={selectedItems.length === 0}
onClick={handleCheckout}
className="px-6"
>
({selectedItems.length})
</Button>
</View>
</View>
</View>
{/* 底部安全区域占位 */}
<View className="h-20"></View>
</View>
</>
);
}
export default Cart;