Explorar el Código

✨ feat(home): 实现商品列表无限滚动加载功能

- 集成react-query实现商品数据无限滚动加载
- 添加商品列表加载状态样式(加载中、加载失败、空数据、加载更多、无更多数据)
- 实现商品数据分页请求和转换逻辑
- 添加ScrollView容器支持滚动加载更多

♻️ refactor(home): 重构首页数据获取逻辑

- 移除模拟商品数据,接入真实API请求
- 替换useState+useEffect数据管理为useInfiniteQuery
- 优化组件导入结构,移除未使用的依赖

💄 style(home): 添加页面滚动和状态样式

- 新增ScrollView容器样式
- 添加各种加载状态(加载中、错误、空数据等)的CSS样式
- 优化页面布局结构,提升用户体验
yourname hace 1 mes
padre
commit
5eff22476d
Se han modificado 2 ficheros con 156 adiciones y 91 borrados
  1. 34 0
      mini/src/pages/index/index.css
  2. 122 91
      mini/src/pages/index/index.tsx

+ 34 - 0
mini/src/pages/index/index.css

@@ -42,4 +42,38 @@
 .home-page-container .goods-list-wrap {
   background: #f5f5f5 !important;
   margin-top: 16rpx;
+}
+
+/* ScrollView 样式 */
+.home-scroll-view {
+  height: 100vh;
+}
+
+/* 加载状态样式 */
+.loading-container,
+.error-container,
+.empty-container,
+.loading-more-container,
+.no-more-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 40rpx 0;
+}
+
+.loading-text,
+.error-text,
+.empty-text,
+.loading-more-text,
+.no-more-text {
+  font-size: 28rpx;
+  color: #999;
+}
+
+.error-text {
+  color: #ff4d4f;
+}
+
+.empty-text {
+  color: #666;
 }

+ 122 - 91
mini/src/pages/index/index.tsx

@@ -1,16 +1,19 @@
-import React, { useState, useEffect } from 'react'
-import { View, Text } from '@tarojs/components'
+import React, { useState } from 'react'
+import { View, Text, ScrollView } from '@tarojs/components'
+import { useInfiniteQuery } from '@tanstack/react-query'
 import { TabBarLayout } from '@/layouts/tab-bar-layout'
 import TDesignSearch from '@/components/tdesign/search'
 import TDesignSwiper from '@/components/tdesign/swiper'
 import GoodsList from '@/components/goods-list'
 import { GoodsData } from '@/components/goods-card'
+import { goodsClient } from '@/api'
+import { InferResponseType } from 'hono'
 import './index.css'
 
