|
|
@@ -0,0 +1,216 @@
|
|
|
+import React, { useState, useEffect } from 'react'
|
|
|
+import { View, Text, ScrollView } from '@tarojs/components'
|
|
|
+import { useInfiniteQuery } from '@tanstack/react-query'
|
|
|
+import Taro from '@tarojs/taro'
|
|
|
+import { Navbar } from '@/components/ui/navbar'
|
|
|
+import TDesignSearch from '@/components/tdesign/search'
|
|
|
+import GoodsList from '@/components/goods-list'
|
|
|
+import { goodsClient } from '@/api'
|
|
|
+import { InferResponseType } from 'hono'
|
|
|
+import { useCart } from '@/utils/cart'
|
|
|
+import './index.css'
|
|
|
+
|
|
|
+type GoodsResponse = InferResponseType<typeof goodsClient.$get, 200>
|
|
|
+type Goods = GoodsResponse['data'][0]
|
|
|
+
|
|
|
+const SearchResultPage: React.FC = () => {
|
|
|
+ const [keyword, setKeyword] = useState('')
|
|
|
+ const [searchValue, setSearchValue] = useState('')
|
|
|
+ const { addToCart } = useCart()
|
|
|
+
|
|
|
+ // 获取页面参数
|
|
|
+ useEffect(() => {
|
|
|
+ const params = Taro.getCurrentInstance().router?.params
|
|
|
+ const searchKeyword = params?.keyword || ''
|
|
|
+ setKeyword(searchKeyword)
|
|
|
+ setSearchValue(searchKeyword)
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ const {
|
|
|
+ data,
|
|
|
+ isLoading,
|
|
|
+ isFetchingNextPage,
|
|
|
+ fetchNextPage,
|
|
|
+ hasNextPage,
|
|
|
+ refetch
|
|
|
+ } = useInfiniteQuery({
|
|
|
+ queryKey: ['search-goods-infinite', keyword],
|
|
|
+ queryFn: async ({ pageParam = 1 }) => {
|
|
|
+ const response = await goodsClient.$get({
|
|
|
+ query: {
|
|
|
+ page: pageParam,
|
|
|
+ pageSize: 10,
|
|
|
+ keyword: keyword,
|
|
|
+ filters: JSON.stringify({ state: 1 }) // 只显示可用的商品
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if (response.status !== 200) {
|
|
|
+ throw new Error('搜索商品失败')
|
|
|
+ }
|
|
|
+ return response.json()
|
|
|
+ },
|
|
|
+ getNextPageParam: (lastPage) => {
|
|
|
+ const { pagination } = lastPage
|
|
|
+ const totalPages = Math.ceil(pagination.total / pagination.pageSize)
|
|
|
+ return pagination.current < totalPages ? pagination.current + 1 : undefined
|
|
|
+ },
|
|
|
+ staleTime: 5 * 60 * 1000,
|
|
|
+ initialPageParam: 1,
|
|
|
+ enabled: !!keyword, // 只有有搜索关键词时才执行查询
|
|
|
+ })
|
|
|
+
|
|
|
+ // 合并所有分页数据
|
|
|
+ const allGoods = data?.pages.flatMap(page => page.data) || []
|
|
|
+
|
|
|
+ // 触底加载更多
|
|
|
+ const handleScrollToLower = () => {
|
|
|
+ if (hasNextPage && !isFetchingNextPage) {
|
|
|
+ fetchNextPage()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 下拉刷新
|
|
|
+ const onPullDownRefresh = () => {
|
|
|
+ refetch().finally(() => {
|
|
|
+ Taro.stopPullDownRefresh()
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理搜索提交
|
|
|
+ const handleSubmit = (value: string) => {
|
|
|
+ if (!value.trim()) return
|
|
|
+
|
|
|
+ // 更新搜索关键词并重新搜索
|
|
|
+ setKeyword(value)
|
|
|
+ setSearchValue(value)
|
|
|
+
|
|
|
+ // 重置分页数据
|
|
|
+ refetch()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 跳转到商品详情
|
|
|
+ const handleGoodsClick = (goods: Goods) => {
|
|
|
+ Taro.navigateTo({
|
|
|
+ url: `/pages/goods-detail/index?id=${goods.id}`
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加到购物车
|
|
|
+ const handleAddToCart = (goods: Goods) => {
|
|
|
+ addToCart({
|
|
|
+ id: goods.id,
|
|
|
+ name: goods.name,
|
|
|
+ price: goods.price,
|
|
|
+ image: goods.imageFile?.fullUrl || '',
|
|
|
+ stock: goods.stock,
|
|
|
+ quantity: 1
|
|
|
+ })
|
|
|
+ Taro.showToast({
|
|
|
+ title: '已添加到购物车',
|
|
|
+ icon: 'success'
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <View className="search-result-page">
|
|
|
+ <Navbar
|
|
|
+ title="搜索结果"
|
|
|
+ leftIcon="i-heroicons-chevron-left-20-solid"
|
|
|
+ onClickLeft={() => Taro.navigateBack()}
|
|
|
+ className="bg-white"
|
|
|
+ />
|
|
|
+
|
|
|
+ <ScrollView
|
|
|
+ className="search-result-content"
|
|
|
+ scrollY
|
|
|
+ onScrollToLower={handleScrollToLower}
|
|
|
+ refresherEnabled
|
|
|
+ refresherTriggered={false}
|
|
|
+ onRefresherRefresh={onPullDownRefresh}
|
|
|
+ >
|
|
|
+ {/* 搜索栏 */}
|
|
|
+ <View className="search-input-container">
|
|
|
+ <TDesignSearch
|
|
|
+ placeholder="搜索商品..."
|
|
|
+ shape="round"
|
|
|
+ value={searchValue}
|
|
|
+ onChange={(value) => setSearchValue(value)}
|
|
|
+ onSubmit={() => handleSubmit(searchValue)}
|
|
|
+ onClear={() => {
|
|
|
+ setSearchValue('')
|
|
|
+ setKeyword('')
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 搜索结果 */}
|
|
|
+ <View className="result-container">
|
|
|
+ {/* 搜索结果标题 */}
|
|
|
+ {keyword && (
|
|
|
+ <View className="result-header">
|
|
|
+ <Text className="result-title">
|
|
|
+ 搜索结果:"{keyword}"
|
|
|
+ </Text>
|
|
|
+ <Text className="result-count">
|
|
|
+ 共找到 {data?.pages[0]?.pagination?.total || 0} 件商品
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* 商品列表 */}
|
|
|
+ {isLoading ? (
|
|
|
+ <View className="loading-container">
|
|
|
+ <View className="i-heroicons-arrow-path-20-solid animate-spin w-8 h-8 text-blue-500" />
|
|
|
+ <Text className="loading-text">搜索中...</Text>
|
|
|
+ </View>
|
|
|
+ ) : allGoods.length === 0 ? (
|
|
|
+ <View className="empty-container">
|
|
|
+ <View className="i-heroicons-magnifying-glass-20-solid empty-icon" />
|
|
|
+ <Text className="empty-text">
|
|
|
+ {keyword ? '暂无相关商品' : '请输入搜索关键词'}
|
|
|
+ </Text>
|
|
|
+ <Text className="empty-subtext">
|
|
|
+ {keyword ? '换个关键词试试吧' : '搜索你想要的商品'}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ <View className="goods-list-container">
|
|
|
+ <GoodsList
|
|
|
+ goodsList={allGoods.map(goods => ({
|
|
|
+ id: goods.id.toString(),
|
|
|
+ name: goods.name,
|
|
|
+ cover_image: goods.imageFile?.fullUrl,
|
|
|
+ price: goods.price,
|
|
|
+ originPrice: goods.originPrice,
|
|
|
+ tags: goods.stock <= 0 ? ['已售罄'] : goods.salesNum > 100 ? ['热销'] : []
|
|
|
+ }))}
|
|
|
+ onClick={(goods) => handleGoodsClick(allGoods.find(g => g.id.toString() === goods.id)!)}
|
|
|
+ onAddCart={(goods) => handleAddToCart(allGoods.find(g => g.id.toString() === goods.id)!)}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 加载更多状态 */}
|
|
|
+ {isFetchingNextPage && (
|
|
|
+ <View className="loading-more-container">
|
|
|
+ <View className="i-heroicons-arrow-path-20-solid animate-spin w-6 h-6 text-blue-500" />
|
|
|
+ <Text className="loading-more-text">加载更多...</Text>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* 无更多数据状态 */}
|
|
|
+ {!hasNextPage && allGoods.length > 0 && (
|
|
|
+ <View className="no-more-container">
|
|
|
+ <View className="i-heroicons-check-circle-20-solid w-4 h-4 mr-1" />
|
|
|
+ <Text className="no-more-text">已经到底啦</Text>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </View>
|
|
|
+ </ScrollView>
|
|
|
+ </View>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+export default SearchResultPage
|