feat(find): 将发现页面重构为网点查询功能

- 修改页面标题为"网点"并设置导航栏样式
- 替换原有文章列表功能为网点搜索和展示功能
- 添加搜索框组件支持城市名称查询
- 实现网点卡片布局显示城市、地址、电话等信息
- 集成模拟数据展示网点列表
- 添加距离计算和导航功能
- 优化页面样式和用户体验
This commit is contained in:
2026-03-01 13:37:53 +08:00
parent eee4644d06
commit c90b69140c
3 changed files with 263 additions and 74 deletions

View File

@@ -1,4 +1,5 @@
export default definePageConfig({
navigationBarTitleText: '发现',
navigationStyle: 'custom',
navigationBarTitleText: '网点',
navigationBarTextStyle: 'black',
navigationBarBackgroundColor: '#ffffff'
})

View File

@@ -1,4 +1,144 @@
page {
background: linear-gradient(to bottom, #e9fff2, #f9fafb);
background-size: 100%;
background: #f7f7f7;
}
.sitePage {
min-height: 100vh;
background: linear-gradient(180deg, #fde8ea 0%, #f7f7f7 320rpx, #f7f7f7 100%);
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
}
.searchArea {
padding: 22rpx 24rpx 18rpx;
}
.searchBox {
height: 86rpx;
background: #fff;
border: 2rpx solid #b51616;
border-radius: 12rpx;
display: flex;
align-items: center;
overflow: hidden;
}
.searchInput {
flex: 1;
height: 86rpx;
padding: 0 20rpx;
font-size: 30rpx;
color: #222;
}
.searchPlaceholder {
color: #9e9e9e;
font-size: 30rpx;
}
.searchIconWrap {
width: 88rpx;
height: 86rpx;
display: flex;
align-items: center;
justify-content: center;
}
.siteList {
padding: 0 24rpx 24rpx;
}
.siteCard {
background: #fff;
border-radius: 18rpx;
padding: 22rpx 22rpx 18rpx;
margin-top: 18rpx;
box-shadow: 0 10rpx 24rpx rgba(0, 0, 0, 0.04);
}
.siteCardInner {
display: flex;
align-items: stretch;
}
.siteInfo {
flex: 1;
padding-right: 10rpx;
}
.siteRow {
display: flex;
align-items: flex-start;
padding: 10rpx 0;
}
.siteRowTop {
padding-top: 2rpx;
padding-bottom: 14rpx;
}
.siteLabel {
width: 170rpx;
flex: 0 0 170rpx;
color: #9a9a9a;
font-size: 30rpx;
line-height: 1.6;
}
.siteValue {
flex: 1;
color: #222;
font-size: 30rpx;
line-height: 1.6;
word-break: break-all;
}
.siteValueStrong {
font-weight: 700;
}
.siteDivider {
height: 2rpx;
background: #ededed;
}
.siteSide {
width: 160rpx;
flex: 0 0 160rpx;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
padding-left: 12rpx;
}
.navArrow {
width: 34rpx;
height: 34rpx;
border-top: 8rpx solid #e60012;
border-right: 8rpx solid #e60012;
border-radius: 4rpx;
transform: rotate(45deg);
margin-right: 8rpx;
}
.distanceText {
margin-top: 18rpx;
font-size: 28rpx;
color: #e60012;
font-weight: 700;
}
.emptyWrap {
padding: 40rpx 0;
display: flex;
justify-content: center;
}
.emptyText {
font-size: 28rpx;
color: #9a9a9a;
}
.bottomSafe {
height: 20rpx;
}

View File

@@ -1,82 +1,130 @@
import {useEffect, useState} from "react";
import Taro from '@tarojs/taro';
import {pageCmsArticle} from "@/api/cms/cmsArticle";
import {CmsArticle} from "@/api/cms/cmsArticle/model";
import {NavBar, Cell} from '@nutui/nutui-react-taro';
import {Text} from '@tarojs/components';
import {ArrowRight, ImageRectangle, Coupon, Follow} from '@nutui/icons-react-taro'
import {useMemo, useState} from 'react'
import Taro from '@tarojs/taro'
import {Input, Text, View} from '@tarojs/components'
import {Search} from '@nutui/icons-react-taro'
import './find.scss'
import navTo from "@/utils/common";
/**
* 文章终极列表
* @constructor
*/
type SiteItem = {
id: string
cityName: string
address: string
phone: string
contact: string
distanceMeter: number
}
const MOCK_SITES: SiteItem[] = [
{
id: '1',
cityName: '北京朝阳区网点',
address: '地安门西大街(南门)',
phone: '15878179339',
contact: '刘先生',
distanceMeter: 100
},
{
id: '2',
cityName: '兰州某某区网点',
address: '地安门西大街(南门)',
phone: '15878179339',
contact: '黄先生',
distanceMeter: 150
},
{
id: '3',
cityName: '合肥市某某区网点',
address: '地安门西大街(南门)',
phone: '15878179339',
contact: '黄先生',
distanceMeter: 250
},
{
id: '4',
cityName: '南宁市某某区网点',
address: '广西壮族自治区南宁市良庆区五象新区五象大道403号富雅国际金融中心G1栋高层6006',
phone: '15878179339',
contact: '柳先生',
distanceMeter: 1250
}
]
const Find = () => {
const [statusBarHeight, setStatusBarHeight] = useState<number>()
const [loading, setLoading] = useState<boolean>(false)
const [list, setList] = useState<CmsArticle[]>()
const [keyword, setKeyword] = useState<string>('')
const reload = async () => {
setLoading(true)
const article = await pageCmsArticle({categoryId: 4289, status: 0})
if (article) {
setList(article?.list)
setLoading(false)
}
const filtered = useMemo(() => {
const key = keyword.trim()
if (!key) return MOCK_SITES
return MOCK_SITES.filter((it) => it.cityName.includes(key))
}, [keyword])
const onNavigate = (item: SiteItem) => {
Taro.showToast({title: `导航至:${item.cityName}(示例)`, icon: 'none'})
}
useEffect(() => {
Taro.getSystemInfo({
success: (res) => {
setStatusBarHeight(res.statusBarHeight)
},
})
reload().then(() => {
console.log('初始化完成')
})
}, [])
const onSearch = () => {
Taro.showToast({title: '查询(示例)', icon: 'none'})
}
return (
<>
{loading && (<div></div>)}
<NavBar
fixed={false}
style={{marginTop: `${statusBarHeight}px`, backgroundColor: 'transparent'}}
onBackClick={() => {
}}
>
<span></span>
</NavBar>
{list && (
<>
<Cell title={
<div style={{display: 'inline-flex', alignItems: 'center'}}>
<ImageRectangle size={18}/>
<Text className={'pl-3'} style={{fontSize: '16px'}}></Text>
</div>
} extra={<ArrowRight color="#cccccc" size={18}/>}/>
<Cell
title={
<div style={{display: 'inline-flex', alignItems: 'center'}}>
<Coupon size={18}/>
<span className={'pl-3'} style={{fontSize: '16px'}}></span>
</div>
}
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => {
navTo('/shop/shopArticle/index', true)
}}
<View className='sitePage'>
<View className='searchArea'>
<View className='searchBox'>
<Input
className='searchInput'
value={keyword}
placeholder='请输入城市名称查询'
placeholderClass='searchPlaceholder'
confirmType='search'
onInput={(e) => setKeyword(e.detail.value)}
onConfirm={onSearch}
/>
<Cell title={
<div style={{display: 'inline-flex', alignItems: 'center'}}>
<Follow size={18}/>
<span className={'pl-3'} style={{fontSize: '16px'}}></span>
</div>
} extra={<ArrowRight color="#cccccc" size={18}/>}/>
</>
)}
</>
<View className='searchIconWrap' onClick={onSearch}>
<Search size={18} color='#b51616' />
</View>
</View>
</View>
<View className='siteList'>
{filtered.map((item) => (
<View key={item.id} className='siteCard'>
<View className='siteCardInner'>
<View className='siteInfo'>
<View className='siteRow siteRowTop'>
<Text className='siteLabel'></Text>
<Text className='siteValue siteValueStrong'>{item.cityName}</Text>
</View>
<View className='siteDivider' />
<View className='siteRow'>
<Text className='siteLabel'></Text>
<Text className='siteValue'>{item.address}</Text>
</View>
<View className='siteRow'>
<Text className='siteLabel'></Text>
<Text className='siteValue'>{item.phone}</Text>
</View>
<View className='siteRow'>
<Text className='siteLabel'></Text>
<Text className='siteValue'>{item.contact}</Text>
</View>
</View>
<View className='siteSide' onClick={() => onNavigate(item)}>
<View className='navArrow' />
<Text className='distanceText'>{item.distanceMeter}</Text>
</View>
</View>
</View>
))}
{filtered.length === 0 && (
<View className='emptyWrap'>
<Text className='emptyText'></Text>
</View>
)}
</View>
<View className='bottomSafe' />
</View>
)
}
export default Find