| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- 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}
- >
- {/* 搜索栏 - 参照tcb-shop-demo设计 */}
- <View className="search-bar-container">
- <View className="search-input-wrapper">
- <View className="i-heroicons-magnifying-glass-20-solid search-icon" />
- <input
- className="search-input"
- placeholder="搜索商品..."
- value={searchValue}
- onChange={(e) => setSearchValue(e.target.value)}
- onKeyPress={(e) => {
- if (e.key === 'Enter') {
- handleSubmit(searchValue)
- }
- }}
- />
- {searchValue && (
- <View
- className="i-heroicons-x-mark-20-solid clear-icon"
- onClick={() => {
- setSearchValue('')
- setKeyword('')
- }}
- />
- )}
- </View>
- </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
|