From 13c960a2b6bb54797bdbd03aa3184af507d94a9b Mon Sep 17 00:00:00 2001
From: 卢春龙 <lucl@xiaopeng.com>
Date: 星期一, 20 四月 2026 10:42:28 +0800
Subject: [PATCH] Merge branch 'feature_pr_fix' into 'develop'
---
src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts | 10 +
src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts | 2
src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx | 4
src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx | 12 +
src/robotaxi/pages/site-search/index.tsx | 5
src/robotaxi/pages/begin-route/components/calling-car-modules/vehicle-info-card.tsx | 3
src/robotaxi/pages/begin-route/components/evaluate-popup.tsx | 22 +
src/robotaxi/pages/begin-route/components/release-route.tsx | 3
src/robotaxi/pages/begin-route/components/begin-route-view.tsx | 1
src/robotaxi/pages/begin-route/components/announcements-popup.tsx | 9
src/robotaxi/pages/begin-route/components/passenger-notice.tsx | 20 +-
src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts | 10 +
src/robotaxi/pages/site-search/hooks/use-site-search.ts | 111 ++++++++++---
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx | 6
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx | 28 ++-
src/robotaxi/pages/begin-route/components/over-route.tsx | 11
src/robotaxi/pages/begin-route/index.tsx | 72 ++++----
src/robotaxi/pages/begin-route/components/release-be-being.tsx | 5
src/robotaxi/pages/begin-route/components/calling-car.tsx | 2
src/robotaxi/pages/begin-route/components/calling-car-modules/evaluation-card.tsx | 11
src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts | 70 ++++++++
src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx | 7
src/robotaxi/pages/begin-route/components/cancel-popup.tsx | 7
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts | 13 +
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts | 8
25 files changed, 331 insertions(+), 121 deletions(-)
diff --git a/src/robotaxi/pages/begin-route/components/announcements-popup.tsx b/src/robotaxi/pages/begin-route/components/announcements-popup.tsx
index 0f40b8d..c4a87ca 100644
--- a/src/robotaxi/pages/begin-route/components/announcements-popup.tsx
+++ b/src/robotaxi/pages/begin-route/components/announcements-popup.tsx
@@ -9,14 +9,15 @@
}
export default function AnnouncementsPopup({ show = false, onClose }: Props) {
- const [visible, setVisible] = useState(false);
- const [animationData, setAnimationData] = useState<any>();
- const [doorImgError, setDoorImgError] = useState(false);
- const [seatbeltImgError, setSeatbeltImgError] = useState(false);
+ const [visible, setVisible] = useState(false); // 弹窗显示状态
+ const [animationData, setAnimationData] = useState<any>(); // 弹窗开合动画
+ const [doorImgError, setDoorImgError] = useState(false); // 开门动画图加载失败状态
+ const [seatbeltImgError, setSeatbeltImgError] = useState(false); // 系安全带动画图加载失败状态
const doorImage = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/door-anim.webp'
const seatbeltImage = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/seatbelt-anim.webp'
+ // 控制弹窗开合动画并重置图片兜底状态
useEffect(() => {
if (show && !visible) {
setVisible(true);
diff --git a/src/robotaxi/pages/begin-route/components/begin-route-view.tsx b/src/robotaxi/pages/begin-route/components/begin-route-view.tsx
index af2dd01..05765bb 100644
--- a/src/robotaxi/pages/begin-route/components/begin-route-view.tsx
+++ b/src/robotaxi/pages/begin-route/components/begin-route-view.tsx
@@ -115,6 +115,7 @@
handleConfirmEvaluate
} = props;
+ // 统一渲染地图与业务弹窗容器
return (
<View className='home-container'>
<Map
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx b/src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx
index 5168982..0fcba6e 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx
@@ -18,15 +18,17 @@
handleOpenOverRoutePopup,
handleOpenAnnouncementsPopup
}: Props) {
- const [trunkStatus, setTrunkStatus] = useState(orderTrunkStatus);
- const [lockStatus, setLockStatus] = useState(false);
- const trunkPollTimerRef = useRef<any>(null);
- const hasShownAnnouncementsRef = useRef(false);
+ const [trunkStatus, setTrunkStatus] = useState(orderTrunkStatus); // 后备箱开关状态
+ const [lockStatus, setLockStatus] = useState(false); // 车门解锁状态
+ const trunkPollTimerRef = useRef<any>(null); // 后备箱状态轮询定时器
+ const hasShownAnnouncementsRef = useRef(false); // 安全须知是否已展示
+ // 外部后备箱状态变化时同步本地状态
useEffect(() => {
setTrunkStatus(orderTrunkStatus);
}, [orderTrunkStatus]);
+ // 组件卸载时清理轮询定时器
useEffect(() => {
return () => {
if (trunkPollTimerRef.current) {
@@ -35,6 +37,7 @@
};
}, []);
+ // 获取解锁请求所需定位参数,失败时回退到缓存上车点
const getUnlockLocationPayload = async () => {
try {
Taro.showLoading({ title: '正在获取位置...', mask: true });
@@ -57,11 +60,17 @@
return {};
};
+ // 解锁车门/后备箱,后备箱场景会轮询确认结果
const handleUnlock = async (doorType: 'door' | 'trunk', onSuccess?: () => void) => {
const targetOrderId = orderId || Taro.getStorageSync('orderId');
if (!targetOrderId) return;
const locationPayload = await getUnlockLocationPayload();
+ const isCloseAction = doorType === 'trunk' ? !!trunkStatus : false;
+ const actionLoadingText = doorType === 'trunk'
+ ? (isCloseAction ? '正在关闭后备箱' : '正在开启后备箱')
+ : '正在解锁车门';
+ Taro.showLoading({ title: actionLoadingText, mask: true });
unlockVehicle({
orderId: targetOrderId,
doorType,
@@ -69,16 +78,13 @@
}).then((res: any) => {
console.log('解锁结果:', res);
- if (!res) {
- Taro.showToast({ title: '解锁失败,请重试或联系客服', icon: 'none' });
+ if (res?.code !== 200 || !res?.data) {
+ Taro.hideLoading();
+ Taro.showToast({ title: res?.msg || '解锁失败,请重试或联系客服', icon: 'none' });
return;
}
if (doorType === 'trunk') {
- const isCloseAction = !!trunkStatus;
- const loadingText = isCloseAction ? '正在关闭后备箱' : '正在开启后备箱';
- Taro.showLoading({ title: loadingText, mask: true });
-
let pollCount = 0;
const maxPolls = 5;
const pollInterval = 2000;
@@ -149,6 +155,7 @@
}
if (doorType === 'door') {
+ Taro.hideLoading();
Taro.showToast({ title: '右侧车门已解锁', icon: 'none' });
if (!hasShownAnnouncementsRef.current) {
handleOpenAnnouncementsPopup?.();
@@ -156,6 +163,7 @@
}
}
}).catch(() => {
+ Taro.hideLoading();
Taro.showToast({ title: '解锁失败,请重试或联系客服', icon: 'none' });
});
};
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/evaluation-card.tsx b/src/robotaxi/pages/begin-route/components/calling-car-modules/evaluation-card.tsx
index a5d3779..f87a94f 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/evaluation-card.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/evaluation-card.tsx
@@ -24,10 +24,11 @@
evaluateSubmitVersion,
evaluateCloseVersion
}: Props) {
- const [activeIndex, setActiveIndex] = useState(0);
- const [isEvaluated, setIsEvaluated] = useState(false);
- const skipEvaluateResetRef = useRef(false);
+ const [activeIndex, setActiveIndex] = useState(0); // 当前选中的满意度
+ const [isEvaluated, setIsEvaluated] = useState(false); // 是否已完成评价
+ const skipEvaluateResetRef = useRef(false); // 跳过关闭重置的一次性标记
+ // 外部提交成功后同步评价状态
useEffect(() => {
if (typeof evaluateSubmitVersion === 'number' && evaluateSubmitVersion > 0) {
skipEvaluateResetRef.current = true;
@@ -36,6 +37,7 @@
}
}, [evaluateSubmitVersion]);
+ // 关闭评价弹窗时重置高亮状态
useEffect(() => {
if (typeof evaluateCloseVersion === 'number' && evaluateCloseVersion > 0) {
if (skipEvaluateResetRef.current) {
@@ -46,6 +48,7 @@
}
}, [evaluateCloseVersion]);
+ // 点击评价项:不满意走弹窗,其余直接提交
const changeEvaluate = (index: number) => {
if (isEvaluated) return;
setActiveIndex(index);
@@ -95,4 +98,4 @@
</View>
</View>
);
-}
\ No newline at end of file
+}
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx b/src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx
index 80ebf0f..5625922 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx
@@ -20,20 +20,23 @@
onToggleDetailFade,
handlePay
}: Props) {
- const [showDetail, setShowDetail] = useState(false);
+ const [showDetail, setShowDetail] = useState(false); // 费用明细展开状态
+ // 外部重置版本变化时收起明细
useEffect(() => {
if (typeof resetDetailVersion === 'number') {
setShowDetail(false);
}
}, [resetDetailVersion]);
+ // 跳转费用说明页
const toMoneyDetail = () => {
Taro.navigateTo({
url: `/robotaxi/pages/expense-description/index?orderId=${orderId}`
});
};
+ // 切换明细面板并同步地图淡化状态
const handleToggleDetail = () => {
const next = !showDetail;
setShowDetail(next);
@@ -170,4 +173,4 @@
}
</View>
);
-}
\ No newline at end of file
+}
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx b/src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx
index 13c6abd..3db4f97 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx
@@ -9,13 +9,14 @@
};
export default function TemperatureControl({ tripStatus, orderId }: Props) {
- const [tempStatus, setTempStatus] = useState<number | null>();
- const tempTimerRef = useRef<any>(null);
- const lastConfirmedTempRef = useRef<number>();
+ const [tempStatus, setTempStatus] = useState<number | null>(); // 当前显示温度
+ const tempTimerRef = useRef<any>(null); // 温度设置防抖定时器
+ const lastConfirmedTempRef = useRef<number>(); // 最近一次设置成功温度
const TEMP_MIN = 22;
const TEMP_MAX = 28;
+ // 状态 4 时拉取车辆当前温度
useEffect(() => {
if (tripStatus === 4) {
const targetOrderId = orderId || Taro.getStorageSync('orderId');
@@ -31,6 +32,7 @@
}
}, [tripStatus, orderId]);
+ // 初始化确认温度并在卸载时清理定时器
useEffect(() => {
lastConfirmedTempRef.current = tempStatus as number;
return () => {
@@ -40,6 +42,7 @@
};
}, []);
+ // 防抖提交目标温度到后端
const scheduleTemperatureUpdate = (targetTemperature: number) => {
if (tempTimerRef.current) {
clearTimeout(tempTimerRef.current);
@@ -76,6 +79,7 @@
}, 5000);
};
+ // 处理温度增减并做边界校验
const handleTempChange = (delta: number) => {
setTempStatus((prev) => {
if (typeof prev !== 'number') {
@@ -120,4 +124,4 @@
</View>
</View>
);
-}
\ No newline at end of file
+}
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx b/src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx
index 2e95613..710c268 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx
@@ -27,6 +27,7 @@
limitMinutes,
handleOpenCancelPopup
}: Props) {
+ // 标题行文案映射
const titleMap = {
3: <>预计<View className="time">{waitMinutes}</View>分钟内有车应答</>,
4: <>车辆还有<View className="time">{durationDisplay.value}</View>{durationDisplay.unit}到达</>,
@@ -39,6 +40,7 @@
9: '支付成功'
}
+ // 说明文案映射
const descMap = {
3: { text: '正全力为您叫车中,请耐心等待...', className: 'calling-car-content-desc' },
4: limitMinutes ? { text: `此处限停${limitMinutes}分钟,请尽快前往上车点`, className: isArriveSoon ? 'calling-car-content-desc-red' : 'calling-car-content-warning' } : null,
@@ -66,4 +68,4 @@
}
</View>
);
-}
\ No newline at end of file
+}
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts b/src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts
index eec4cd5..a5844c0 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts
@@ -36,6 +36,7 @@
const pickupTimerRef = useRef<any>(null);
const finishTimerRef = useRef<any>(null);
const countdownFinishedRef = useRef(false);
+ const pickupTripStatusRef = useRef(tripStatus);
const pickupEndTimeRef = useRef<number | null>(null);
const finishEndTimeRef = useRef<number | null>(null);
const lastFareOrderIdRef = useRef<string | null>(null);
@@ -108,12 +109,21 @@
}, [tripStatus]);
useEffect(() => {
+ pickupTripStatusRef.current = tripStatus;
+ }, [tripStatus]);
+
+ useEffect(() => {
if (tripStatus !== 5) {
clearPickupTimer();
return;
}
const runPickupTick = () => {
+ // 防止状态已切换后,旧定时回调仍触发取消逻辑
+ if (pickupTripStatusRef.current !== 5) {
+ clearPickupTimer();
+ return;
+ }
const remaining = getRemainingSeconds(pickupEndTimeRef.current);
if (remaining !== null && remaining <= 0) {
if (!countdownFinishedRef.current) {
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/vehicle-info-card.tsx b/src/robotaxi/pages/begin-route/components/calling-car-modules/vehicle-info-card.tsx
index b58e100..43407b4 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/vehicle-info-card.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/vehicle-info-card.tsx
@@ -12,6 +12,7 @@
};
export default function VehicleInfoCard({ vehicleInfo }: Props) {
+ // 展示车辆基础信息与车辆图片
return (
<View className="car-card-detail">
<View>
@@ -31,4 +32,4 @@
}
</View>
);
-}
\ No newline at end of file
+}
diff --git a/src/robotaxi/pages/begin-route/components/calling-car.tsx b/src/robotaxi/pages/begin-route/components/calling-car.tsx
index 9f37838..adaa499 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -76,6 +76,7 @@
orderTrunkStatus,
}: Props) {
+ // 汇总行程中的倒计时与费用数据
const {
waitMinutes,
limitMinutes,
@@ -99,6 +100,7 @@
/** 上车倒计时即将结束(<60秒) */
const isPickupCountdownSoon = tripStatus === 5 && (typeof pickupCountdownSeconds === 'number' && pickupCountdownSeconds < 60);
+ // 按状态组合渲染行程卡片模块
return (
<View className='calling-car'>
<Image className="calling-car-bg" src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/site-top-bg.png' />
diff --git a/src/robotaxi/pages/begin-route/components/cancel-popup.tsx b/src/robotaxi/pages/begin-route/components/cancel-popup.tsx
index 0c03ed5..93387dd 100644
--- a/src/robotaxi/pages/begin-route/components/cancel-popup.tsx
+++ b/src/robotaxi/pages/begin-route/components/cancel-popup.tsx
@@ -22,8 +22,8 @@
}
export default function CancelPopup({ show = false, cancelType = 1, startSite, endSite, waitMinutes, tripStatus, routeDurationSeconds, discountAmount, onClose, onConfirm }: Props) {
- const [visible, setVisible] = useState(false);
- const [animationData, setAnimationData] = useState<any>();
+ const [visible, setVisible] = useState(false); // 弹窗显示状态
+ const [animationData, setAnimationData] = useState<any>(); // 弹窗开合动画
const cachedStartSite = startSite || Taro.getStorageSync('startSite') || {};
const cachedEndSite = endSite || Taro.getStorageSync('endSite') || {};
const startSiteName = cachedStartSite?.name || cachedStartSite?.address || '-';
@@ -32,6 +32,7 @@
const durationDisplay = formatDurationDisplay(routeDurationSeconds);
const discountAmountValue = typeof discountAmount === 'number' && !Number.isNaN(discountAmount) ? discountAmount : 0;
+ // 不同取消阶段对应的文案配置
const STATUS_MAP = {
1: {
title: `预计${waitMinutesValue}分钟内有车,确定取消吗?`,
@@ -61,6 +62,7 @@
},
}
+ // 控制弹窗开合动画
useEffect(() => {
if (show && !visible) {
setVisible(true);
@@ -84,6 +86,7 @@
}
}, [show]);
+ // 关闭弹窗时按类型执行对应流程
const closePopup = () => {
if (cancelType == 5) {
onConfirm?.();
diff --git a/src/robotaxi/pages/begin-route/components/evaluate-popup.tsx b/src/robotaxi/pages/begin-route/components/evaluate-popup.tsx
index c277d6d..98a96ab 100644
--- a/src/robotaxi/pages/begin-route/components/evaluate-popup.tsx
+++ b/src/robotaxi/pages/begin-route/components/evaluate-popup.tsx
@@ -15,18 +15,19 @@
}
export default function EvaluatePopup({ show = false, onClose, onConfirm, orderId, orderRating, submitMode = 'comment' }: Props) {
- const [visible, setVisible] = useState(false);
- const [animationData, setAnimationData] = useState<any>();
- const [selectedTypeId, setSelectedTypeId] = useState<number | null>(null);
- const [feedbackContent, setFeedbackContent] = useState('');
- const [typeOptions, setTypeOptions] = useState<any[]>([]);
- const [isSubmitting, setIsSubmitting] = useState(false);
+ const [visible, setVisible] = useState(false); // 弹窗显示状态
+ const [animationData, setAnimationData] = useState<any>(); // 弹窗进出场动画
+ const [selectedTypeId, setSelectedTypeId] = useState<number | null>(null); // 当前选择的问题类型
+ const [feedbackContent, setFeedbackContent] = useState(''); // 反馈文本内容
+ const [typeOptions, setTypeOptions] = useState<any[]>([]); // 问题类型选项
+ const [isSubmitting, setIsSubmitting] = useState(false); // 提交中状态
const [pictures, setPictures] = useState<Array<{
localUrl: string;
server?: { id: string; name: string; url: string } | null;
status: 'uploading' | 'success' | 'failed';
- }>>([]);
+ }>>([]); // 图片上传队列
+ // 控制弹窗开合动画
useEffect(() => {
if (show && !visible) {
setVisible(true);
@@ -50,6 +51,7 @@
}
}, [show]);
+ // 提交反馈(评价/投诉)
const submit = () => {
if (isSubmitting) {
return;
@@ -114,6 +116,7 @@
});
}
+ // 压缩图片到可上传大小
const ensureSizeUnderLimit = async (path: string): Promise<string> => {
try {
const fs = Taro.getFileSystemManager();
@@ -149,6 +152,7 @@
}
};
+ // 上传单张图片并写回状态
const uploadSingle = async (index: number, filePath: string) => {
setPictures(prev => {
const next = [...prev];
@@ -192,6 +196,7 @@
}
};
+ // 选择图片并触发上传
const handleChooseImages = async () => {
const remain = Math.max(0, 3 - pictures.length);
if (remain <= 0) {
@@ -218,12 +223,14 @@
}
};
+ // 重试上传失败图片
const handleRetry = async (index: number) => {
const item = pictures[index];
if (!item) return;
uploadSingle(index, item.localUrl);
};
+ // 删除已选图片
const handleDelete = (index: number) => {
setPictures(prev => {
const next = [...prev];
@@ -232,6 +239,7 @@
});
};
+ // 拉取问题类型并重置本次填写内容
useEffect(() => {
if (!show) {
return;
diff --git a/src/robotaxi/pages/begin-route/components/over-route.tsx b/src/robotaxi/pages/begin-route/components/over-route.tsx
index fb94ebc..8de4105 100644
--- a/src/robotaxi/pages/begin-route/components/over-route.tsx
+++ b/src/robotaxi/pages/begin-route/components/over-route.tsx
@@ -14,11 +14,12 @@
}
export default function OverRoute({ show = false, onClose, onConfirm }: Props) {
- const [visible, setVisible] = useState(false);
- const [animationData, setAnimationData] = useState<any>();
- const [finishMinutes, setFinishMinutes] = useState<number | null>(null);
- const [isSubmitting, setIsSubmitting] = useState(false);
+ const [visible, setVisible] = useState(false); // 弹窗显示状态
+ const [animationData, setAnimationData] = useState<any>(); // 弹窗动画数据
+ const [finishMinutes, setFinishMinutes] = useState<number | null>(null); // 自动驶离倒计时(分钟)
+ const [isSubmitting, setIsSubmitting] = useState(false); // 结束行程提交状态
+ // 确认结束行程并调用结束接口
const handleConfirm = () => {
if (isSubmitting) {
return;
@@ -51,6 +52,7 @@
});
};
+ // 控制弹窗关闭动画
useEffect(() => {
if (!show && visible) {
const animation = Taro.createAnimation({
@@ -66,6 +68,7 @@
}
}, [show]);
+ // 打开弹窗时先拉取结束后离车时间
useEffect(() => {
if (!show) {
return;
diff --git a/src/robotaxi/pages/begin-route/components/passenger-notice.tsx b/src/robotaxi/pages/begin-route/components/passenger-notice.tsx
index 30a1935..521b2d5 100644
--- a/src/robotaxi/pages/begin-route/components/passenger-notice.tsx
+++ b/src/robotaxi/pages/begin-route/components/passenger-notice.tsx
@@ -11,15 +11,16 @@
}
export default function PassengerNotice({ show = false, onClose, onCheckedChange }: Props) {
- const [visible, setVisible] = useState(false);
- const [animationData, setAnimationData] = useState<any>();
- const [checked, setChecked] = useState(true);
- const [noticeContent, setNoticeContent] = useState('');
- const [thumbHeight, setThumbHeight] = useState(0);
- const [thumbTop, setThumbTop] = useState(0);
- const [containerHeight, setContainerHeight] = useState(0);
- const [showScrollbar, setShowScrollbar] = useState(false);
+ const [visible, setVisible] = useState(false); // 弹窗显示状态
+ const [animationData, setAnimationData] = useState<any>(); // 弹窗动画数据
+ const [checked, setChecked] = useState(true); // 近三个月不再显示勾选状态
+ const [noticeContent, setNoticeContent] = useState(''); // 须知富文本内容
+ const [thumbHeight, setThumbHeight] = useState(0); // 自定义滚动条高度
+ const [thumbTop, setThumbTop] = useState(0); // 自定义滚动条位置
+ const [containerHeight, setContainerHeight] = useState(0); // 文本容器高度
+ const [showScrollbar, setShowScrollbar] = useState(false); // 是否展示自定义滚动条
+ // 内容滚动时同步自定义滚动条位置
const onScroll = (e) => {
const { scrollHeight, scrollTop } = e.detail;
if (containerHeight > 0 && scrollHeight > containerHeight) {
@@ -28,6 +29,7 @@
}
};
+ // 文案渲染完成后计算滚动条尺寸
useEffect(() => {
if (noticeContent && visible) {
// Give it a little time to render
@@ -54,6 +56,7 @@
}
}, [noticeContent, visible]);
+ // 控制弹窗开合动画
useEffect(() => {
if (show && !visible) {
setVisible(true);
@@ -77,6 +80,7 @@
}
}, [show]);
+ // 每次打开时拉取最新须知内容
useEffect(() => {
if (!show) {
return;
diff --git a/src/robotaxi/pages/begin-route/components/release-be-being.tsx b/src/robotaxi/pages/begin-route/components/release-be-being.tsx
index 2f35ca7..b7a5604 100644
--- a/src/robotaxi/pages/begin-route/components/release-be-being.tsx
+++ b/src/robotaxi/pages/begin-route/components/release-be-being.tsx
@@ -9,13 +9,14 @@
}
export default function ReleaseBeBeing({ handleOpenCancelPopup, onFinish }: Props) {
- const [progress, setProgress] = useState(0);
- const [completedCount, setCompletedCount] = useState(0);
+ const [progress, setProgress] = useState(0); // 发布进度条
+ const [completedCount, setCompletedCount] = useState(0); // 已完成步骤数
const startSite = Taro.getStorageSync('startSite') || {};
const endSite = Taro.getStorageSync('endSite') || {};
const startSiteName = startSite?.name || startSite?.address || '-';
const endSiteName = endSite?.name || endSite?.address || '-';
+ // 模拟发布流程进度和状态推进
useEffect(() => {
// 使用 setTimeout 触发 CSS transition 动画
const timer = setTimeout(() => {
diff --git a/src/robotaxi/pages/begin-route/components/release-route.tsx b/src/robotaxi/pages/begin-route/components/release-route.tsx
index 546ae34..7fe0581 100644
--- a/src/robotaxi/pages/begin-route/components/release-route.tsx
+++ b/src/robotaxi/pages/begin-route/components/release-route.tsx
@@ -17,16 +17,19 @@
export default function ReleaseRoute({ startSite, endSite, callImmediately, fareEstimate = {}, handleOpenPassengerNotice, walkingTime = 0, isCallDisabled = false }: Props) {
+ // 跳转选点页(起点/终点)
const toSeleteSitePage = (type: string) => {
Taro.navigateTo({
url: `/robotaxi/pages/select-map-site/index?pageType=${type}&skipSource=1`,
})
}
+ // 跳转费用说明页
const toPriceDescription = () => {
Taro.navigateTo({
url: `/robotaxi/pages/expense-detail/index`,
})
}
+ // 格式化步行时长文案
const formatWalkingTime = (minutes: number) => {
const safeMinutes = Math.max(0, Math.floor(Number(minutes) || 0));
if (safeMinutes >= 60) {
diff --git a/src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts b/src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts
index c5f18c6..c880af5 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts
@@ -5,6 +5,7 @@
shouldShowWaitingRadar: boolean;
isAndroid: boolean;
tripStatus: number;
+ mapScale: number;
startSite: any;
endSite: any;
vehicleLocation: { latitude: number; longitude: number } | null;
@@ -151,6 +152,15 @@
updateArriveCalloutPosition();
}, [deps.isAndroid, deps.tripStatus, deps.endSite?.latitude, deps.endSite?.longitude]);
+ useEffect(() => {
+ if (!deps.isAndroid || deps.tripStatus !== 7) return;
+ if (deps.mapScale < 20) return;
+ const timer = setTimeout(() => {
+ updateArriveCalloutPosition();
+ }, 80);
+ return () => clearTimeout(timer);
+ }, [deps.isAndroid, deps.tripStatus, deps.mapScale, deps.endSite?.latitude, deps.endSite?.longitude]);
+
return {
waitingRadarStyle,
waitingRadarVisible,
diff --git a/src/robotaxi/pages/begin-route/hooks/use-map-markers.ts b/src/robotaxi/pages/begin-route/hooks/use-map-markers.ts
index a6bc18e..ff7e315 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-map-markers.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-map-markers.ts
@@ -10,6 +10,8 @@
vehicleLocation: { latitude: number; longitude: number } | null;
vehicleDirection: number | null;
vehicleMarkerId: number;
+ arrivedMarkerReady: boolean;
+ arrivedTooltipReady: boolean;
};
const defaultDeps: Deps = {
@@ -20,7 +22,9 @@
endSite: null,
vehicleLocation: null,
vehicleDirection: null,
- vehicleMarkerId: 3
+ vehicleMarkerId: 3,
+ arrivedMarkerReady: false,
+ arrivedTooltipReady: false
};
export default function useMapMarkers(rawDeps?: Partial<Deps>) {
@@ -137,10 +141,12 @@
const useAndroidNativeTrip1Callout = deps.isAndroid && [1, 5, 8, 9, 10].includes(deps.tripStatus);
const pickupMarker = createPickupMarker(pickupSite, useAndroidNativeTrip1Callout);
const dropoffMarker = createDropoffMarker(dropoffSite, useAndroidNativeTrip1Callout);
+ const shouldWaitArrivedDirection = deps.tripStatus === 7 && !deps.arrivedMarkerReady;
+ const effectiveShowCar = deps.statusRule.showCar && !shouldWaitArrivedDirection;
const carMarker = createCarMarker();
const nextMarkers: any[] = [];
if (deps.statusRule.showPickup && pickupMarker) nextMarkers.push(pickupMarker);
- if (deps.statusRule.showCar && carMarker) nextMarkers.push(carMarker);
+ if (effectiveShowCar && carMarker) nextMarkers.push(carMarker);
if (deps.statusRule.showDropoff && dropoffMarker) nextMarkers.push(dropoffMarker);
const nextMarkersSignature = nextMarkers
.map((m: any) => [
@@ -154,8 +160,7 @@
.join(';');
const hasDropoffMarker = markersRef.current.some((m: any) => m.id === 2);
const shouldHoldMarkerByTranslate = [4, 6].includes(deps.tripStatus) && carMarkerInitialized.current && deps.statusRule.showCar && (!deps.statusRule.showDropoff || hasDropoffMarker);
- const shouldHoldMarkerWhenArrived = deps.tripStatus === 7 && carMarkerInitialized.current && deps.statusRule.showCar && (!deps.statusRule.showDropoff || hasDropoffMarker);
- const shouldSkipUpdate = (shouldHoldMarkerByTranslate && (deps.isAndroid || tripStatus6MarkerRefreshedRef.current)) || shouldHoldMarkerWhenArrived;
+ const shouldSkipUpdate = shouldHoldMarkerByTranslate && (deps.isAndroid || tripStatus6MarkerRefreshedRef.current);
applyMarkerState(nextMarkers, nextMarkersSignature, shouldSkipUpdate, carMarker);
}, [deps.tripStatus, deps.startSite?.name, deps.startSite?.latitude, deps.startSite?.longitude, deps.endSite?.name, deps.endSite?.latitude, deps.endSite?.longitude, deps.vehicleLocation, deps.vehicleDirection, deps.vehicleMarkerId, deps.isAndroid, deps.statusRule.showPickup, deps.statusRule.showCar, deps.statusRule.showDropoff]);
diff --git a/src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts b/src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts
index 315d301..b278b54 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts
@@ -49,7 +49,10 @@
const mapRectRef = useRef<any>(null);
const viewportKeyRef = useRef<string>('');
+ const orderDetailViewportLockedRef = useRef(false);
const tripStatus6ViewportLockedRef = useRef(false);
+ const orderDetailEnterSignatureRef = useRef('');
+ const orderDetailRouteUpdatedAfterEnterRef = useRef(false);
const isAndroid = useMemo(() => {
try {
@@ -183,6 +186,22 @@
return { points, keyParts };
};
+ const buildOrderDetailRouteSignature = (pickupSite: any, dropoffSite: any) => {
+ const firstPoint = deps.routePoints[0];
+ const lastPoint = deps.routePoints[deps.routePoints.length - 1];
+ return [
+ deps.routePoints.length,
+ firstPoint?.latitude ?? '',
+ firstPoint?.longitude ?? '',
+ lastPoint?.latitude ?? '',
+ lastPoint?.longitude ?? '',
+ pickupSite?.latitude ?? '',
+ pickupSite?.longitude ?? '',
+ dropoffSite?.latitude ?? '',
+ dropoffSite?.longitude ?? '',
+ ].join('|');
+ };
+
const updateViewportByPayload = (viewportType: string, points: any[]) => {
const mapCtx = Taro.createMapContext('map');
const bottomPadding = computeBottomPadding(points, viewportType);
@@ -263,10 +282,44 @@
const { points, keyParts } = buildViewportPayload(viewportType, pickupSite, dropoffSite);
if (points.length === 0) return;
const nextKey = keyParts.join('|');
- if (nextKey === viewportKeyRef.current) return;
+ if (nextKey === viewportKeyRef.current) {
+ if (mapScale < DROPOFF_MAX_SCALE) {
+ setMapScale(DROPOFF_MAX_SCALE);
+ }
+ return;
+ }
viewportKeyRef.current = nextKey;
updateViewportByPayload(viewportType, points);
- }, [deps.tripStatus, deps.statusRule?.viewport, deps.endSite?.latitude, deps.endSite?.longitude]);
+ }, [deps.tripStatus, deps.statusRule?.viewport, deps.endSite?.latitude, deps.endSite?.longitude, mapScale]);
+
+ useEffect(() => {
+ if (![8, 9].includes(deps.tripStatus)) {
+ orderDetailViewportLockedRef.current = false;
+ orderDetailRouteUpdatedAfterEnterRef.current = false;
+ orderDetailEnterSignatureRef.current = '';
+ return;
+ }
+ const { pickupSite, dropoffSite } = resolveViewportSites();
+ orderDetailEnterSignatureRef.current = buildOrderDetailRouteSignature(pickupSite, dropoffSite);
+ orderDetailRouteUpdatedAfterEnterRef.current = false;
+ orderDetailViewportLockedRef.current = false;
+ viewportKeyRef.current = '';
+ setMapScale(14);
+ }, [deps.tripStatus]);
+
+ useEffect(() => {
+ if (![8, 9].includes(deps.tripStatus)) return;
+ const { pickupSite, dropoffSite } = resolveViewportSites();
+ const currentSignature = buildOrderDetailRouteSignature(pickupSite, dropoffSite);
+ if (!orderDetailEnterSignatureRef.current) {
+ orderDetailEnterSignatureRef.current = currentSignature;
+ return;
+ }
+ if (currentSignature === orderDetailEnterSignatureRef.current) return;
+ orderDetailRouteUpdatedAfterEnterRef.current = true;
+ orderDetailViewportLockedRef.current = false;
+ viewportKeyRef.current = '';
+ }, [deps.tripStatus, deps.routePoints, deps.startSite?.latitude, deps.startSite?.longitude, deps.endSite?.latitude, deps.endSite?.longitude]);
useEffect(() => {
const { pickupSite, dropoffSite } = resolveViewportSites();
@@ -274,6 +327,14 @@
if (viewportType === 'dropoff') return;
const { points, keyParts } = buildViewportPayload(viewportType, pickupSite, dropoffSite);
if (points.length === 0) return;
+ if (
+ [8, 9].includes(deps.tripStatus) &&
+ viewportType === 'order_detail' &&
+ orderDetailViewportLockedRef.current &&
+ orderDetailRouteUpdatedAfterEnterRef.current
+ ) {
+ return;
+ }
if (deps.tripStatus === 6 && viewportType === 'route' && tripStatus6ViewportLockedRef.current) return;
const nextKey = keyParts.join('|');
if (nextKey === viewportKeyRef.current) return;
@@ -281,6 +342,10 @@
updateViewportByPayload(viewportType, points);
if (deps.tripStatus === 6 && viewportType === 'route') {
tripStatus6ViewportLockedRef.current = true;
+ }
+ if ([8, 9].includes(deps.tripStatus) && viewportType === 'order_detail') {
+ const hasOrderDetailRouteAfterEnter = orderDetailRouteUpdatedAfterEnterRef.current && deps.routePoints.length > 0;
+ orderDetailViewportLockedRef.current = hasOrderDetailRouteAfterEnter;
}
}, [deps.tripStatus, deps.statusRule, deps.routePoints, deps.walkingPoints, deps.startSite?.latitude, deps.startSite?.longitude, deps.endSite?.latitude, deps.endSite?.longitude, deps.vehicleLocation, deps.longitude, deps.latitude, mapBottomPadding]);
@@ -309,6 +374,7 @@
setShowLocationFlag,
mapRectRef,
viewportKeyRef,
+ orderDetailViewportLockedRef,
tripStatus6ViewportLockedRef,
isAndroid,
resolveSafeBottomInset,
diff --git a/src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts b/src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts
index 7095372..8285eed 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts
@@ -231,7 +231,7 @@
useEffect(() => {
const wasHold = [4, 6].includes(deps.lastTripStatusRef.current);
const isHold = [4, 6].includes(deps.tripStatus);
- if (deps.tripStatus != 4) Taro.removeStorageSync('pickupEndTime');
+ if (deps.tripStatus != 5) Taro.removeStorageSync('pickupEndTime');
if (deps.tripStatus != 7) Taro.removeStorageSync('finishEndTime');
if (!wasHold && isHold) {
deps.orderRouteInitializedRef.current = false;
diff --git a/src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx b/src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
index 4295305..94b5350 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
+++ b/src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
@@ -30,6 +30,8 @@
arriveCalloutVisible: boolean;
/** 到达气泡样式 */
arriveCalloutStyle: any;
+ /** 到达态气泡渲染就绪(角度与缩放均就绪) */
+ arrivedTooltipReady: boolean;
/** 路线距离 */
routeDistance: { value: number; unit: 'km' | 'm' } | null;
/** 路线时长 */
@@ -130,7 +132,7 @@
</CoverView>
</CoverView>
)}
- {(item.id === 2 && [7].includes(deps.tripStatus) && !deps.isAndroid && deps.mapScale >= 20) && (
+ {(item.id === 2 && [7].includes(deps.tripStatus) && !deps.isAndroid && deps.arrivedTooltipReady) && (
<CoverView className='custom-callout-content'>
已到达
</CoverView>
@@ -170,7 +172,7 @@
</View>
</View>
)}
- {deps.isAndroid && deps.arriveCalloutVisible && (
+ {deps.isAndroid && deps.arriveCalloutVisible && deps.arrivedTooltipReady && (
<View className="car-callout-android" style={deps.arriveCalloutStyle}>
<View className="custom-callout-content">
已到达
diff --git a/src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts b/src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
index b73181e..384199c 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
@@ -202,12 +202,10 @@
const currentTripStatus = Number(deps.tripStatusRef.current);
if (currentTripStatus === 7) {
const lastPoint = deps.lastVehiclePointRef.current;
- const lastUpdateAt = Number(deps.lastVehicleUpdateAtRef.current || 0);
- const now = Date.now();
const latDiff = Math.abs(Number(lastPoint?.latitude || 0) - targetPoint.latitude);
const lngDiff = Math.abs(Number(lastPoint?.longitude || 0) - targetPoint.longitude);
const almostSamePoint = latDiff < 0.000003 && lngDiff < 0.000003;
- if (almostSamePoint || (now - lastUpdateAt < 1200)) {
+ if (almostSamePoint) {
return;
}
}
@@ -268,6 +266,10 @@
const forceFullRedraw = payload?.forceFullRedraw === true;
const currentTripStatus = Number(deps.tripStatusRef.current);
updateRouteSummary(payload, deps);
+ // 行程结束态仅更新概要信息,不再消费路线点,避免视口反复重算造成跳动
+ if ([8, 9].includes(currentTripStatus)) {
+ return;
+ }
if ([4, 6].includes(currentTripStatus)) {
deps.setVehicleMoveAlongPoints(servedPoints);
}
diff --git a/src/robotaxi/pages/begin-route/index.tsx b/src/robotaxi/pages/begin-route/index.tsx
index 88447bc..95989f3 100644
--- a/src/robotaxi/pages/begin-route/index.tsx
+++ b/src/robotaxi/pages/begin-route/index.tsx
@@ -110,7 +110,7 @@
4: { showPickup: true, showDropoff: false, showCar: true, showRoute: true, showWalking: false, viewport: 'route', translateCar: true },
5: { showPickup: true, showDropoff: false, showCar: true, showRoute: false, showWalking: false, viewport: 'car_user', translateCar: true },
6: { showPickup: false, showDropoff: true, showCar: true, showRoute: true, showWalking: false, viewport: 'route', translateCar: true },
- 7: { showPickup: false, showDropoff: true, showCar: true, showRoute: false, showWalking: false, viewport: 'dropoff', translateCar: false },
+ 7: { showPickup: false, showDropoff: true, showCar: true, showRoute: false, showWalking: false, viewport: 'car_user', translateCar: true },
8: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: false, viewport: 'order_detail', translateCar: false },
9: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: false, viewport: 'order_detail', translateCar: false },
10: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
@@ -144,38 +144,6 @@
lastVehiclePointRef,
lastVehicleUpdateAtRef
} = vehicleState; // 车辆状态解构
- const markerState = useMapMarkers({
- tripStatus,
- statusRule,
- isAndroid,
- startSite,
- endSite,
- vehicleLocation,
- vehicleDirection,
- vehicleMarkerId
- }); // 地图标记状态容器
- const {
- markers,
- tripStatus6MarkerRefreshedRef
- } = markerState; // 标记状态解构
- const {
- waitingRadarStyle,
- waitingRadarVisible,
- carCalloutStyle,
- carCalloutVisible,
- arriveCalloutStyle,
- arriveCalloutVisible,
- handleRegionChange: handleAndroidOverlayRegionChange
- } = useAndroidOverlays({
- shouldShowWaitingRadar,
- isAndroid,
- tripStatus,
- startSite,
- endSite,
- vehicleLocation,
- longitude,
- latitude
- }); // Android 覆盖物状态编排
const routeState = useRouteManager({
isAndroid,
tripStatus,
@@ -239,6 +207,43 @@
toOrigin,
getLocationInfo
} = viewportState; // 视口状态解构
+ const arrivedMarkerReady = tripStatus !== 7 || typeof vehicleDirection === 'number';
+ const arrivedTooltipReady = tripStatus !== 7 || (arrivedMarkerReady && mapScale >= 20);
+ const markerState = useMapMarkers({
+ tripStatus,
+ statusRule,
+ isAndroid,
+ startSite,
+ endSite,
+ vehicleLocation,
+ vehicleDirection,
+ vehicleMarkerId,
+ arrivedMarkerReady,
+ arrivedTooltipReady
+ }); // 地图标记状态容器
+ const {
+ markers,
+ tripStatus6MarkerRefreshedRef
+ } = markerState; // 标记状态解构
+ const {
+ waitingRadarStyle,
+ waitingRadarVisible,
+ carCalloutStyle,
+ carCalloutVisible,
+ arriveCalloutStyle,
+ arriveCalloutVisible,
+ handleRegionChange: handleAndroidOverlayRegionChange
+ } = useAndroidOverlays({
+ shouldShowWaitingRadar,
+ isAndroid,
+ tripStatus,
+ mapScale,
+ startSite,
+ endSite,
+ vehicleLocation,
+ longitude,
+ latitude
+ }); // Android 覆盖物状态编排
const isCheckedEnter = useRef(false); // 是否已检查页面进入状态
const { checkDistanceAndCityReady } = useOrderDistanceConfig({
endSite,
@@ -458,6 +463,7 @@
carCalloutStyle,
arriveCalloutVisible,
arriveCalloutStyle,
+ arrivedTooltipReady,
routeDistance,
routeDuration,
customCalloutIconSrc,
diff --git a/src/robotaxi/pages/site-search/hooks/use-site-search.ts b/src/robotaxi/pages/site-search/hooks/use-site-search.ts
index df70be5..8f8b302 100644
--- a/src/robotaxi/pages/site-search/hooks/use-site-search.ts
+++ b/src/robotaxi/pages/site-search/hooks/use-site-search.ts
@@ -1,7 +1,6 @@
import Taro from '@tarojs/taro';
import { useEffect, useMemo, useRef, useState } from 'react';
import { getStartPoints, getEndPoints, queryPointByName } from '../../../api/site-search';
-import { debounce } from '../../../../utils/Robotaxi-public-method';
import { checkOtp } from '../../../../utils/tools';
import { getNearestSite } from '../../../api/home';
import { _AMAP_KEY } from '../../../../constants/api';
@@ -30,6 +29,12 @@
const [nearbySiteList, setNearbySiteList] = useState<any[]>([]);
// 页面类型(start/end)
const [pageType, setPageType] = useState('');
+ // 当前关键词是否仍在搜索中(含高德补充结果)
+ const [isSearching, setIsSearching] = useState(false);
+ // 最近一次完成搜索的关键词
+ const [lastCompletedKeyword, setLastCompletedKeyword] = useState('');
+ const searchSeqRef = useRef(0);
+ const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// 拉取附近站点
const fetchNearbySites = async (lat: number, lon: number, electronicAreaId?: string) => {
@@ -50,9 +55,17 @@
// 站点搜索请求与结果处理
const searchSite = async (val?: string) => {
- setNonOperationalSite([]);
const queryVal = val !== undefined ? val : deps.searchValue;
- if (!queryVal) return;
+ const normalizedQuery = String(queryVal || '').trim();
+ searchSeqRef.current += 1;
+ const requestSeq = searchSeqRef.current;
+ setNonOperationalSite([]);
+ if (!normalizedQuery) {
+ setIsSearching(false);
+ setLastCompletedKeyword('');
+ return;
+ }
+ setIsSearching(true);
const { latitude, longitude, electronicAreaId } = deps.router.params;
const selectedCity = Taro.getStorageSync('selectedCity');
const payload = {
@@ -60,42 +73,63 @@
electronicAreaId: electronicAreaId ? Number(electronicAreaId) : 0,
lat: Number(latitude),
lon: Number(longitude),
- pointPoi: queryVal
+ pointPoi: normalizedQuery
};
try {
const res = await queryPointByName(payload);
+ if (requestSeq !== searchSeqRef.current) return;
const { availablePoints } = res;
if (Array.isArray(availablePoints)) {
const list = availablePoints.map(normalizeSiteItem);
- const sorted = sortSitesByNameMatchAndDistance(list, queryVal);
+ const sorted = sortSitesByNameMatchAndDistance(list, normalizedQuery);
setSiteList(sorted.slice(0, deps.maxExpandedCount));
} else {
setSiteList([]);
}
const myAmapFun = new amapFile.AMapWX({ key: _AMAP_KEY });
- myAmapFun.getPoiAround({
- querykeywords: queryVal,
- location: `${longitude},${latitude}`,
- success: (data: any) => {
- if (data && data.poisData) {
- const list = data.poisData.map((item: any) => {
- const locationArr = item.location.split(',');
- return {
- ...item,
- longitude: Number(locationArr[0]),
- latitude: Number(locationArr[1]),
- ...normalizeSiteItem(item)
- };
- });
- const sorted = sortSitesByNameMatchAndDistance(list, queryVal);
- setNonOperationalSite(sorted.slice(0, deps.maxShowCount));
+ await new Promise<void>((resolve) => {
+ myAmapFun.getPoiAround({
+ querykeywords: normalizedQuery,
+ location: `${longitude},${latitude}`,
+ success: (data: any) => {
+ if (requestSeq !== searchSeqRef.current) {
+ resolve();
+ return;
+ }
+ if (data && data.poisData) {
+ const list = data.poisData.map((item: any) => {
+ const locationArr = item.location.split(',');
+ return {
+ ...item,
+ longitude: Number(locationArr[0]),
+ latitude: Number(locationArr[1]),
+ ...normalizeSiteItem(item)
+ };
+ });
+ const sorted = sortSitesByNameMatchAndDistance(list, normalizedQuery);
+ setNonOperationalSite(sorted.slice(0, deps.maxShowCount));
+ } else {
+ setNonOperationalSite([]);
+ }
+ resolve();
+ },
+ fail: () => {
+ if (requestSeq === searchSeqRef.current) {
+ setNonOperationalSite([]);
+ }
+ resolve();
}
- },
- fail: () => { }
+ });
});
} catch {
+ if (requestSeq !== searchSeqRef.current) return;
setSiteList([]);
setNonOperationalSite([]);
+ } finally {
+ if (requestSeq === searchSeqRef.current) {
+ setIsSearching(false);
+ setLastCompletedKeyword(normalizedQuery);
+ }
}
};
@@ -103,15 +137,38 @@
searchSiteRef.current = searchSite;
// 搜索输入防抖
- const debouncedSearch = useMemo(() => debounce((val) => {
- searchSiteRef.current(val);
- }, 300), []);
+ const debouncedSearch = useMemo(() => {
+ return (val: string) => {
+ if (debounceTimerRef.current) {
+ clearTimeout(debounceTimerRef.current);
+ }
+ debounceTimerRef.current = setTimeout(() => {
+ searchSiteRef.current(val);
+ }, 300);
+ };
+ }, []);
// 清空搜索结果列表
const clearSearchResults = () => {
+ if (debounceTimerRef.current) {
+ clearTimeout(debounceTimerRef.current);
+ debounceTimerRef.current = null;
+ }
+ // 推进请求序号,使清空前已发出的请求结果失效
+ searchSeqRef.current += 1;
setSiteList([]);
setNonOperationalSite([]);
+ setIsSearching(false);
+ setLastCompletedKeyword('');
};
+
+ useEffect(() => {
+ return () => {
+ if (debounceTimerRef.current) {
+ clearTimeout(debounceTimerRef.current);
+ }
+ };
+ }, []);
// 初始化:拉取附近站点、历史记录、页面类型
useEffect(() => {
@@ -145,6 +202,8 @@
historySite,
nonOperationalSite,
nearbySiteList,
+ isSearching,
+ lastCompletedKeyword,
searchSite,
debouncedSearch,
clearSearchResults
diff --git a/src/robotaxi/pages/site-search/index.tsx b/src/robotaxi/pages/site-search/index.tsx
index 795c5a4..58507ca 100644
--- a/src/robotaxi/pages/site-search/index.tsx
+++ b/src/robotaxi/pages/site-search/index.tsx
@@ -37,6 +37,8 @@
historySite,
nonOperationalSite,
nearbySiteList,
+ isSearching,
+ lastCompletedKeyword,
searchSite,
debouncedSearch,
clearSearchResults
@@ -224,6 +226,7 @@
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());
// 使用数据截断展示,不再通过高度裁剪内容
@@ -297,7 +300,7 @@
</View>
}
{
- (!siteList.length && (!searchValue ? !nearbySiteList.length : true) && !nonOperationalSite.length && (!searchValue ? !historySite.length : true)) &&
+ 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'>
--
Gitblit v1.7.1