卢春龙
2026-04-22 be89bb996f63b74df9e3ef010a92d4a7dc34b60e
Merge branch 'feature_search_fix' into 'develop'

fix(站点搜索): 修复初始化加载期间显示错误推荐的问题

See merge request robotaxi1/xp-robotaxi-miniprogram!15
5个文件已修改
163 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/hooks/use-order-actions.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/site-search/hooks/use-site-search.ts 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/site-search/index.tsx 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-order-actions.ts
@@ -25,6 +25,8 @@
  setTripStatus: (value: number) => void;
  /** 订单设置 */
  setOrderId: (value: string) => void;
  /** 车辆信息设置 */
  setVehicleInfo: (value: any) => void;
  /** 下单提交设置 */
  setCallSubmitting: (value: boolean) => void;
  /** 取消类型设置 */
@@ -173,6 +175,7 @@
   */
  const clearActiveOrderRuntime = () => {
    deps.setOrderId('');
    deps.setVehicleInfo({});
    clearOrderTripStorage();
    Taro.removeStorageSync('pickupEndTime');
    Taro.removeStorageSync('waitingTimerStartAt');
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
@@ -42,7 +42,8 @@
    payload?.content?.orderId || payload?.orderId || fallbackOrderId || Taro.getStorageSync('orderId');
const isSameOrder = (incomingOrderId: string, currentOrderId: string) => {
    if (!incomingOrderId || !currentOrderId) return true;
    if (!incomingOrderId && !currentOrderId) return true;
    if (!incomingOrderId || !currentOrderId) return false;
    return String(incomingOrderId) === String(currentOrderId);
};
@@ -86,20 +87,21 @@
    lastFetchedVehicleOrderIdRef: any
) => {
    if (!effectiveOrderId) return;
    const vehicleInfoEmpty =
        !deps.vehicleInfo ||
        (!deps.vehicleInfo.plateNumber && !deps.vehicleInfo.vehicleCode && !deps.vehicleInfo.vehicleColor && !deps.vehicleInfo.vehiclePictures);
    const shouldFetchVehicle =
        lastFetchedVehicleOrderIdRef.current !== effectiveOrderId &&
        (vehicleInfoEmpty || deps.orderId !== effectiveOrderId);
    const shouldFetchVehicle = lastFetchedVehicleOrderIdRef.current !== effectiveOrderId;
    if (deps.orderId !== effectiveOrderId) {
        deps.setOrderId(effectiveOrderId);
        Taro.setStorageSync('orderId', effectiveOrderId);
    }
    if (!shouldFetchVehicle) return;
    lastFetchedVehicleOrderIdRef.current = effectiveOrderId;
    const requestedOrderId = String(effectiveOrderId);
    queryVehicleByOrderId({ orderId: effectiveOrderId })
        .then((res: any) => {
            const latestOrderId = String(Taro.getStorageSync('orderId') || '');
            if (latestOrderId !== requestedOrderId) {
                return;
            }
            deps.setVehicleInfo(res);
        })
        .catch(() => { });
@@ -109,6 +111,7 @@
    if (!payload?.showCancelPopup) return;
    if (changeStatusOld === 4 || changeStatusOld === 5) {
        deps.setOrderId('');
        deps.setVehicleInfo({});
        clearOrderTripStorage();
        Taro.removeStorageSync('pickupEndTime');
        Taro.removeStorageSync('waitingTimerStartAt');
@@ -346,8 +349,13 @@
                return;
            }
            lastFetchedVehicleOrderIdRef.current = null;
            const requestedOrderId = String(currentOrderId || '');
            queryVehicleByOrderId({ orderId: currentOrderId })
                .then((res: any) => {
                    const latestOrderId = String((depsRef.current.orderId || Taro.getStorageSync('orderId') || ''));
                    if (!requestedOrderId || latestOrderId !== requestedOrderId) {
                        return;
                    }
                    currentDeps.setVehicleInfo(res);
                })
                .catch(() => { });
src/robotaxi/pages/begin-route/index.tsx
@@ -393,6 +393,7 @@
    cancelType,
    setTripStatus,
    setOrderId,
    setVehicleInfo,
    setCallSubmitting,
    setCancelType,
    setShowCancelPopup,
src/robotaxi/pages/site-search/hooks/use-site-search.ts
@@ -33,11 +33,19 @@
  const [isSearching, setIsSearching] = useState(false);
  // 最近一次完成搜索的关键词
  const [lastCompletedKeyword, setLastCompletedKeyword] = useState('');
  // 初始化数据是否已完成加载
  const [isInitLoaded, setIsInitLoaded] = useState(false);
  const searchSeqRef = useRef(0);
  const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const pendingSearchValueRef = useRef('');
  // 拉取附近站点
  const fetchNearbySites = async (lat: number, lon: number, electronicAreaId?: string) => {
  const fetchNearbySites = async (
    lat: number,
    lon: number,
    electronicAreaId?: string,
    canUpdate?: () => boolean
  ) => {
    const selectedCity = Taro.getStorageSync('selectedCity');
    try {
      const res = await getNearestSite({
@@ -46,9 +54,15 @@
        longitude: Number(lon),
        electronicAreaId
      });
      if (canUpdate && !canUpdate()) {
        return;
      }
      const points = Array.isArray(res?.points) ? res.points : [];
      setNearbySiteList(points.map(normalizeSiteItem));
    } catch {
      if (canUpdate && !canUpdate()) {
        return;
      }
      setNearbySiteList([]);
    }
  };
@@ -139,21 +153,55 @@
  // 搜索输入防抖
  const debouncedSearch = useMemo(() => {
    return (val: string) => {
      pendingSearchValueRef.current = val;
      if (debounceTimerRef.current) {
        clearTimeout(debounceTimerRef.current);
      }
      debounceTimerRef.current = setTimeout(() => {
        debounceTimerRef.current = null;
        searchSiteRef.current(val);
      }, 300);
    };
  }, []);
  // 清空搜索结果列表
  const clearSearchResults = () => {
  // 取消搜索输入防抖定时器
  const cancelDebouncedSearch = () => {
    if (debounceTimerRef.current) {
      clearTimeout(debounceTimerRef.current);
      debounceTimerRef.current = null;
    }
    pendingSearchValueRef.current = '';
  };
  // 立即执行待触发的防抖搜索
  const flushDebouncedSearch = () => {
    if (!debounceTimerRef.current) {
      return;
    }
    const pendingValue = pendingSearchValueRef.current;
    clearTimeout(debounceTimerRef.current);
    debounceTimerRef.current = null;
    pendingSearchValueRef.current = '';
    searchSiteRef.current(pendingValue);
  };
  // 确认搜索:优先使用显式关键字,其次使用防抖待执行关键字,最后回退到当前输入值
  const confirmSearch = (val?: string) => {
    const explicitValue = typeof val === 'string' ? val : '';
    const pendingValue = pendingSearchValueRef.current;
    const fallbackValue = deps.searchValue;
    const targetValue = explicitValue || pendingValue || fallbackValue || '';
    if (debounceTimerRef.current) {
      clearTimeout(debounceTimerRef.current);
      debounceTimerRef.current = null;
    }
    pendingSearchValueRef.current = '';
    searchSiteRef.current(targetValue);
  };
  // 清空搜索结果列表
  const clearSearchResults = () => {
    cancelDebouncedSearch();
    // 推进请求序号,使清空前已发出的请求结果失效
    searchSeqRef.current += 1;
    setSiteList([]);
@@ -164,36 +212,55 @@
  useEffect(() => {
    return () => {
      if (debounceTimerRef.current) {
        clearTimeout(debounceTimerRef.current);
      }
      cancelDebouncedSearch();
    };
  }, []);
  // 初始化:拉取附近站点、历史记录、页面类型
  useEffect(() => {
    const { pageType: type, latitude: lat, longitude: lon, electronicAreaId } = deps.router.params;
    if (lat && lon) {
      fetchNearbySites(Number(lat), Number(lon), electronicAreaId);
    }
    checkOtp(false).then(otp => {
      if (otp && lat && lon) {
        const payload = { lat: Number(lat), lon: Number(lon), electronicAreaId };
        const handleSuccess = (res: any) => {
          if (Array.isArray(res)) {
    let isUnmounted = false;
    const initPageData = async () => {
      const { pageType: type, latitude: lat, longitude: lon, electronicAreaId } = deps.router.params;
      const initTasks: Promise<unknown>[] = [];
      if (lat && lon) {
        initTasks.push(
          fetchNearbySites(
            Number(lat),
            Number(lon),
            electronicAreaId,
            () => !isUnmounted
          )
        );
      }
      initTasks.push((async () => {
        try {
          const otp = await checkOtp(false);
          if (!otp || !lat || !lon) {
            return;
          }
          const payload = { lat: Number(lat), lon: Number(lon), electronicAreaId };
          const res = type === 'start'
            ? await getStartPoints(payload)
            : type === 'end'
              ? await getEndPoints(payload)
              : [];
          if (Array.isArray(res) && !isUnmounted) {
            setHistorySite(res.map(normalizeSiteItem));
          }
        };
        if (type === 'start') {
          getStartPoints(payload).then(handleSuccess);
        } else if (type === 'end') {
          getEndPoints(payload).then(handleSuccess);
        }
        } catch { }
      })());
      if (type) {
        setPageType(type);
      }
    });
    if (type) {
      setPageType(type);
    }
      await Promise.allSettled(initTasks);
      if (!isUnmounted) {
        setIsInitLoaded(true);
      }
    };
    initPageData();
    return () => {
      isUnmounted = true;
    };
  }, []);
  return {
@@ -204,8 +271,12 @@
    nearbySiteList,
    isSearching,
    lastCompletedKeyword,
    isInitLoaded,
    searchSite,
    debouncedSearch,
    cancelDebouncedSearch,
    flushDebouncedSearch,
    confirmSearch,
    clearSearchResults
  };
}
src/robotaxi/pages/site-search/index.tsx
@@ -39,8 +39,12 @@
    nearbySiteList,
    isSearching,
    lastCompletedKeyword,
    isInitLoaded,
    searchSite,
    debouncedSearch,
    cancelDebouncedSearch,
    flushDebouncedSearch,
    confirmSearch,
    clearSearchResults
  } = useSiteSearch({
    router,
@@ -61,7 +65,7 @@
    }).catch(() => { });
  }, []);
  const changeSearchValue = (e) => {
  const changeSearchValue = (e: any) => {
    const val = e.detail.value;
    setSearchValue(val);
    debouncedSearch(val);
@@ -238,8 +242,12 @@
        <View className='search-input-box'>
          <Input className='search-input' placeholder={pageType == 'start' ? '请输入上车点' : '请输入目的地'} value={searchValue}
            onInput={(e) => changeSearchValue(e)}
            onBlur={() => searchSite()}
            onConfirm={() => searchSite()}
            onBlur={flushDebouncedSearch}
            onConfirm={(e) => {
              const keyword = e?.detail?.value || searchValue; // 确认搜索关键字,优先取输入框事件值
              confirmSearch(keyword);
            }}
            confirmType='search'
            autoFocus
          />
          {
@@ -300,7 +308,7 @@
        </View>
      }
      {
        canShowSearchFallback && (!siteList.length && (!searchValue ? !nearbySiteList.length : true) && !nonOperationalSite.length && (!searchValue ? !historySite.length : true)) &&
        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'>