|
|
@@ -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>
|
|
|
)
|
|
|
}
|