|
|
@@ -1,4 +1,4 @@
|
|
|
-import { View, ScrollView, Text } from '@tarojs/components'
|
|
|
+import { View, ScrollView, Text, Input } from '@tarojs/components'
|
|
|
import { useState, useEffect } from 'react'
|
|
|
import { useQueries } from '@tanstack/react-query'
|
|
|
import Taro from '@tarojs/taro'
|
|
|
@@ -15,6 +15,8 @@ export default function CartPage() {
|
|
|
const { cart, updateQuantity, removeFromCart, clearCart, isLoading } = useCart()
|
|
|
const [selectedItems, setSelectedItems] = useState<number[]>([])
|
|
|
const [showSkeleton, setShowSkeleton] = useState(true)
|
|
|
+ // 为每个商品维护本地输入值,用于显示空字符串
|
|
|
+ const [inputValues, setInputValues] = useState<{[key: number]: string}>({})
|
|
|
|
|
|
// 为每个购物车商品创建查询,从数据库重新获取最新信息
|
|
|
const goodsQueries = useQueries({
|
|
|
@@ -103,6 +105,7 @@ export default function CartPage() {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+
|
|
|
// 骨架屏组件
|
|
|
const CartSkeleton = () => (
|
|
|
<View className="cart-skeleton">
|
|
|
@@ -259,12 +262,246 @@ export default function CartPage() {
|
|
|
</View>
|
|
|
|
|
|
{/* 数量选择器 */}
|
|
|
- <View className="goods-stepper">
|
|
|
+ <View
|
|
|
+ className="cart-quantity-controls"
|
|
|
+ onClick={(e) => {
|
|
|
+ e.stopPropagation(); // 阻止事件冒泡到商品卡片
|
|
|
+ }}
|
|
|
+ onTouchStart={(e) => {
|
|
|
+ e.stopPropagation(); // 阻止触摸事件冒泡
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Button
|
|
|
+ size="sm"
|
|
|
+ variant="ghost"
|
|
|
+ className="cart-quantity-btn"
|
|
|
+ onClick={(e) => {
|
|
|
+ e.stopPropagation(); // 阻止事件冒泡
|
|
|
+ // 清除本地输入值
|
|
|
+ setInputValues(prev => {
|
|
|
+ const newValues = { ...prev };
|
|
|
+ delete newValues[item.id];
|
|
|
+ return newValues;
|
|
|
+ });
|
|
|
+ updateQuantity(item.id, Math.max(1, item.quantity - 1));
|
|
|
+ }}
|
|
|
+ onTouchStart={(e) => {
|
|
|
+ e.stopPropagation(); // 阻止触摸开始事件冒泡
|
|
|
+ }}
|
|
|
+ onTouchEnd={(e) => {
|
|
|
+ e.stopPropagation(); // 阻止触摸结束事件冒泡
|
|
|
+ }}
|
|
|
+ disabled={item.quantity <= 1}
|
|
|
+ >
|
|
|
+ -
|
|
|
+ </Button>
|
|
|
+ <Input
|
|
|
+ className="cart-quantity-input"
|
|
|
+ type="number"
|
|
|
+ value={inputValues[item.id] !== undefined ? inputValues[item.id] : item.quantity.toString()}
|
|
|
+ onClick={(e) => {
|
|
|
+ e.stopPropagation(); // 阻止点击事件冒泡
|
|
|
+ }}
|
|
|
+ onTouchStart={(e) => {
|
|
|
+ e.stopPropagation(); // 阻止触摸开始事件冒泡
|
|
|
+ }}
|
|
|
+ onTouchEnd={(e) => {
|
|
|
+ e.stopPropagation(); // 阻止触摸结束事件冒泡
|
|
|
+ }}
|
|
|
+ onInput={(e) => {
|
|
|
+ e.stopPropagation(); // 阻止事件冒泡
|
|
|
+ const value = e.detail.value;
|
|
|
+ // 清除非数字字符
|
|
|
+ const cleanedValue = value.replace(/[^\d]/g, '');
|
|
|
+
|
|
|
+ // 更新本地输入值
|
|
|
+ setInputValues(prev => ({
|
|
|
+ ...prev,
|
|
|
+ [item.id]: value // 使用原始值,允许显示空字符串
|
|
|
+ }));
|
|
|
+
|
|
|
+ // 如果输入为空,只更新本地状态,不更新购物车
|
|
|
+ if (cleanedValue === '') {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const numValue = parseInt(cleanedValue);
|
|
|
+
|
|
|
+ // 验证最小值
|
|
|
+ if (numValue < 1) {
|
|
|
+ updateQuantity(item.id, 1);
|
|
|
+ Taro.showToast({
|
|
|
+ title: '数量不能小于1',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 1500
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证最大值(考虑库存和单次购买限制)
|
|
|
+ const maxQuantity = Math.min(goodsStock, 999);
|
|
|
+ if (numValue > maxQuantity) {
|
|
|
+ updateQuantity(item.id, maxQuantity);
|
|
|
+ if (maxQuantity === goodsStock) {
|
|
|
+ Taro.showToast({
|
|
|
+ title: `库存只有${goodsStock}件`,
|
|
|
+ icon: 'none',
|
|
|
+ duration: 1500
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ Taro.showToast({
|
|
|
+ title: '单次最多购买999件',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 1500
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ updateQuantity(item.id, numValue);
|
|
|
+ }}
|
|
|
+ onBlur={(e) => {
|
|
|
+ e.stopPropagation(); // 阻止事件冒泡
|
|
|
+ const value = e.detail.value;
|
|
|
+ const cleanedValue = value.replace(/[^\d]/g, '');
|
|
|
+
|
|
|
+ // 清除本地输入值,恢复显示购物车数量
|
|
|
+ setInputValues(prev => {
|
|
|
+ const newValues = { ...prev };
|
|
|
+ delete newValues[item.id];
|
|
|
+ return newValues;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 如果输入为空,不更新数量
|
|
|
+ if (cleanedValue === '') {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const numValue = parseInt(cleanedValue);
|
|
|
+
|
|
|
+ // 如果输入无效(小于1),设为1
|
|
|
+ if (numValue < 1) {
|
|
|
+ updateQuantity(item.id, 1);
|
|
|
+ Taro.showToast({
|
|
|
+ title: '数量不能小于1',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 1500
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ onFocus={(e) => {
|
|
|
+ e.stopPropagation(); // 阻止焦点事件冒泡
|
|
|
+ }}
|
|
|
+ onConfirm={(e) => {
|
|
|
+ e.stopPropagation(); // 阻止事件冒泡
|
|
|
+ const value = e.detail.value;
|
|
|
+ const cleanedValue = value.replace(/[^\d]/g, '');
|
|
|
+
|
|
|
+ // 清除本地输入值
|
|
|
+ setInputValues(prev => {
|
|
|
+ const newValues = { ...prev };
|
|
|
+ delete newValues[item.id];
|
|
|
+ return newValues;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 如果输入为空,设为1
|
|
|
+ if (cleanedValue === '') {
|
|
|
+ updateQuantity(item.id, 1);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const numValue = parseInt(cleanedValue);
|
|
|
+
|
|
|
+ // 验证最小值
|
|
|
+ if (numValue < 1) {
|
|
|
+ updateQuantity(item.id, 1);
|
|
|
+ Taro.showToast({
|
|
|
+ title: '数量不能小于1',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 1500
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证最大值(考虑库存和单次购买限制)
|
|
|
+ const maxQuantity = Math.min(goodsStock, 999);
|
|
|
+ if (numValue > maxQuantity) {
|
|
|
+ updateQuantity(item.id, maxQuantity);
|
|
|
+ if (maxQuantity === goodsStock) {
|
|
|
+ Taro.showToast({
|
|
|
+ title: `库存只有${goodsStock}件`,
|
|
|
+ icon: 'none',
|
|
|
+ duration: 1500
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ Taro.showToast({
|
|
|
+ title: '单次最多购买999件',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 1500
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ updateQuantity(item.id, numValue);
|
|
|
+ }}
|
|
|
+ placeholder="1"
|
|
|
+ maxlength={3}
|
|
|
+ confirmType="done"
|
|
|
+ />
|
|
|
+ <Button
|
|
|
+ size="sm"
|
|
|
+ variant="ghost"
|
|
|
+ className="cart-quantity-btn"
|
|
|
+ onClick={(e) => {
|
|
|
+ e.stopPropagation(); // 阻止事件冒泡
|
|
|
+ // 清除本地输入值
|
|
|
+ setInputValues(prev => {
|
|
|
+ const newValues = { ...prev };
|
|
|
+ delete newValues[item.id];
|
|
|
+ return newValues;
|
|
|
+ });
|
|
|
+ // 验证最大值(考虑库存和单次购买限制)
|
|
|
+ const maxQuantity = Math.min(goodsStock, 999);
|
|
|
+ if (item.quantity >= maxQuantity) {
|
|
|
+ if (maxQuantity === goodsStock) {
|
|
|
+ Taro.showToast({
|
|
|
+ title: `库存只有${goodsStock}件`,
|
|
|
+ icon: 'none',
|
|
|
+ duration: 1500
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ Taro.showToast({
|
|
|
+ title: '单次最多购买999件',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 1500
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ updateQuantity(item.id, item.quantity + 1);
|
|
|
+ }}
|
|
|
+ onTouchStart={(e) => {
|
|
|
+ e.stopPropagation(); // 阻止触摸开始事件冒泡
|
|
|
+ }}
|
|
|
+ onTouchEnd={(e) => {
|
|
|
+ e.stopPropagation(); // 阻止触摸结束事件冒泡
|
|
|
+ }}
|
|
|
+ disabled={item.quantity >= Math.min(goodsStock, 999)}
|
|
|
+ >
|
|
|
+ +
|
|
|
+ </Button>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* <View className="goods-stepper">
|
|
|
<Button
|
|
|
size="sm"
|
|
|
variant="ghost"
|
|
|
className="stepper-btn minus"
|
|
|
- onClick={() => updateQuantity(item.id, Math.max(1, item.quantity - 1))}
|
|
|
+ onClick={(e) => {
|
|
|
+ e.stopPropagation(); // 阻止事件冒泡
|
|
|
+ updateQuantity(item.id, Math.max(1, item.quantity - 1));
|
|
|
+ }}
|
|
|
disabled={item.quantity <= 1}
|
|
|
>
|
|
|
<View className="i-heroicons-minus-20-solid w-3 h-3" />
|
|
|
@@ -274,11 +511,15 @@ export default function CartPage() {
|
|
|
size="sm"
|
|
|
variant="ghost"
|
|
|
className="stepper-btn plus"
|
|
|
- onClick={() => updateQuantity(item.id, item.quantity + 1)}
|
|
|
+ onClick={(e) => {
|
|
|
+ e.stopPropagation(); // 阻止事件冒泡
|
|
|
+ updateQuantity(item.id, item.quantity + 1);
|
|
|
+ }}
|
|
|
>
|
|
|
<View className="i-heroicons-plus-20-solid w-3 h-3" />
|
|
|
</Button>
|
|
|
- </View>
|
|
|
+ </View> */}
|
|
|
+
|
|
|
</View>
|
|
|
</View>
|
|
|
</View>
|