-const HomePage: React.FC = () => {
-  const [pageLoading, setPageLoading] = useState(true)
-  const [goodsList, setGoodsList] = useState<GoodsData[]>([])
+type GoodsResponse = InferResponseType<typeof goodsClient.$get, 200>
+type Goods = GoodsResponse['data'][0]
 
+const HomePage: React.FC = () => {
   // 模拟轮播图数据
   const imgSrcs = [
     'https://via.placeholder.com/750x400/fa4126/ffffff?text=Banner1',
@@ -18,61 +21,61 @@ const HomePage: React.FC = () => {
     'https://via.placeholder.com/750x400/34c759/ffffff?text=Banner3'
   ]
 
-  // 模拟商品数据
-  const mockGoodsList: GoodsData[] = [
-    {
-      id: '1',
-      name: '高品质T恤 纯棉舒适 多色可选',
-      price: 8990, // 单位:分
-      cover_image: 'https://via.placeholder.com/300x300/fa4126/ffffff?text=T恤',
-      tags: ['热销', '新品']
-    },
-    {
-      id: '2',
-      name: '时尚牛仔裤 修身版型 百搭款式',
-      price: 15990,
-      cover_image: 'https://via.placeholder.com/300x300/fa550f/ffffff?text=牛仔裤',
-      tags: ['热销']
-    },
-    {
-      id: '3',
-      name: '运动鞋 轻便舒适 防滑耐磨',
-      price: 29990,
-      cover_image: 'https://via.placeholder.com/300x300/34c759/ffffff?text=运动鞋',
-      tags: ['新品']
+  const {
+    data,
+    isLoading,
+    isFetchingNextPage,
+    fetchNextPage,
+    hasNextPage,
+    error
+  } = useInfiniteQuery({
+    queryKey: ['home-goods-infinite'],
+    queryFn: async ({ pageParam = 1 }) => {
+      const response = await goodsClient.$get({
+        query: {
+          page: pageParam,
+          pageSize: 10,
+          filters: JSON.stringify({ state: 1 }) // 只显示可用的商品
+        }
+      })
+      if (response.status !== 200) {
+        throw new Error('获取商品失败')
+      }
+      return response.json()
     },
-    {
-      id: '4',
-      name: '智能手表 多功能运动健康监测',
-      price: 59990,
-      cover_image: 'https://via.placeholder.com/300x300/007AFF/ffffff?text=智能手表',
-      tags: ['热销', '新品']
+    getNextPageParam: (lastPage) => {
+      const { pagination } = lastPage
+      const totalPages = Math.ceil(pagination.total / pagination.pageSize)
+      return pagination.current < totalPages ? pagination.current + 1 : undefined
     },
-    {
-      id: '5',
-      name: '无线耳机 降噪蓝牙 长续航',
-      price: 39990,
-      cover_image: 'https://via.placeholder.com/300x300/8E44AD/ffffff?text=无线耳机',
-      tags: ['热销']
-    },
-    {
-      id: '6',
-      name: '保温杯 304不锈钢 长效保温',
-      price: 12990,
-      cover_image: 'https://via.placeholder.com/300x300/E74C3C/ffffff?text=保温杯',
-      tags: ['新品']
+    staleTime: 5 * 60 * 1000,
+    initialPageParam: 1,
+  })
+
+  // 合并所有分页数据
+  const allGoods = data?.pages.flatMap(page => page.data) || []
+
+  // 数据转换:将API返回的商品数据转换为GoodsData接口格式
+  const convertToGoodsData = (goods: Goods): GoodsData => {
+    return {
+      id: goods.id,
+      name: goods.name,
+      cover_image: goods.imageFile?.fullUrl || '',
+      price: goods.price,
+      originPrice: goods.originPrice,
+      tags: goods.salesNum > 100 ? ['热销'] : ['新品']
     }
-  ]
+  }
 
-  // 模拟数据加载
-  useEffect(() => {
-    const timer = setTimeout(() => {
-      setGoodsList(mockGoodsList)
-      setPageLoading(false)
-    }, 1000)
+  // 转换后的商品列表
+  const goodsList = allGoods.map(convertToGoodsData)
 
-    return () => clearTimeout(timer)
-  }, [])
+  // 触底加载更多
+  const handleScrollToLower = () => {
+    if (hasNextPage && !isFetchingNextPage) {
+      fetchNextPage()
+    }
+  }
 
 
   // 商品点击
@@ -97,48 +100,76 @@ const HomePage: React.FC = () => {
 
   return (
     <TabBarLayout activeKey="home">
-      {pageLoading ? (
-        <View style={{ textAlign: 'center', color: '#b9b9b9' }}>
-          <Text>加载中...</Text>
-        </View>
-      ) : (
-        <View>
-          {/* 页面头部 - 搜索栏和轮播图 */}
-          <View className="home-page-header">
-            {/* 搜索栏 */}
-            <View className="search" onClick={handleSearchClick}>
-              <TDesignSearch
-                placeholder="搜索商品..."
-                disabled={true}
-                shape="round"
+      <ScrollView
+        className="home-scroll-view"
+        scrollY
+        onScrollToLower={handleScrollToLower}
+      >
+        {/* 页面头部 - 搜索栏和轮播图 */}
+        <View className="home-page-header">
+          {/* 搜索栏 */}
+          <View className="search" onClick={handleSearchClick}>
+            <TDesignSearch
+              placeholder="搜索商品..."
+              disabled={true}
+              shape="round"
+            />
+          </View>
+
+          {/* 轮播图 */}
+          <View className="swiper-wrap">
+            {imgSrcs.length > 0 && (
+              <TDesignSwiper
+                images={imgSrcs}
+                autoplay={true}
+                interval={5000}
+                indicatorDots={true}
+                height="300rpx"
               />
+            )}
+          </View>
+        </View>
+
+        {/* 页面内容 - 商品列表 */}
+        <View className="home-page-container">
+          {isLoading ? (
+            <View className="loading-container">
+              <Text className="loading-text">加载中...</Text>
+            </View>
+          ) : error ? (
+            <View className="error-container">
+              <Text className="error-text">加载失败,请重试</Text>
+            </View>
+          ) : goodsList.length === 0 ? (
+            <View className="empty-container">
+              <Text className="empty-text">暂无商品</Text>
             </View>
+          ) : (
+            <>
+              <GoodsList
+                goodsList={goodsList}
+                onClick={handleGoodsClick}
+                onAddCart={handleAddCart}
+                onThumbClick={handleThumbClick}
+              />
 
-            {/* 轮播图 */}
-            <View className="swiper-wrap">
-              {imgSrcs.length > 0 && (
-                <TDesignSwiper
-                  images={imgSrcs}
-                  autoplay={true}
-                  interval={5000}
-                  indicatorDots={true}
-                  height="300rpx"
-                />
+              {/* 加载更多状态 */}
+              {isFetchingNextPage && (
+                <View className="loading-more-container">
+                  <Text className="loading-more-text">加载更多...</Text>
+                </View>
               )}
-            </View>
-          </View>
 
-          {/* 页面内容 - 商品列表 */}
-          <View className="home-page-container">
-            <GoodsList
-              goodsList={goodsList}
-              onClick={handleGoodsClick}
-              onAddCart={handleAddCart}
-              onThumbClick={handleThumbClick}
-            />
-          </View>
+              {/* 无更多数据状态 */}
+              {!hasNextPage && goodsList.length > 0 && (
+                <View className="no-more-container">
+                  <Text className="no-more-text">已经到底啦</Text>
+                </View>
+              )}
+            </>
+          )}
         </View>
-      )}
+      </ScrollView>
     </TabBarLayout>
   )
 }