forked from gxwebsoft/mp-10550
修复已知问题
This commit is contained in:
33
src/shop/search/components/GoodsItem.scss
Normal file
33
src/shop/search/components/GoodsItem.scss
Normal file
@@ -0,0 +1,33 @@
|
||||
// 使用与首页相同的样式,主要依赖Tailwind CSS类名
|
||||
.buy-btn {
|
||||
background: linear-gradient(to right, #1cd98a, #24ca94);
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.cart-icon {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 20px 0 0 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.car-no {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
58
src/shop/search/components/GoodsItem.tsx
Normal file
58
src/shop/search/components/GoodsItem.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { View } from '@tarojs/components'
|
||||
import { Image } from '@nutui/nutui-react-taro'
|
||||
import { Share } from '@nutui/icons-react-taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { ShopGoods } from '@/api/shop/shopGoods/model'
|
||||
import './GoodsItem.scss'
|
||||
|
||||
interface GoodsItemProps {
|
||||
goods: ShopGoods
|
||||
}
|
||||
|
||||
const GoodsItem = ({ goods }: GoodsItemProps) => {
|
||||
// 跳转到商品详情
|
||||
const goToDetail = () => {
|
||||
Taro.navigateTo({
|
||||
url: `/shop/goodsDetail/index?id=${goods.goodsId}`
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'flex flex-col rounded-lg bg-white shadow-sm w-full mb-5'}>
|
||||
<Image
|
||||
src={goods.image || ''}
|
||||
mode={'aspectFit'}
|
||||
lazyLoad={false}
|
||||
radius="10px 10px 0 0"
|
||||
height="180"
|
||||
onClick={goToDetail}
|
||||
/>
|
||||
<div className={'flex flex-col p-2 rounded-lg'}>
|
||||
<div>
|
||||
<div className={'car-no text-sm'}>{goods.name || goods.goodsName}</div>
|
||||
<div className={'flex justify-between text-xs py-1'}>
|
||||
<span className={'text-orange-500'}>{goods.comments || ''}</span>
|
||||
<span className={'text-gray-400'}>已售 {goods.sales || 0}</span>
|
||||
</div>
|
||||
<div className={'flex justify-between items-center py-2'}>
|
||||
<div className={'flex text-red-500 text-xl items-baseline'}>
|
||||
<span className={'text-xs'}>¥</span>
|
||||
<span className={'font-bold text-2xl'}>{goods.price || '0.00'}</span>
|
||||
</div>
|
||||
<div className={'buy-btn'}>
|
||||
<div className={'cart-icon'}>
|
||||
<Share size={20} className={'mx-4 mt-2'}
|
||||
onClick={goToDetail}/>
|
||||
</div>
|
||||
<div className={'text-white pl-4 pr-5'}
|
||||
onClick={goToDetail}>购买
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default GoodsItem
|
||||
3
src/shop/search/index.config.ts
Normal file
3
src/shop/search/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '商品搜索'
|
||||
})
|
||||
103
src/shop/search/index.scss
Normal file
103
src/shop/search/index.scss
Normal file
@@ -0,0 +1,103 @@
|
||||
.search-page {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
|
||||
// 搜索输入框样式
|
||||
.search-input-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #f5f5f5;
|
||||
border-radius: 20px;
|
||||
padding: 0 12px;
|
||||
|
||||
.search-icon {
|
||||
color: #999;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-size: 14px;
|
||||
|
||||
input {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
padding: 0 16px;
|
||||
height: 36px;
|
||||
border-radius: 18px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.search-content {
|
||||
padding-top: calc(32px + env(safe-area-inset-top));
|
||||
|
||||
.search-history {
|
||||
background: #fff;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.history-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
.history-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.history-list {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
|
||||
.history-item {
|
||||
padding: 8px 16px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 16px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
|
||||
&:active {
|
||||
background: #e5e5e5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-results {
|
||||
.result-header {
|
||||
padding: 16px;
|
||||
color: #666;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.loading-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40px 0;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
237
src/shop/search/index.tsx
Normal file
237
src/shop/search/index.tsx
Normal file
@@ -0,0 +1,237 @@
|
||||
import {useEffect, useState} from 'react'
|
||||
import {useRouter} from '@tarojs/taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import {View} from '@tarojs/components'
|
||||
import {Loading, Empty, InfiniteLoading, Input, Button} from '@nutui/nutui-react-taro'
|
||||
import {Search} from '@nutui/icons-react-taro';
|
||||
import {ShopGoods} from '@/api/shop/shopGoods/model'
|
||||
import {pageShopGoods} from '@/api/shop/shopGoods'
|
||||
import GoodsItem from './components/GoodsItem'
|
||||
import './index.scss'
|
||||
|
||||
const SearchPage = () => {
|
||||
const router = useRouter()
|
||||
const [keywords, setKeywords] = useState<string>('')
|
||||
const [goodsList, setGoodsList] = useState<ShopGoods[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [page, setPage] = useState(1)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
const [total, setTotal] = useState(0)
|
||||
const [searchHistory, setSearchHistory] = useState<string[]>([])
|
||||
|
||||
// 从路由参数获取搜索关键词
|
||||
useEffect(() => {
|
||||
const {keywords: routeKeywords} = router.params || {}
|
||||
if (routeKeywords) {
|
||||
setKeywords(decodeURIComponent(routeKeywords))
|
||||
handleSearch(decodeURIComponent(routeKeywords), 1).then()
|
||||
}
|
||||
|
||||
// 加载搜索历史
|
||||
loadSearchHistory()
|
||||
}, [])
|
||||
|
||||
// 加载搜索历史
|
||||
const loadSearchHistory = () => {
|
||||
try {
|
||||
const history = Taro.getStorageSync('search_history') || []
|
||||
setSearchHistory(history)
|
||||
} catch (error) {
|
||||
console.error('加载搜索历史失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存搜索历史
|
||||
const saveSearchHistory = (keyword: string) => {
|
||||
try {
|
||||
let history = Taro.getStorageSync('search_history') || []
|
||||
// 去重并添加到开头
|
||||
history = history.filter(item => item !== keyword)
|
||||
history.unshift(keyword)
|
||||
// 只保留最近10条
|
||||
history = history.slice(0, 10)
|
||||
Taro.setStorageSync('search_history', history)
|
||||
setSearchHistory(history)
|
||||
} catch (error) {
|
||||
console.error('保存搜索历史失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeywords = (keywords) => {
|
||||
setKeywords(keywords)
|
||||
handleSearch(keywords).then()
|
||||
}
|
||||
|
||||
// 搜索商品
|
||||
const handleSearch = async (searchKeywords: string, pageNum: number = 1) => {
|
||||
if (!searchKeywords.trim()) {
|
||||
Taro.showToast({
|
||||
title: '请输入搜索关键词',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
const params = {
|
||||
keywords: searchKeywords.trim(),
|
||||
page: pageNum,
|
||||
size: 10,
|
||||
isShow: 1 // 只搜索上架商品
|
||||
}
|
||||
|
||||
const result = await pageShopGoods(params)
|
||||
|
||||
if (pageNum === 1) {
|
||||
setGoodsList(result?.list || [])
|
||||
setTotal(result?.count || 0)
|
||||
// 保存搜索历史
|
||||
saveSearchHistory(searchKeywords.trim())
|
||||
} else {
|
||||
setGoodsList(prev => [...prev, ...(result?.list || [])])
|
||||
}
|
||||
|
||||
setHasMore((result?.list?.length || 0) >= 10)
|
||||
setPage(pageNum)
|
||||
|
||||
} catch (error) {
|
||||
console.error('搜索失败:', error)
|
||||
Taro.showToast({
|
||||
title: '搜索失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
const loadMore = () => {
|
||||
if (!loading && hasMore && keywords.trim()) {
|
||||
handleSearch(keywords, page + 1).then()
|
||||
}
|
||||
}
|
||||
|
||||
// 点击历史搜索
|
||||
const onHistoryClick = (keyword: string) => {
|
||||
setKeywords(keyword)
|
||||
setPage(1)
|
||||
handleSearch(keyword, 1)
|
||||
}
|
||||
|
||||
// 清空搜索历史
|
||||
const clearHistory = () => {
|
||||
Taro.showModal({
|
||||
title: '提示',
|
||||
content: '确定要清空搜索历史吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
Taro.removeStorageSync('search_history')
|
||||
setSearchHistory([])
|
||||
} catch (error) {
|
||||
console.error('清空搜索历史失败:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="search-page pt-3">
|
||||
<div className={'px-2'}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
background: '#ffffff',
|
||||
padding: '0 5px',
|
||||
borderRadius: '20px',
|
||||
marginTop: '5px',
|
||||
}}
|
||||
>
|
||||
<Search size={18} className={'ml-2 text-gray-400'}/>
|
||||
<Input
|
||||
placeholder="搜索商品"
|
||||
value={keywords}
|
||||
onChange={handleKeywords}
|
||||
onConfirm={() => handleSearch(keywords)}
|
||||
style={{padding: '9px 8px'}}
|
||||
/>
|
||||
<div
|
||||
className={'flex items-center'}
|
||||
>
|
||||
<Button type="success" style={{background: 'linear-gradient(to bottom, #1cd98a, #24ca94)'}}
|
||||
onClick={() => handleSearch(keywords)}>
|
||||
搜索
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/*<SearchBar style={{height: `${statusBarHeight}px`}} shape="round" placeholder="搜索商品" onChange={setKeywords} onSearch={handleSearch}/>*/}
|
||||
|
||||
{/* 搜索内容 */}
|
||||
<View className="search-content">
|
||||
{/* 搜索历史 */}
|
||||
{!keywords && searchHistory.length > 0 && (
|
||||
<View className="search-history">
|
||||
<View className="history-header">
|
||||
<View className="text-sm">搜索历史</View>
|
||||
<View className={'text-gray-400'} onClick={clearHistory}>清空</View>
|
||||
</View>
|
||||
<View className="history-list">
|
||||
{searchHistory.map((item, index) => (
|
||||
<View
|
||||
key={index}
|
||||
className="history-item"
|
||||
onClick={() => onHistoryClick(item)}
|
||||
>
|
||||
{item}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 搜索结果 */}
|
||||
{keywords && (
|
||||
<View className="search-results">
|
||||
{/* 结果统计 */}
|
||||
<View className="result-header">
|
||||
找到 {total} 件相关商品
|
||||
</View>
|
||||
|
||||
{/* 商品列表 */}
|
||||
{loading && page === 1 ? (
|
||||
<View className="loading-wrapper">
|
||||
<Loading>搜索中...</Loading>
|
||||
</View>
|
||||
) : goodsList.length > 0 ? (
|
||||
<div className={'py-3'}>
|
||||
<div className={'flex flex-col justify-between items-center rounded-lg px-2'}>
|
||||
<InfiniteLoading
|
||||
hasMore={hasMore}
|
||||
// @ts-ignore
|
||||
onLoadMore={loadMore}
|
||||
loadingText="加载中..."
|
||||
loadMoreText="没有更多了"
|
||||
>
|
||||
{goodsList.map((item) => (
|
||||
<GoodsItem key={item.goodsId} goods={item}/>
|
||||
))}
|
||||
</InfiniteLoading>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Empty description="暂无相关商品"/>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default SearchPage
|
||||
Reference in New Issue
Block a user