228 lines
7.5 KiB
TypeScript
228 lines
7.5 KiB
TypeScript
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>
|
||
)
|
||
}
|