import Taro from '@tarojs/taro';
|
import { useState, useEffect } from 'react';
|
import { View, Text, Image, Input } from '@tarojs/components';
|
import './index.less';
|
import map from '../../assets/map.png'
|
import address from '../../assets/address.png'
|
import arrow from '../../assets/arrow.png'
|
import { getDistance } from '../../../utils/Robotaxi-public-method';
|
import { getOrderDistance } from '../../api/select-map-site';
|
import { checkOtp } from '../../../utils/tools';
|
import TipPopup from '../select-map-site/components/tip-popup';
|
import SiteListItem from './components/site-list-item';
|
import useSiteSearch from './hooks/use-site-search';
|
import {
|
sortSitesByNameMatchAndDistance,
|
splitHighlightParts,
|
filterNearbyListByCurrentSite
|
} from './utils/site-search';
|
|
|
|
export default function SiteSearchPage() {
|
const [searchValue, setSearchValue] = useState('') //搜索值
|
const [showAllHistory, setShowAllHistory] = useState(false) // 是否展示全部历史记录
|
const [showAllRecommend, setShowAllRecommend] = useState(false) // 是否展示全部推荐站点
|
const [distanceConfig, setDistanceConfig] = useState<any>({})
|
const [showTipPopup, setShowTipPopup] = useState(false)
|
const [tipType, setTipType] = useState('')
|
|
const MAX_SHOW_COUNT = 5 // 默认最多显示5条
|
const MAX_EXPANDED_COUNT = 10
|
|
const router = Taro.useRouter();
|
const {
|
pageType,
|
siteList,
|
historySite,
|
nonOperationalSite,
|
nearbySiteList,
|
isSearching,
|
lastCompletedKeyword,
|
isInitLoaded,
|
searchSite,
|
debouncedSearch,
|
cancelDebouncedSearch,
|
flushDebouncedSearch,
|
confirmSearch,
|
clearSearchResults
|
} = useSiteSearch({
|
router,
|
searchValue,
|
maxExpandedCount: MAX_EXPANDED_COUNT,
|
maxShowCount: MAX_SHOW_COUNT
|
});
|
|
useEffect(() => {
|
getOrderDistance().then((res: any) => {
|
if (res) {
|
setDistanceConfig({
|
maxDistance: res.maxDistance,
|
minDistance: res.minDistance,
|
adsorptionDistance: res.adsorptionDistance
|
});
|
}
|
}).catch(() => { });
|
}, []);
|
|
const changeSearchValue = (e: any) => {
|
const val = e.detail.value;
|
setSearchValue(val);
|
debouncedSearch(val);
|
return val;
|
}
|
|
// 切换历史记录展示状态
|
const toggleHistoryShow = () => {
|
setShowAllHistory(!showAllHistory)
|
}
|
|
// 切换推荐站点展示状态
|
const toggleRecommendShow = () => {
|
setShowAllRecommend(!showAllRecommend)
|
}
|
|
|
const toSelectSite = () => {
|
Taro.navigateTo({
|
url: '/robotaxi/pages/select-map-site/index?pageType=' + pageType,
|
})
|
}
|
|
const onChangeSelectSite = async (item, isNonOperational = true) => {
|
if (item?.operationStatus === 2) {
|
Taro.showToast({
|
title: '当前点位暂未开放,请重新选点',
|
icon: 'none',
|
})
|
return
|
}
|
if (!isNonOperational) {
|
Taro.showToast({
|
title: '当前点位暂未开放,请重新选点',
|
icon: 'none',
|
})
|
return
|
}
|
if (pageType == 'start') {
|
const endSiteCache = Taro.getStorageSync('endSite');
|
if (endSiteCache?.electronicAreaId && item?.electronicAreaId && endSiteCache.electronicAreaId !== item.electronicAreaId) {
|
Taro.showToast({
|
title: '起终点需在同一运营区域',
|
icon: 'none',
|
})
|
return
|
}
|
try {
|
if (!distanceConfig?.maxDistance) {
|
const res = await getOrderDistance();
|
if (res) {
|
setDistanceConfig({
|
maxDistance: res.maxDistance,
|
minDistance: res.minDistance,
|
adsorptionDistance: res.adsorptionDistance
|
});
|
}
|
}
|
const loc = await Taro.getLocation({ type: 'gcj02' });
|
const distance = getDistance(loc.latitude, loc.longitude, item.latitude, item.longitude);
|
if (distanceConfig?.maxDistance && distance > distanceConfig.maxDistance) {
|
setTipType('2');
|
setShowTipPopup(true);
|
return;
|
}
|
} catch { }
|
Taro.setStorageSync('startSite', item);
|
Taro.navigateBack()
|
} else if (pageType == 'end') {
|
const otp = await checkOtp(false);
|
if (!otp) {
|
Taro.navigateTo({ url: '/pages/user/login?hideBack=1' });
|
return
|
}
|
const startSiteCache = Taro.getStorageSync('startSite');
|
if (startSiteCache?.electronicAreaId && item?.electronicAreaId && startSiteCache.electronicAreaId !== item.electronicAreaId) {
|
Taro.showToast({
|
title: '起终点需在同一运营区域',
|
icon: 'none',
|
})
|
return
|
}
|
try {
|
if (!distanceConfig?.minDistance) {
|
const res = await getOrderDistance();
|
if (res) {
|
setDistanceConfig({
|
maxDistance: res.maxDistance,
|
minDistance: res.minDistance,
|
adsorptionDistance: res.adsorptionDistance,
|
geo_lat: item.latitude,
|
geo_lng: item.longitude,
|
odd_grid_id: item.electronicAreaId,
|
timestamp: Date.now(),
|
});
|
}
|
}
|
if (startSiteCache && startSiteCache.longitude && startSiteCache.latitude) {
|
const distance = getDistance(startSiteCache.latitude, startSiteCache.longitude, item.latitude, item.longitude);
|
if (distanceConfig?.minDistance && distance < distanceConfig.minDistance) {
|
setTipType('1');
|
setShowTipPopup(true);
|
return;
|
}
|
}
|
} catch { }
|
Taro.setStorageSync('endSite', item)
|
Taro.reLaunch({
|
url: Taro.getStorageSync('orderId') ? '/robotaxi/pages/begin-route/index?skipPageFlag=1' : '/robotaxi/pages/begin-route/index',
|
})
|
}
|
}
|
|
const clearSearchValue = () => {
|
setSearchValue('');
|
clearSearchResults();
|
}
|
const handleCloseTipPopup = () => {
|
setShowTipPopup(false);
|
setTimeout(() => {
|
setTipType('');
|
}, 600);
|
}
|
|
const renderHighlightedText = (text: string, className: string) => { // 通用高亮渲染
|
const kw = (searchValue || '').trim();
|
if (!kw) {
|
return <View className={className}>{text}</View>;
|
}
|
const parts = splitHighlightParts(String(text || ''), kw);
|
return (
|
<View className={className}>
|
{parts.map((part, idx) => (
|
<Text key={idx} className={part.match ? 'highlight-match' : ''}>
|
{part.text}
|
</Text>
|
))}
|
</View>
|
);
|
}
|
|
const renderSiteItems = (list: any[], onSelect: (item: any) => void, showDistance = true) => { // 复用列表项渲染
|
return list.map((item, index) => (
|
<SiteListItem
|
key={item.id || index}
|
itemKey={item.id || index}
|
item={item}
|
iconSrc={address}
|
showDistance={showDistance}
|
onSelect={onSelect}
|
renderHighlightedText={renderHighlightedText}
|
/>
|
));
|
};
|
|
const currentSiteCache = pageType === 'start'
|
? Taro.getStorageSync('startSite')
|
: pageType === 'end'
|
? Taro.getStorageSync('endSite')
|
: null;
|
const filteredNearbyList = filterNearbyListByCurrentSite(nearbySiteList, currentSiteCache);
|
const recommendList = searchValue ? sortSitesByNameMatchAndDistance(siteList, searchValue) : filteredNearbyList;
|
const visibleHistory = showAllHistory ? historySite.slice(0, MAX_EXPANDED_COUNT) : historySite.slice(0, MAX_SHOW_COUNT);
|
const visibleRecommend = showAllRecommend ? recommendList.slice(0, MAX_EXPANDED_COUNT) : recommendList.slice(0, MAX_SHOW_COUNT);
|
const canShowSearchFallback = !searchValue || (!isSearching && lastCompletedKeyword === searchValue.trim());
|
|
// 使用数据截断展示,不再通过高度裁剪内容
|
|
return (
|
<View className='search-page'>
|
{/* 搜索区域 */}
|
<View className='search-card'>
|
<View className={`search-icon ${pageType == 'end' ? 'end-icon' : ''}`}></View>
|
<View className='search-input-box'>
|
<Input className='search-input' placeholder={pageType == 'start' ? '请输入上车点' : '请输入目的地'} value={searchValue}
|
onInput={(e) => changeSearchValue(e)}
|
onBlur={flushDebouncedSearch}
|
onConfirm={(e) => {
|
const keyword = e?.detail?.value || searchValue; // 确认搜索关键字,优先取输入框事件值
|
confirmSearch(keyword);
|
}}
|
confirmType='search'
|
autoFocus
|
/>
|
{
|
searchValue &&
|
<View onClick={clearSearchValue} className='cancel-icon-box'>
|
<Image className='cancel-icon' src={require('../../assets/clear-icon.png')} />
|
</View>
|
}
|
</View>
|
<Image onClick={toSelectSite} className='clear-icon' src={map}></Image>
|
</View>
|
{/* 历史记录 */}
|
{
|
(historySite.length > 0 && !searchValue) &&
|
<View className='history-card'>
|
<View className='card-header'>历史记录</View>
|
<View className='card-content list-content' id='history-card-content'>
|
{
|
renderSiteItems(visibleHistory, (item) => onChangeSelectSite(item), true)
|
}
|
</View>
|
{historySite.length > MAX_SHOW_COUNT && (
|
<View className='show-more' onClick={toggleHistoryShow}>
|
<Text className='more-text'>{showAllHistory ? '收起' : '展示更多'}</Text>
|
<Image className={`more-icon ${showAllHistory ? 'rotate-up' : ''}`} src={arrow}></Image>
|
</View>
|
)}
|
</View>
|
}
|
{/* 推荐站点 */}
|
{
|
recommendList.length > 0 &&
|
<View className='recommend-card'>
|
<View className='card-header'>{searchValue ? '推荐站点' : '附近站点'}</View>
|
<View className='card-content list-content' id='recommend-card-content'>
|
{
|
renderSiteItems(visibleRecommend, (item) => onChangeSelectSite(item), true)
|
}
|
</View>
|
{recommendList.length > MAX_SHOW_COUNT && (
|
<View className='show-more' onClick={toggleRecommendShow}>
|
<Text className='more-text'>{showAllRecommend ? '收起' : '展示更多'}</Text>
|
<Image className={`more-icon ${showAllRecommend ? 'rotate-up' : ''}`} src={arrow}></Image>
|
</View>
|
)}
|
</View>
|
}
|
{/* 非营运区域 */}
|
{
|
nonOperationalSite.length > 0 &&
|
<View className='nonOperational-card'>
|
<View className='card-header'>非运营区域</View>
|
<View className='card-content'>
|
{
|
renderSiteItems(nonOperationalSite, (item) => onChangeSelectSite(item, false), false)
|
}
|
</View>
|
</View>
|
}
|
{
|
isInitLoaded && canShowSearchFallback && (!siteList.length && (!searchValue ? !nearbySiteList.length : true) && !nonOperationalSite.length && (!searchValue ? !historySite.length : true)) &&
|
<View className='history-card'>
|
<View className='card-header'>暂无搜索结果,为您推荐附近站点</View>
|
<View className='card-content list-content'>
|
{
|
renderSiteItems(filteredNearbyList.slice(0, MAX_EXPANDED_COUNT), (item) => onChangeSelectSite(item), true)
|
}
|
</View>
|
</View>
|
}
|
<TipPopup
|
show={showTipPopup}
|
onClose={handleCloseTipPopup}
|
tipType={tipType}
|
isMap={false}
|
/>
|
</View>
|
)
|
}
|