|
|
@@ -17,8 +17,15 @@ type Goods = GoodsResponse['data'][0]
|
|
|
|
|
|
export default function GoodsListPage() {
|
|
|
const [searchKeyword, setSearchKeyword] = useState('')
|
|
|
+ const [activeCategory, setActiveCategory] = useState('all')
|
|
|
const { addToCart } = useCart()
|
|
|
|
|
|
+ const categories = [
|
|
|
+ { id: 'all', name: '全部' },
|
|
|
+ { id: 'hot', name: '热销' },
|
|
|
+ { id: 'new', name: '新品' },
|
|
|
+ ]
|
|
|
+
|
|
|
const {
|
|
|
data,
|
|
|
isLoading,
|
|
|
@@ -93,129 +100,193 @@ export default function GoodsListPage() {
|
|
|
|
|
|
return (
|
|
|
<TabBarLayout activeKey="goods-list">
|
|
|
- <View className="min-h-screen bg-gray-50">
|
|
|
- <Navbar
|
|
|
- title="商品列表"
|
|
|
- leftIcon="i-heroicons-chevron-left-20-solid"
|
|
|
- onClickLeft={() => Taro.navigateBack()}
|
|
|
- />
|
|
|
-
|
|
|
- <ScrollView
|
|
|
- className="h-screen pt-12"
|
|
|
- scrollY
|
|
|
- onScrollToLower={handleScrollToLower}
|
|
|
- refresherEnabled
|
|
|
- refresherTriggered={false}
|
|
|
- onRefresherRefresh={onPullDownRefresh}
|
|
|
- >
|
|
|
- <View className="px-4 py-4">
|
|
|
- {/* 搜索栏 */}
|
|
|
- <View className="bg-white rounded-lg p-3 mb-4 shadow-sm">
|
|
|
- <Input
|
|
|
- type="text"
|
|
|
- placeholder="搜索商品"
|
|
|
- className="w-full outline-none"
|
|
|
- value={searchKeyword}
|
|
|
- onChange={(value) => setSearchKeyword(value)}
|
|
|
- onConfirm={() => refetch()}
|
|
|
- />
|
|
|
+ <Navbar
|
|
|
+ title="商品列表"
|
|
|
+ leftIcon="i-heroicons-chevron-left-20-solid"
|
|
|
+ onClickLeft={() => Taro.navigateBack()}
|
|
|
+ className="bg-white shadow-sm"
|
|
|
+ />
|
|
|
+
|
|
|
+ <ScrollView
|
|
|
+ className="flex-1"
|
|
|
+ scrollY
|
|
|
+ onScrollToLower={handleScrollToLower}
|
|
|
+ refresherEnabled
|
|
|
+ refresherTriggered={false}
|
|
|
+ onRefresherRefresh={onPullDownRefresh}
|
|
|
+ >
|
|
|
+ <View className="px-4 py-4">
|
|
|
+ {/* 搜索栏 */}
|
|
|
+ <View className="bg-white rounded-2xl p-4 mb-4 shadow-lg">
|
|
|
+ <View className="flex items-center space-x-3">
|
|
|
+ <View className="flex-1 relative">
|
|
|
+ <View className="absolute left-4 top-1/2 -translate-y-1/2">
|
|
|
+ <View className="i-heroicons-magnifying-glass-20-solid w-5 h-5 text-gray-400" />
|
|
|
+ </View>
|
|
|
+ <Input
|
|
|
+ type="text"
|
|
|
+ placeholder="搜索你想要的商品..."
|
|
|
+ className="w-full pl-12 pr-4 py-3 bg-gray-50 rounded-xl outline-none focus:bg-gray-100 transition-colors text-sm"
|
|
|
+ value={searchKeyword}
|
|
|
+ onChange={(value) => setSearchKeyword(value)}
|
|
|
+ onConfirm={() => refetch()}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+ {searchKeyword && (
|
|
|
+ <Button
|
|
|
+ size="mini"
|
|
|
+ variant="ghost"
|
|
|
+ className="!w-8 !h-8 !p-0"
|
|
|
+ onClick={() => {
|
|
|
+ setSearchKeyword('')
|
|
|
+ refetch()
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <View className="i-heroicons-x-mark-20-solid w-4 h-4 text-gray-500" />
|
|
|
+ </Button>
|
|
|
+ )}
|
|
|
</View>
|
|
|
+ </View>
|
|
|
|
|
|
- {/* 商品列表 */}
|
|
|
- {isLoading ? (
|
|
|
- <View className="flex justify-center py-10">
|
|
|
- <View className="i-heroicons-arrow-path-20-solid animate-spin w-8 h-8 text-blue-500" />
|
|
|
- </View>
|
|
|
- ) : (
|
|
|
- <>
|
|
|
+ {/* 分类筛选 */}
|
|
|
+ <View className="flex space-x-2 mb-4 overflow-x-auto">
|
|
|
+ {categories.map((category) => (
|
|
|
+ <Button
|
|
|
+ key={category.id}
|
|
|
+ size="sm"
|
|
|
+ variant={activeCategory === category.id ? "solid" : "ghost"}
|
|
|
+ className={`whitespace-nowrap rounded-full px-4 py-2 transition-all ${
|
|
|
+ activeCategory === category.id
|
|
|
+ ? 'bg-blue-500 text-white shadow-md'
|
|
|
+ : 'text-gray-600 hover:bg-gray-100'
|
|
|
+ }`}
|
|
|
+ onClick={() => {
|
|
|
+ setActiveCategory(category.id)
|
|
|
+ refetch()
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {category.name}
|
|
|
+ </Button>
|
|
|
+ ))}
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 商品列表 */}
|
|
|
+ {isLoading ? (
|
|
|
+ <View className="flex justify-center py-10">
|
|
|
+ <View className="i-heroicons-arrow-path-20-solid animate-spin w-8 h-8 text-blue-500" />
|
|
|
+ </View>
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ <View className="grid grid-cols-2 gap-4">
|
|
|
{allGoods.map((goods) => (
|
|
|
- <Card key={goods.id} className="mb-4 overflow-hidden">
|
|
|
- <View className="flex p-4">
|
|
|
- <View
|
|
|
- className="w-24 h-24 bg-gray-100 rounded-lg mr-4"
|
|
|
- onClick={() => handleGoodsClick(goods)}
|
|
|
- >
|
|
|
+ <Card
|
|
|
+ key={goods.id}
|
|
|
+ className="overflow-hidden bg-white rounded-2xl shadow-sm active:shadow-lg transition-all duration-300 active:scale-95"
|
|
|
+ onClick={() => handleGoodsClick(goods)}
|
|
|
+ >
|
|
|
+ <View className="relative">
|
|
|
+ <View className="w-full aspect-square bg-gradient-to-br from-gray-100 to-gray-200 overflow-hidden">
|
|
|
{goods.imageFile?.fullUrl ? (
|
|
|
<Image
|
|
|
src={goods.imageFile.fullUrl}
|
|
|
- className="w-full h-full object-cover rounded-lg"
|
|
|
+ className="w-full h-full object-cover transition-transform duration-300 active:scale-110"
|
|
|
mode="aspectFill"
|
|
|
/>
|
|
|
) : (
|
|
|
<View className="w-full h-full flex items-center justify-center text-gray-400">
|
|
|
- <View className="i-heroicons-photo-20-solid w-8 h-8" />
|
|
|
+ <View className="i-heroicons-photo-20-solid w-12 h-12" />
|
|
|
</View>
|
|
|
)}
|
|
|
</View>
|
|
|
|
|
|
- <View className="flex-1">
|
|
|
- <Text
|
|
|
- className="text-lg font-medium text-gray-900 mb-2 line-clamp-2"
|
|
|
- onClick={() => handleGoodsClick(goods)}
|
|
|
- >
|
|
|
- {goods.name}
|
|
|
- </Text>
|
|
|
-
|
|
|
- <Text className="text-sm text-gray-500 mb-2">
|
|
|
- {goods.instructions || '暂无描述'}
|
|
|
- </Text>
|
|
|
-
|
|
|
- <View className="flex items-center justify-between">
|
|
|
- <View>
|
|
|
- <Text className="text-red-500 text-lg font-bold">
|
|
|
- ¥{goods.price.toFixed(2)}
|
|
|
- </Text>
|
|
|
- <Text className="text-xs text-gray-400 ml-2">
|
|
|
- 已售{goods.salesNum}
|
|
|
- </Text>
|
|
|
- </View>
|
|
|
-
|
|
|
- <View className="flex items-center space-x-2">
|
|
|
- <Button
|
|
|
- size="sm"
|
|
|
- variant="outline"
|
|
|
- onClick={() => handleGoodsClick(goods)}
|
|
|
- >
|
|
|
- 详情
|
|
|
- </Button>
|
|
|
- <Button
|
|
|
- size="sm"
|
|
|
- onClick={() => handleAddToCart(goods)}
|
|
|
- disabled={goods.stock <= 0}
|
|
|
- >
|
|
|
- {goods.stock > 0 ? '加入购物车' : '已售罄'}
|
|
|
- </Button>
|
|
|
- </View>
|
|
|
+ {goods.stock <= 0 && (
|
|
|
+ <View className="absolute inset-0 bg-black/50 flex items-center justify-center">
|
|
|
+ <Text className="text-white font-bold text-sm">已售罄</Text>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {goods.stock > 0 && goods.salesNum > 100 && (
|
|
|
+ <View className="absolute top-2 left-2 bg-gradient-to-r from-orange-500 to-red-500 text-white text-xs px-2 py-1 rounded-full shadow-lg">
|
|
|
+ 热销
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {goods.stock > 0 && goods.salesNum <= 100 && (
|
|
|
+ <View className="absolute top-2 right-2 bg-white/90 backdrop-blur text-gray-700 text-xs px-2 py-1 rounded-full shadow-md">
|
|
|
+ 库存{goods.stock}
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <View className="p-3">
|
|
|
+ <Text className="text-sm font-semibold text-gray-900 mb-1 line-clamp-2 h-10 leading-tight">
|
|
|
+ {goods.name}
|
|
|
+ </Text>
|
|
|
+
|
|
|
+ <Text className="text-xs text-gray-500 mb-2 line-clamp-1">
|
|
|
+ {goods.instructions || '优质商品,值得信赖'}
|
|
|
+ </Text>
|
|
|
+
|
|
|
+ <View className="flex items-center justify-between">
|
|
|
+ <View>
|
|
|
+ <Text className="text-red-500 text-lg font-bold">
|
|
|
+ ¥{goods.price.toFixed(2)}
|
|
|
+ </Text>
|
|
|
+ <Text className="text-xs text-gray-400 ml-1">
|
|
|
+ 已售{goods.salesNum}
|
|
|
+ </Text>
|
|
|
</View>
|
|
|
+
|
|
|
+ <Button
|
|
|
+ size="mini"
|
|
|
+ className={`!w-8 !h-8 !p-0 rounded-full transition-all ${
|
|
|
+ goods.stock > 0
|
|
|
+ ? 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-md active:shadow-lg'
|
|
|
+ : 'bg-gray-200 text-gray-400'
|
|
|
+ }`}
|
|
|
+ onClick={(e) => {
|
|
|
+ e.stopPropagation()
|
|
|
+ if (goods.stock > 0) {
|
|
|
+ handleAddToCart(goods)
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ disabled={goods.stock <= 0}
|
|
|
+ >
|
|
|
+ <View className={`w-4 h-4 ${goods.stock > 0 ? 'i-heroicons-plus-20-solid' : 'i-heroicons-minus-20-solid'}`} />
|
|
|
+ </Button>
|
|
|
</View>
|
|
|
</View>
|
|
|
</Card>
|
|
|
))}
|
|
|
-
|
|
|
- {isFetchingNextPage && (
|
|
|
- <View className="flex justify-center py-4">
|
|
|
- <View className="i-heroicons-arrow-path-20-solid animate-spin w-6 h-6 text-blue-500" />
|
|
|
- <Text className="ml-2 text-sm text-gray-500">加载更多...</Text>
|
|
|
- </View>
|
|
|
- )}
|
|
|
-
|
|
|
- {!hasNextPage && allGoods.length > 0 && (
|
|
|
- <View className="text-center py-4 text-sm text-gray-400">
|
|
|
- 没有更多了
|
|
|
- </View>
|
|
|
- )}
|
|
|
-
|
|
|
- {!isLoading && allGoods.length === 0 && (
|
|
|
- <View className="flex flex-col items-center py-10">
|
|
|
- <View className="i-heroicons-inbox-20-solid w-12 h-12 text-gray-300 mb-4" />
|
|
|
- <Text className="text-gray-500">暂无商品</Text>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {isFetchingNextPage && (
|
|
|
+ <View className="flex justify-center py-4 mt-4">
|
|
|
+ <View className="i-heroicons-arrow-path-20-solid animate-spin w-6 h-6 text-blue-500" />
|
|
|
+ <Text className="ml-2 text-sm text-gray-500">加载更多...</Text>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {!hasNextPage && allGoods.length > 0 && (
|
|
|
+ <View className="text-center py-8 text-sm text-gray-400">
|
|
|
+ <View className="flex items-center justify-center">
|
|
|
+ <View className="i-heroicons-check-circle-20-solid w-4 h-4 mr-1" />
|
|
|
+ 已经到底啦
|
|
|
</View>
|
|
|
- )}
|
|
|
- </>
|
|
|
- )}
|
|
|
- </View>
|
|
|
- </ScrollView>
|
|
|
- </View>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {!isLoading && allGoods.length === 0 && (
|
|
|
+ <View className="flex flex-col items-center py-10">
|
|
|
+ <View className="i-heroicons-inbox-20-solid w-16 h-16 text-gray-300 mb-4" />
|
|
|
+ <Text className="text-gray-500 text-lg mb-2">暂无商品</Text>
|
|
|
+ <Text className="text-gray-400 text-sm">换个关键词试试吧</Text>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </View>
|
|
|
+ </ScrollView>
|
|
|
</TabBarLayout>
|
|
|
)
|
|
|
}
|