商品以及商品详情
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '搜索',
|
||||
})
|
||||
@@ -0,0 +1,194 @@
|
||||
/* ========================================
|
||||
搜索页
|
||||
======================================== */
|
||||
|
||||
.search-page {
|
||||
min-height: 100vh;
|
||||
background: #f7f8fa;
|
||||
}
|
||||
|
||||
/* ========== 自定义导航栏 ========== */
|
||||
.custom-navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.navbar-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.navbar-back {
|
||||
width: 60px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
.back-icon {
|
||||
font-size: 56px;
|
||||
color: #1a1a1a;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-search {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.navbar-search-btn {
|
||||
flex-shrink: 0;
|
||||
padding: 8px 8px;
|
||||
|
||||
.search-btn-text {
|
||||
font-size: 28px;
|
||||
color: #1989fa;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 滚动区域 ========== */
|
||||
.search-scroll {
|
||||
height: calc(100vh - 100px);
|
||||
}
|
||||
|
||||
/* ========== section 通用 ========== */
|
||||
.section {
|
||||
margin: 20px 24px;
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
padding: 20px 24px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.section-title {
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
padding: 4px;
|
||||
|
||||
.clear-icon {
|
||||
font-size: 28px;
|
||||
color: #969799;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tag-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 10px 20px;
|
||||
background: #f7f8fa;
|
||||
border-radius: 32px;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.hot {
|
||||
background: #fff7e6;
|
||||
}
|
||||
|
||||
.rank {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #ff6034;
|
||||
}
|
||||
|
||||
.tag-text {
|
||||
font-size: 26px;
|
||||
color: #646566;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 搜索结果 ========== */
|
||||
.results-section {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.results-header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
padding: 20px 0 16px;
|
||||
|
||||
.results-title {
|
||||
font-size: 28px;
|
||||
color: #646566;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.results-keyword {
|
||||
color: #1989fa;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.results-count {
|
||||
font-size: 24px;
|
||||
color: #969799;
|
||||
flex-shrink: 0;
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.results-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.result-post-wrap {
|
||||
/* ForumPostCard 自带 padding 和 shadow */
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120px 0;
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80px;
|
||||
margin-bottom: 24px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28px;
|
||||
color: #969799;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-safe {
|
||||
height: 40px;
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user