index.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import { View, ScrollView, Text } from '@tarojs/components'
  2. import { useInfiniteQuery } from '@tanstack/react-query'
  3. import { useState } from 'react'
  4. import Taro from '@tarojs/taro'
  5. import { goodsClient } from '@/api'
  6. import { InferResponseType } from 'hono'
  7. import { Navbar } from '@/components/ui/navbar'
  8. import { useCart } from '@/contexts/CartContext'
  9. import GoodsList from '@/components/goods-list'
  10. import TDesignSearch from '@/components/tdesign/search'
  11. type GoodsResponse = InferResponseType<typeof goodsClient.$get, 200>
  12. type Goods = GoodsResponse['data'][0]
  13. export default function GoodsListPage() {
  14. const [searchKeyword, setSearchKeyword] = useState('')
  15. const [activeCategory, setActiveCategory] = useState('all')
  16. const { addToCart } = useCart()
  17. // 获取URL参数中的分类ID
  18. const params = Taro.getCurrentInstance().router?.params
  19. const categoryId = params?.cateId || ''
  20. const categories = [
  21. { id: 'all', name: '全部' },
  22. { id: 'hot', name: '热销' },
  23. { id: 'new', name: '新品' },
  24. ]
  25. const {
  26. data,
  27. isLoading,
  28. isFetchingNextPage,
  29. fetchNextPage,
  30. hasNextPage,
  31. refetch
  32. } = useInfiniteQuery({
  33. queryKey: ['goods-infinite', searchKeyword, categoryId],
  34. queryFn: async ({ pageParam = 1 }) => {
  35. // 构建筛选条件
  36. const filters: any = { state: 1 } // 只显示可用的商品
  37. if (categoryId) {
  38. filters.categoryId1 = categoryId
  39. }
  40. const response = await goodsClient.$get({
  41. query: {
  42. page: pageParam,
  43. pageSize: 10,
  44. keyword: searchKeyword,
  45. filters: JSON.stringify(filters)
  46. }
  47. })
  48. if (response.status !== 200) {
  49. throw new Error('获取商品失败')
  50. }
  51. return response.json()
  52. },
  53. getNextPageParam: (lastPage) => {
  54. const { pagination } = lastPage
  55. const totalPages = Math.ceil(pagination.total / pagination.pageSize)
  56. return pagination.current < totalPages ? pagination.current + 1 : undefined
  57. },
  58. staleTime: 5 * 60 * 1000,
  59. initialPageParam: 1,
  60. })
  61. // 合并所有分页数据
  62. const allGoods = data?.pages.flatMap(page => page.data) || []
  63. // 触底加载更多
  64. const handleScrollToLower = () => {
  65. if (hasNextPage && !isFetchingNextPage) {
  66. fetchNextPage()
  67. }
  68. }
  69. // 下拉刷新
  70. const onPullDownRefresh = () => {
  71. refetch().finally(() => {
  72. Taro.stopPullDownRefresh()
  73. })
  74. }
  75. // 跳转到商品详情
  76. const handleGoodsClick = (goods: Goods) => {
  77. Taro.navigateTo({
  78. url: `/pages/goods-detail/index?id=${goods.id}`
  79. })
  80. }
  81. // 添加到购物车
  82. const handleAddToCart = (goods: Goods) => {
  83. addToCart({
  84. id: goods.id,
  85. name: goods.name,
  86. price: goods.price,
  87. image: goods.imageFile?.fullUrl || '',
  88. stock: goods.stock,
  89. quantity: 1
  90. })
  91. Taro.showToast({
  92. title: '已添加到购物车',
  93. icon: 'success'
  94. })
  95. }
  96. return (
  97. <View className="min-h-screen bg-white">
  98. <Navbar
  99. title="商品列表"
  100. leftIcon="i-heroicons-chevron-left-20-solid"
  101. onClickLeft={() => Taro.navigateBack()}
  102. className="bg-white shadow-sm"
  103. />
  104. <ScrollView
  105. className="flex-1"
  106. scrollY
  107. onScrollToLower={handleScrollToLower}
  108. refresherEnabled
  109. refresherTriggered={false}
  110. onRefresherRefresh={onPullDownRefresh}
  111. >
  112. <View className="goods-page-container bg-[#f2f2f2] p-[20rpx_24rpx]">
  113. {/* 搜索栏 */}
  114. <View className="search-bar-container mb-4">
  115. <View onClick={() => Taro.navigateTo({ url: '/pages/search/index' })}>
  116. <TDesignSearch
  117. placeholder="搜索你想要的商品..."
  118. shape="round"
  119. value={searchKeyword}
  120. onChange={(value) => setSearchKeyword(value)}
  121. onSubmit={() => refetch()}
  122. onClear={() => {
  123. setSearchKeyword('')
  124. refetch()
  125. }}
  126. disabled={true}
  127. />
  128. </View>
  129. </View>
  130. {/* 分类筛选 */}
  131. {/* <View className="flex space-x-2 mb-4 overflow-x-auto">
  132. {categories.map((category) => (
  133. <View
  134. key={category.id}
  135. className={`whitespace-nowrap rounded-full px-4 py-2 transition-all cursor-pointer ${
  136. activeCategory === category.id
  137. ? 'bg-blue-500 text-white shadow-md'
  138. : 'text-gray-600 bg-gray-100 hover:bg-gray-200'
  139. }`}
  140. onClick={() => {
  141. setActiveCategory(category.id)
  142. refetch()
  143. }}
  144. >
  145. {category.name}
  146. </View>
  147. ))}
  148. </View> */}
  149. {/* 商品列表 */}
  150. {isLoading ? (
  151. <View className="flex justify-center py-10">
  152. <View className="i-heroicons-arrow-path-20-solid animate-spin w-8 h-8 text-blue-500" />
  153. </View>
  154. ) : (
  155. <>
  156. <GoodsList
  157. goodsList={allGoods.map(goods => ({
  158. id: goods.id.toString(),
  159. name: goods.name,
  160. cover_image: goods.imageFile?.fullUrl,
  161. price: goods.price,
  162. originPrice: goods.originPrice,
  163. tags: goods.stock <= 0 ? ['已售罄'] : goods.salesNum > 100 ? ['热销'] : []
  164. }))}
  165. onClick={(goods) => handleGoodsClick(allGoods.find(g => g.id.toString() === goods.id)!)}
  166. onAddCart={(goods) => handleAddToCart(allGoods.find(g => g.id.toString() === goods.id)!)}
  167. />
  168. {isFetchingNextPage && (
  169. <View className="flex justify-center py-4 mt-4">
  170. <View className="i-heroicons-arrow-path-20-solid animate-spin w-6 h-6 text-blue-500" />
  171. <Text className="ml-2 text-sm text-gray-500">加载更多...</Text>
  172. </View>
  173. )}
  174. {!hasNextPage && allGoods.length > 0 && (
  175. <View className="text-center py-8 text-sm text-gray-400">
  176. <View className="flex items-center justify-center">
  177. <View className="i-heroicons-check-circle-20-solid w-4 h-4 mr-1" />
  178. 已经到底啦
  179. </View>
  180. </View>
  181. )}
  182. {!isLoading && allGoods.length === 0 && (
  183. <View className="flex flex-col items-center py-10">
  184. <View className="i-heroicons-inbox-20-solid w-16 h-16 text-gray-300 mb-4" />
  185. <Text className="text-gray-500 text-lg mb-2">暂无商品</Text>
  186. <Text className="text-gray-400 text-sm">换个关键词试试吧</Text>
  187. </View>
  188. )}
  189. </>
  190. )}
  191. </View>
  192. </ScrollView>
  193. </View>
  194. )
  195. }