Procházet zdrojové kódy

✨ feat(goods-spec-selector): 优化商品规格选择器的数量输入功能

- 将数量显示从静态文本替换为可编辑的输入框,支持直接输入
- 新增输入框样式,确保视觉一致性和良好的交互体验
- 重构数量变更逻辑,拆分为独立的增加、减少和输入处理函数
- 增加输入验证,包括数字过滤、最小值(1)和最大值(库存或999)限制
- 添加用户友好的提示,当输入超出库存或最大购买限制时显示Toast
- 处理输入框失焦事件,确保空输入或无效值自动修正为1
- 移除商品详情页底部操作栏中重复的数量控制组件,避免功能冗余
yourname před 1 měsícem
rodič
revize
02c5a798c1

+ 19 - 0
mini/src/components/goods-spec-selector/index.css

@@ -197,6 +197,25 @@
   border-right: 1rpx solid #e8e8e8;
 }
 
+.quantity-input {
+  width: 60rpx;
+  height: 48rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 24rpx;
+  color: #333;
+  border: none;
+  background: transparent;
+  text-align: center;
+  padding: 0;
+  margin: 0;
+}
+
+.quantity-input:focus {
+  outline: none;
+}
+
 /* 弹窗底部 */
 .spec-modal-footer {
   padding: 24rpx;

+ 104 - 10
mini/src/components/goods-spec-selector/index.tsx

@@ -1,7 +1,8 @@
-import { View, Text, ScrollView } from '@tarojs/components'
+import { View, Text, ScrollView, Input } from '@tarojs/components'
 import { Button } from '@/components/ui/button'
 import { useState, useEffect } from 'react'
 import { goodsClient } from '@/api'
+import Taro from '@tarojs/taro'
 import './index.css'
 
 interface SpecOption {
@@ -149,12 +150,98 @@ export function GoodsSpecSelector({
     validatePriceCalculation()
   }, [selectedSpec, quantity])
 
-  const handleQuantityChange = (change: number) => {
+  // 获取最大可购买数量
+  const getMaxQuantity = () => {
+    if (!selectedSpec) return 999
+    return Math.min(selectedSpec.stock, 999)
+  }
+
+  // 处理减少数量
+  const handleDecrease = () => {
     if (!selectedSpec) return
+    const currentQty = quantity === 0 ? 1 : quantity
+    const newQuantity = Math.max(1, currentQty - 1)
+    setQuantity(newQuantity)
+  }
+
+  // 处理增加数量
+  const handleIncrease = () => {
+    if (!selectedSpec) return
+    const currentQty = quantity === 0 ? 1 : quantity
+    const maxQuantity = getMaxQuantity()
+    if (currentQty >= maxQuantity) {
+      if (maxQuantity === selectedSpec.stock) {
+        Taro.showToast({
+          title: `库存只有${selectedSpec.stock}件`,
+          icon: 'none',
+          duration: 1500
+        })
+      } else {
+        Taro.showToast({
+          title: '单次最多购买999件',
+          icon: 'none',
+          duration: 1500
+        })
+      }
+      return
+    }
+    setQuantity(currentQty + 1)
+  }
+
+  // 处理数量输入变化
+  const handleQuantityChange = (value: string) => {
+    if (!selectedSpec) return
+
+    // 清除非数字字符
+    const cleanedValue = value.replace(/[^\d]/g, '')
+
+    // 如果输入为空,设为空字符串(允许用户删除)
+    if (cleanedValue === '') {
+      setQuantity(0) // 设为0表示空输入
+      return
+    }
+
+    const numValue = parseInt(cleanedValue)
+
+    // 验证最小值
+    if (numValue < 1) {
+      setQuantity(1)
+      Taro.showToast({
+        title: '数量不能小于1',
+        icon: 'none',
+        duration: 1500
+      })
+      return
+    }
+
+    // 验证最大值
+    const maxQuantity = getMaxQuantity()
+    if (numValue > maxQuantity) {
+      setQuantity(maxQuantity)
+      if (maxQuantity === selectedSpec.stock) {
+        Taro.showToast({
+          title: `库存只有${selectedSpec.stock}件`,
+          icon: 'none',
+          duration: 1500
+        })
+      } else {
+        Taro.showToast({
+          title: '单次最多购买999件',
+          icon: 'none',
+          duration: 1500
+        })
+      }
+      return
+    }
+
+    setQuantity(numValue)
+  }
 
-    const newQuantity = quantity + change
-    if (newQuantity >= 1 && newQuantity <= selectedSpec.stock) {
-      setQuantity(newQuantity)
+  // 处理输入框失去焦点(完成输入)
+  const handleQuantityBlur = () => {
+    // 如果数量小于1(表示空输入或负数),设为1
+    if (quantity < 1) {
+      setQuantity(1)
     }
   }
 
@@ -252,18 +339,25 @@ export function GoodsSpecSelector({
                 size="sm"
                 variant="ghost"
                 className="quantity-btn"
-                onClick={() => handleQuantityChange(-1)}
-                disabled={quantity <= 1}
+                onClick={handleDecrease}
               >
                 -
               </Button>
-              <Text className="quantity-value">{quantity}</Text>
+              <Input
+                className="quantity-input"
+                type="number"
+                value={quantity === 0 ? '' : quantity.toString()}
+                onInput={(e) => handleQuantityChange(e.detail.value)}
+                onBlur={handleQuantityBlur}
+                placeholder="1"
+                maxlength={3}
+                confirmType="done"
+              />
               <Button
                 size="sm"
                 variant="ghost"
                 className="quantity-btn"
-                onClick={() => handleQuantityChange(1)}
-                disabled={quantity >= selectedSpec.stock}
+                onClick={handleIncrease}
               >
                 +
               </Button>

+ 2 - 2
mini/src/pages/goods-detail/index.tsx

@@ -564,7 +564,7 @@ export default function GoodsDetailPage() {
 
       {/* 底部操作栏 */}
       <View className="bottom-action-bar">
-        <View className="quantity-section">
+        {/* <View className="quantity-section">
           <Text className="quantity-label">数量</Text>
           <View className="quantity-controls">
             <Button
@@ -594,7 +594,7 @@ export default function GoodsDetailPage() {
               +
             </Button>
           </View>
-        </View>
+        </View> */}