Files
xin-school-taro/src/pages/search/index.tsx
T
2026-07-10 20:14:43 +08:00

228 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
)
}