商品以及商品详情

This commit is contained in:
liu
2026-07-10 20:14:43 +08:00
parent c25cf0e8b3
commit 3022990ea1
93 changed files with 4576 additions and 1008 deletions
+227
View File
@@ -0,0 +1,227 @@
import { useState, useMemo, useEffect } from 'react'
import Taro from '@tarojs/taro'
import { View, Text, ScrollView } from '@tarojs/components'
import { Search, Tag } from '@antmjs/vantui'
import { shops } from '@/mock/shops'
import { forumPosts } from '@/mock/forum'
import ShopCard from '@/components/ShopCard'
import ForumPostCard from '@/components/ForumPostCard'
import type { Article } from '@/types/article'
import type { Shop } from '@/types/shop'
import './index.less'
/** 状态栏 + 导航栏高度 */
function getNavBarHeight() {
try {
const sys = Taro.getSystemInfoSync()
return { statusBarHeight: sys.statusBarHeight || 20, navBarHeight: 44 }
} catch {
return { statusBarHeight: 20, navBarHeight: 44 }
}
}
/** 搜索结果项的联合类型 */
type SearchResult =
| { kind: 'shop'; data: Shop }
| { kind: 'post'; data: Article }
export default function SearchPage() {
const [keyword, setKeyword] = useState('')
const [submittedKeyword, setSubmittedKeyword] = useState('')
const [history, setHistory] = useState<string[]>(['麻辣烫', '二手 iPad', '考研资料'])
const [navBarHeight, setNavBarHeight] = useState({ statusBarHeight: 20, navBarHeight: 44 })
useEffect(() => {
setNavBarHeight(getNavBarHeight())
}, [])
/** 热门搜索 */
const hotKeywords = ['麻辣烫', '奶茶', '二手 iPad', '考研资料', '黄焖鸡', '失物招领', '六级', '外卖红包']
/** 执行搜索(综合匹配商家和帖子) */
const results = useMemo<SearchResult[]>(() => {
if (!submittedKeyword.trim()) return []
const kw = submittedKeyword.toLowerCase().trim()
const matchedShops: Shop[] = shops.filter(
(s) =>
s.name.toLowerCase().includes(kw) ||
s.description.toLowerCase().includes(kw) ||
s.tags.some((t) => t.toLowerCase().includes(kw)),
)
const matchedPosts: Article[] = forumPosts.filter(
(p) =>
p.title.toLowerCase().includes(kw) ||
p.excerpt.toLowerCase().includes(kw) ||
p.source.toLowerCase().includes(kw),
)
return [
...matchedShops.map((s) => ({ kind: 'shop' as const, data: s })),
...matchedPosts.map((p) => ({ kind: 'post' as const, data: p })),
]
}, [submittedKeyword])
/** 返回 */
const handleBack = () => {
Taro.navigateBack()
}
/** 点击搜索 */
const handleSearch = () => {
const kw = keyword.trim()
if (!kw) {
Taro.showToast({ title: '请输入搜索关键词', icon: 'none' })
return
}
setSubmittedKeyword(kw)
// 保存到历史(去重,最多 8 条)
setHistory((prev) => {
const next = [kw, ...prev.filter((h) => h !== kw)]
return next.slice(0, 8)
})
}
/** 点击历史/热门 */
const handleQuickKeyword = (kw: string) => {
setKeyword(kw)
setSubmittedKeyword(kw)
setHistory((prev) => {
const next = [kw, ...prev.filter((h) => h !== kw)]
return next.slice(0, 8)
})
}
/** 清空历史 */
const handleClearHistory = () => {
setHistory([])
}
/** 商家点击 */
const handleShopClick = (shop: Shop) => {
Taro.navigateTo({ url: `/pages/shop-detail/index?id=${shop.id}` })
}
/** 帖子点击 */
const handlePostClick = (post: Article) => {
Taro.navigateTo({ url: `/pages/article/index?id=${post.id}` })
}
return (
<View className='search-page'>
{/* ========== 自定义导航栏(带返回 + 搜索框 + 搜索按钮) ========== */}
<View
className='custom-navbar'
style={{ paddingTop: `${navBarHeight.statusBarHeight}px` }}
>
<View
className='navbar-content'
style={{ height: `${navBarHeight.navBarHeight}px` }}
>
<View className='navbar-back' onClick={handleBack}>
<Text className='back-icon'></Text>
</View>
<View className='navbar-search'>
<Search
value={keyword}
placeholder='搜索商家、菜品、帖子'
shape='round'
background='#f2f3f5'
onChange={(e) => setKeyword(e.detail.value)}
onSearch={handleSearch}
/>
</View>
<View className='navbar-search-btn' onClick={handleSearch}>
<Text className='search-btn-text'></Text>
</View>
</View>
</View>
{/* 占位 */}
<View style={{ height: `${navBarHeight.statusBarHeight + navBarHeight.navBarHeight}px` }} />
<ScrollView className='search-scroll' scrollY enhanced showScrollbar={false}>
{/* ========== 搜索结果(已提交关键词时显示) ========== */}
{submittedKeyword ? (
<View className='results-section'>
<View className='results-header'>
<Text className='results-title'>
"<Text className='results-keyword'>{submittedKeyword}</Text>"
</Text>
<Text className='results-count'> {results.length} </Text>
</View>
{results.length > 0 ? (
<View className='results-list'>
{results.map((r) =>
r.kind === 'shop' ? (
<ShopCard
key={`shop-${r.data.id}`}
shop={r.data}
onClick={handleShopClick}
/>
) : (
<View key={`post-${r.data.id}`} className='result-post-wrap'>
<ForumPostCard post={r.data} onClick={handlePostClick} />
</View>
),
)}
</View>
) : (
<View className='empty-tip'>
<Text className='empty-icon'>🔍</Text>
<Text className='empty-text'></Text>
</View>
)}
</View>
) : (
<>
{/* ========== 搜索历史 ========== */}
{history.length > 0 && (
<View className='section'>
<View className='section-header'>
<Text className='section-title'></Text>
<View className='clear-btn' onClick={handleClearHistory}>
<Text className='clear-icon'>🗑</Text>
</View>
</View>
<View className='tag-list'>
{history.map((h) => (
<View
key={h}
className='tag-item'
onClick={() => handleQuickKeyword(h)}
>
<Text className='tag-text'>{h}</Text>
</View>
))}
</View>
</View>
)}
{/* ========== 热门搜索 ========== */}
<View className='section'>
<View className='section-header'>
<Text className='section-title'></Text>
</View>
<View className='tag-list'>
{hotKeywords.map((kw, idx) => (
<View
key={kw}
className={`tag-item ${idx < 3 ? 'hot' : ''}`}
onClick={() => handleQuickKeyword(kw)}
>
{idx < 3 && <Text className='rank'>{idx + 1}</Text>}
<Text className='tag-text'>{kw}</Text>
</View>
))}
</View>
</View>
</>
)}
<View className='bottom-safe' />
</ScrollView>
</View>
)
}