董国庆
2026-02-14 473d865daf3d53b2bee066ca12e613192602425a
优化小程序问题
1个文件已添加
16个文件已修改
428 ■■■■■ 已修改文件
config/dev.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/constants/app.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/index.tsx 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/begin-route.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/home.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/pricing.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/expense-detail/index.tsx 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/order/index.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/pricing-rules/index.less 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/pricing-rules/index.tsx 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/select-map-site/index.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/Robotaxi-public-method.ts 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/report.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/request.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/websocket.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/dev.js
@@ -17,10 +17,10 @@
const COMMON_OSS_HOST = '"https://spre.xiaopeng.com"'; // 官渠通用 oss 资源前缀
const ADMIN_OSS_HOST = '"https://pre-xps01.xiaopeng.com"'; // 运营后台上传的额文件的 oss 域名
// const ROBOTAXI_HOST = '"http://192.168.110.80:8000"'; // 打车接口域名
// const ROBOTAXI_WS_HOST = '"ws://192.168.110.34:8888/websocket"';
const ROBOTAXI_HOST = '"http://221.182.45.100:8000"';
const ROBOTAXI_WS_HOST = '"ws://221.182.45.100:8888/websocket"';
const ROBOTAXI_HOST = '"http://192.168.110.80:8000"'; // 打车接口域名
const ROBOTAXI_WS_HOST = '"ws://192.168.110.34:8888/websocket"';
// const ROBOTAXI_HOST = '"http://221.182.45.100:8000"';
// const ROBOTAXI_WS_HOST = '"ws://221.182.45.100:8888/websocket"';
const XP_APPID_MAP = {
  weapp: '"xxxxxxx"', // 用户中心登录appId
src/constants/app.ts
@@ -13,7 +13,7 @@
export const PUBLIC_HEADER = {
  'XP-Uid': '-1', // 登录后属性
  'XP-Uid': '1', // 登录后属性
  'Content-Type': 'application/json', // 数据格式,固定
  'XP-Authorization': '', // 计算属性
  'XP-Encryption-Type': '3',  // 加密方式,对应base64
src/pages/robotaxi/home/index.tsx
@@ -19,10 +19,11 @@
  extractPointsFromPolygons,
  convertAdcodeToCityCode,
  handleBannerJump,
  fetchMapData
  fetchMapData,
  getLocationByIP
} from '../../../utils/Robotaxi-public-method';
import { _ROBOTAXI_WS_HOST } from '../../../constants/api';
import { getGeofence, getBannerList } from '../../../robotaxi/api/home';
import { getGeofence, getBannerList, getAgreement } from '../../../robotaxi/api/home';
import { getOrderDistance } from '../../../robotaxi/api/select-map-site';
import { checkOtp } from '../../../utils/tools';
@@ -99,16 +100,42 @@
        const auth = res.authSetting['scope.userLocation'];
        if (auth === false) {
          setIsLocationAuthDenied(true);
          setLatitude(DEFAULT_LATITUDE);
          setLongitude(DEFAULT_LONGITUDE);
          // 检查是否有选中的城市缓存
          const selectedCity = Taro.getStorageSync('selectedCity');
          if (!selectedCity || !selectedCity.code) {
            setSelectedCity({
              name: '北京市',
              code: '110000',
            })
            Taro.setStorageSync('selectedCity', {
              name: '北京市',
              code: '110000',
            // 没有缓存,尝试通过 IP 获取位置
            getLocationByIP().then(res => {
              setLatitude(res.latitude);
              setLongitude(res.longitude);
              const cityName = res.city || res.province || '北京市';
              const cityCode = convertAdcodeToCityCode(res.adcode, cityName, res.province);
              const cityInfo = {
                name: cityName,
                code: cityCode
              };
              setSelectedCity(cityInfo);
              Taro.setStorageSync('selectedCity', cityInfo);
            }).catch(() => {
              // IP 获取失败,使用默认值
              setLatitude(DEFAULT_LATITUDE);
              setLongitude(DEFAULT_LONGITUDE);
              setSelectedCity({
                name: '北京市',
                code: '110000',
              })
              Taro.setStorageSync('selectedCity', {
                name: '北京市',
                code: '110000',
              });
            });
          } else {
            // 有缓存,但授权被拒绝,仍然可以尝试通过 IP 更新中心点
            getLocationByIP().then(res => {
              setLatitude(res.latitude);
              setLongitude(res.longitude);
            }).catch(() => {
              setLatitude(DEFAULT_LATITUDE);
              setLongitude(DEFAULT_LONGITUDE);
            });
          }
        } else {
@@ -151,7 +178,28 @@
  // 监听隐私政策显示状态
  useEffect(() => {
    setShowPrivacy(Taro.getStorageSync('has_show_privicy_Robotaxi') != 'true');
    checkOtp(false).then(otp => {
      if (otp) {
        getAgreement().then((res: any) => {
          console.log('获取协议状态:', res);
          // 如果接口返回未同意,则显示弹窗
          if (res === false) {
            setShowPrivacy(true);
            Taro.setStorageSync('has_show_privicy_Robotaxi', 'false');
          } else {
            setShowPrivacy(false);
            Taro.setStorageSync('has_show_privicy_Robotaxi', 'true');
          }
        }).catch(err => {
          console.error('获取协议状态失败', err);
          // 兜底逻辑:如果接口报错,根据本地缓存判断
          setShowPrivacy(Taro.getStorageSync('has_show_privicy_Robotaxi') != 'true');
        });
      } else {
        // 未登录时,根据本地缓存判断
        setShowPrivacy(Taro.getStorageSync('has_show_privicy_Robotaxi') != 'true');
      }
    });
    getBannerList().then((res: any) => {
      if (res && Array.isArray(res)) {
@@ -332,8 +380,28 @@
      })
      .catch(() => {
        setIsLocationAuthDenied(true);
        setLatitude(DEFAULT_LATITUDE);
        setLongitude(DEFAULT_LONGITUDE);
        // 定位授权失败,尝试通过 IP 获取位置
        getLocationByIP().then(res => {
          setLatitude(res.latitude);
          setLongitude(res.longitude);
          const cityName = res.city || res.province || '北京市';
          const cityCode = convertAdcodeToCityCode(res.adcode, cityName, res.province);
          const cityInfo = {
            name: cityName,
            code: cityCode
          };
          setSelectedCity(cityInfo);
          Taro.setStorageSync('selectedCity', cityInfo);
          setCurrentAddress(cityName);
          setCurrentSite({
            name: cityName,
            latitude: res.latitude,
            longitude: res.longitude
          });
        }).catch(() => {
          setLatitude(DEFAULT_LATITUDE);
          setLongitude(DEFAULT_LONGITUDE);
        });
      });
  }
src/robotaxi/api/begin-route.ts
@@ -114,4 +114,12 @@
        method: 'POST',
        payload
    });
}
// 取消订单
export const cancelOrder = async (orderId: string | number) => {
    return request({
        url: _ROBOTAXI_HOST + `/order/api/v1/mini-program/order/cancel/${orderId}`,
        method: 'PUT'
    });
}
src/robotaxi/api/home.ts
@@ -59,4 +59,12 @@
        method: 'POST',
        payload
    });
}
// 获取协议状态
export const getAgreement = async () => {
    return request({
        url: _ROBOTAXI_HOST + '/operation/api/v1/mini-program/privacy/getAgreement',
        method: 'POST'
    });
}
src/robotaxi/api/pricing.ts
New file
@@ -0,0 +1,13 @@
import request from '../../utils/request';
import { _ROBOTAXI_HOST } from '../../constants/api';
/**
 * 获取定价规则
 * @param openCityId 城市ID
 */
export const getPricingRules = async (openCityId: string | number) => {
    return request({
        url: `${_ROBOTAXI_HOST}/operation/base/v1/mini-program/pricing/rules/${openCityId}`,
        method: 'GET'
    });
};
src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -429,7 +429,7 @@
                                车身编码: {vehicleInfo?.vehicleCode}
                            </View>
                        </View>
                        <Image className="car-card-detail-img" src={vehicleInfo?.vehiclePictures} />
                        <Image className="car-card-detail-img" src={vehicleInfo?.vehiclePictures?.[0].url || ''} />
                    </View>
                    {
                        tripStatus == 4 && <View>
src/robotaxi/pages/begin-route/index.tsx
@@ -28,6 +28,7 @@
  queryRouteAndFare,
  queryVehicleByOrderId,
  getOrderPayment,
  cancelOrder,
} from '../../../robotaxi/api/begin-route';
export default function SelectMapSite() {
@@ -589,14 +590,32 @@
  }
  // 确认取消
  const handleConfirmCancel = () => {
  const handleConfirmCancel = async () => {
    const targetOrderId = orderId || Taro.getStorageSync('orderId');
    if (targetOrderId) {
      try {
        await cancelOrder(targetOrderId);
        Taro.showToast({
          title: '行程已取消',
          icon: 'success',
        });
      } catch (error) {
        Taro.showToast({
          title: '取消失败,请重试',
          icon: 'none',
        });
        return;
      }
    }
    setShowCancelPopup(false);
    setTripStatus(1);
    setOrderId('');
    Taro.removeStorageSync('orderRouteDuration');
    Taro.removeStorageSync('orderRouteDistance');
    Taro.removeStorageSync('orderRoutePoints');
    Taro.removeStorageSync('orderRouteLatestType');
    Taro.removeStorageSync('orderId');
    Taro.removeStorageSync('tripStatus');
  }
  // 弹窗操作
src/robotaxi/pages/expense-detail/index.tsx
@@ -18,6 +18,12 @@
    if (start) setStartSite(start);
    if (end) setEndSite(end);
  }, []);
  const toPricingRule = () => {
    const cityID = Taro.getStorageSync('selectedCity').id;
    Taro.navigateTo({
      url: '/robotaxi/pages/pricing-rules/index?cityID=' + cityID,
    })
  }
  if (!pricingCard) return null;
@@ -83,7 +89,7 @@
      当前预估价是基于实时交通情况、预估里程和时长等因素得出;若中途发生修改目的地、路线以及加途径点、里程时长偏差较大等场景,订单结算金额将会重新计算。高速路桥费、停车费、超时等待费、远程调度费、跨域费等,将按行驶过程中实际产生的费用额外收取。
      </Text>
      {/* 查看计价规则 */}
      <View className='look-rule'>
      <View className='look-rule' onClick={()=> toPricingRule()}>
        <Text className='look-rule-txt'>查看计价规则</Text>
        <Image className='arrow-icon' src={Subtract}></Image>
      </View>
src/robotaxi/pages/order/index.tsx
@@ -23,7 +23,7 @@
  const getList = async (page: number, isRefresh = false) => {
    try {
      const res = await getOrderHistoryList({ pageNum: page, pageSize, status: 9 });
      const res = await getOrderHistoryList({ pageNum: page, pageSize: pageSize });
      const newList = (res && (res as any).list) || [];
      if (isRefresh) {
        setGroups(newList);
src/robotaxi/pages/pricing-rules/index.less
@@ -4,6 +4,15 @@
  flex-direction: column;
  padding: 72px 64px;
  .rich-text-wrapper {
    width: 100%;
    font-size: 28px;
    line-height: 44px;
    color: rgba(0, 0, 0, 0.9);
    word-break: break-word;
    overflow-wrap: break-word;
  }
  .title {
    font-family: PingFang SC;
    font-size: 28px;
@@ -57,6 +66,15 @@
    color: rgba(0, 0, 0, 0.6);
  }
  .loading-box, .no-data {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 28px;
    color: rgba(0, 0, 0, 0.6);
  }
}
.relative {
@@ -76,4 +94,4 @@
.color-red {
  color: #FF4D4F !important;
}
}
src/robotaxi/pages/pricing-rules/index.tsx
@@ -1,73 +1,110 @@
import Taro from '@tarojs/taro';
import { Component, useState, useEffect } from 'react';
import { View, Text, Image, Input } from '@tarojs/components';
import Taro, { useRouter } from '@tarojs/taro';
import { useState, useEffect } from 'react';
import { View, Text, RichText } from '@tarojs/components';
import { getPricingRules } from '../../api/pricing';
import './index.less';
import Subtract from '../../assets/Subtract.png';
export default function PricingRulesPage() {
  const router = useRouter();
  const { cityID } = router.params;
  const [pricingData, setPricingData] = useState<any>(null);
  const [loading, setLoading] = useState(true);
  const [pricingHtml, setPricingHtml] = useState<string>('');
  const [pricingNodes, setPricingNodes] = useState<any[]>([]);
  useEffect(() => {
    if (cityID) {
      fetchPricingRules(cityID);
    } else {
      setLoading(false);
    }
  }, [cityID]);
  const extractHtmlFromData = (data: any): string => {
    if (!data) return '';
    if (typeof data === 'string') return data;
    const candidates = ['html', 'content', 'richText', 'rich_text', 'data'];
    for (const key of candidates) {
      const val = (data as any)?.[key];
      if (typeof val === 'string') return val;
    }
    return '';
  };
  const parseHtmlToNodes = async (html: string) => {
    if (!html) {
      setPricingNodes([]);
      return;
    }
    try {
      const { parse } = await import('mini-html-parser2');
      parse(html, (_err: any, nodes: any[]) => {
        const enhanceNodes = (ns: any[]): any[] =>
          ns.map((n: any) => {
            const node = { ...n };
            if (node.name === 'img') {
              node.attrs = {
                ...(node.attrs || {}),
                style: `${node.attrs?.style || ''};max-width:100%;height:auto;display:block;`
              };
            }
            if (Array.isArray(node.children) && node.children.length > 0) {
              node.children = enhanceNodes(node.children);
            }
            return node;
          });
        setPricingNodes(enhanceNodes(nodes || []));
      });
    } catch {
      setPricingNodes([]);
    }
  };
  const fetchPricingRules = async (cityId: string) => {
    try {
      setLoading(true);
      const res: any = await getPricingRules(cityId);
      const data = res?.data ?? res;
      setPricingData(data);
      const html = extractHtmlFromData(data);
      setPricingHtml(html);
      await parseHtmlToNodes(html);
    } catch (error) {
      console.error('获取定价规则失败', error);
      Taro.showToast({
        title: '获取定价规则失败',
        icon: 'none'
      });
    } finally {
      setLoading(false);
    }
  };
  if (loading) {
    return (
      <View className='pricing-rules-page'>
        <View className='loading-box'>
          <Text>加载中...</Text>
        </View>
      </View>
    );
  }
  if ((!pricingHtml || pricingHtml.trim().length === 0) && (!pricingNodes || pricingNodes.length === 0)) {
    return (
      <View className='pricing-rules-page'>
        <View className='no-data'>
          <Text>暂无定价规则</Text>
        </View>
      </View>
    );
  }
  return (
    <View className='pricing-rules-page'>
      <View className='title'>起步价(3公里,8分钟)</View>
      <View className='dis'>
        包含里程3公里,包含时长8分钟,包含里程或包含时长(低速时长)任何一项超出时,将在起步价基础上累加计费。
      </View>
      <View className='flex-between-card'>
        <View className='item-value'>普通时段</View>
        <View className='item-value'>4.0元 </View>
      </View>
      <View className='flex-between-card'>
        <View className='item-value'>00:00-07:00</View>
        <View className='item-value'>4.0元</View>
      </View>
      <View className='flex-between-card'>
        <View className='item-value'>07:00-18:00</View>
        <View className='item-value'>4.0元</View>
      </View>
      {/* 里程费 */}
      <View className='item-title'>里程费</View>
      <View className='flex-between-card'>
        <View className='item-value'>普通时段</View>
        <View className='item-value'>1.0元</View>
      </View>
      <View className='flex-between-card'>
        <View className='item-value'>普通时段</View>
        <View className='item-value'>1.0元</View>
      </View>
      <View className='flex-between-card'>
        <View className='item-value'>普通时段</View>
        <View className='item-value'>1.0元</View>
      </View>
      {/* 时长费 */}
      <View className='item-title'>时长费</View>
      <View className='flex-between-card'>
        <View className='item-value'>普通时段</View>
        <View className='item-value'>1.0元</View>
      </View>
      <View className='flex-between-card'>
        <View className='item-value'>普通时段</View>
        <View className='item-value'>1.0元</View>
      </View>
      <View className='flex-between-card'>
        <View className='item-value'>普通时段</View>
        <View className='item-value'>1.0元</View>
      </View>
      {/* 远途费 */}
      <View className='item-title'>远途费</View>
      <View className='flex-between-card'>
        <View className='item-value'>普通时段</View>
        <View className='item-value'>1.0元</View>
      </View>
      <View className='flex-between-card'>
        <View className='item-value'>普通时段</View>
        <View className='item-value'>1.0元</View>
      </View>
      <View className='flex-between-card'>
        <View className='item-value'>普通时段</View>
        <View className='item-value'>1.0元</View>
      <View className='rich-text-wrapper'>
        <RichText nodes={pricingNodes && pricingNodes.length > 0 ? pricingNodes : pricingHtml} />
      </View>
    </View>
  )
  );
}
src/robotaxi/pages/select-map-site/index.tsx
@@ -236,7 +236,7 @@
    }
    if (pageType === 'end') {
      const startSiteCache = Taro.getStorageSync('startSite');
      if (startSiteCache?.electronicAreaId && selectedSite?.electronicAreaId && startSiteCache.electronicAreaId !== selectedSite.electronicAreaId) {
      if (startSiteCache?.electronicAreaId && selectedSite?.electronicAreaId && startSiteCache.electronicAreaId != selectedSite.electronicAreaId) {
        Taro.showToast({
          title: '起终点需在同一运营区域',
          icon: 'none',
src/utils/Robotaxi-public-method.ts
@@ -1,6 +1,7 @@
import amapFile from '../libs/amap-wx.130';
import { AMAP_KEY } from '../constants/api';
import amapFile from '../libs/amap-wx.130.js';
import { AMAP_KEY, _ROBOTAXI_HOST } from '../constants/api';
import Taro from '@tarojs/taro';
import request from './request';
import { getFenceCheck, getNearestSite } from '../robotaxi/api/home';
// 辅助函数:计算两点之间的距离(单位:米)
@@ -245,3 +246,61 @@
    });
  });
};
/**
 * 通过 IP 获取当前位置信息(用于定位授权失败时的兜底)
 */
export const getLocationByIP = (): Promise<any> => {
  return new Promise(async (resolve, reject) => {
    try {
      // 1. 获取当前 network IP
      // 微信小程序没有直接获取公网 IP 的接口,通常由后端从请求头中获取,或者通过第三方接口获取
      // 这里模拟调用服务端提供的 IP 定位接口,假设接口会自动识别请求来源 IP
      const res: any = await request({
        url: _ROBOTAXI_HOST + '/map/base/v1/mini-program/point/getPointByIp',
        method: 'GET',
      });
      if (res && res.point && typeof res.point.latitude === 'number' && typeof res.point.longitude === 'number') {
        resolve({
          longitude: res.point.longitude,
          latitude: res.point.latitude,
          city: res.city || '',
          adcode: res.adcode || ''
        });
      } else {
        // 如果后端接口不可用或未返回数据,尝试使用第三方接口作为备选获取 IP
        Taro.request({
          url: 'https://api.ipify.org?format=json',
          success: (ipRes) => {
            const ip = ipRes.data?.ip;
            if (ip) {
              // 拿着获取到的 IP 去请求服务端接口
              request({
                url: _ROBOTAXI_HOST + '/map/base/v1/mini-program/point/getPointByIp',
                method: 'POST',
                payload: { ip }
              }).then((businessRes: any) => {
                if (businessRes && businessRes.point && typeof businessRes.point.latitude === 'number' && typeof businessRes.point.longitude === 'number') {
                  resolve({
                    longitude: businessRes.point.longitude,
                    latitude: businessRes.point.latitude,
                    city: businessRes.city || '',
                    adcode: businessRes.adcode || ''
                  });
                } else {
                  reject('Business IP location failed');
                }
              }).catch(reject);
            } else {
              reject('Failed to get network IP');
            }
          },
          fail: reject
        });
      }
    } catch (error) {
      reject(error);
    }
  });
};
src/utils/report.ts
@@ -63,7 +63,7 @@
  // 获取uid
  const uid = Taro.getStorageSync(_UID) || '-1';
  header['XP-Uid'] = uid;
  header['XP-Uid'] = 1||uid;
  const ENV = process.env.TARO_ENV
  const accountInfo: any = ENV === 'weapp' ? Taro.getAccountInfoSync() || {} : {};
src/utils/request.ts
@@ -151,7 +151,7 @@
  // 获取uid
  const uid = await getStorage(_UID) || '-1';
  header['XP-Uid'] = uid.toString();
  header['XP-Uid'] = 1||uid.toString();
  // 获取设备id
  header['XP-Deviceid'] = Taro.getStorageSync('wxOpenId') || 'default_device_id';
src/utils/websocket.ts
@@ -42,7 +42,7 @@
    const uid = await getStorage(_UID) || '-1';
    const task = Taro.connectSocket({
      header: {
        'XP-Uid': uid.toString(),
        'XP-Uid': 1 || uid.toString(),
      },
      url: this.url,
      success: () => { },
@@ -88,7 +88,7 @@
        this.reconnectAttempts = 0;
        this.startHeartbeat();
        const uid = await getStorage(_UID) || '-1';
        this.send({ type: 'auth', appUserId: uid.toString(), timestamp: Date.now() });
        this.send({ type: 'auth', appUserId: '12345678', timestamp: Date.now() });
        this.notifyListeners('open', { status: 'connected' });
      });
@@ -136,7 +136,7 @@
      this.reconnectAttempts = 0;
      this.startHeartbeat();
      const uid = await getStorage(_UID) || '-1';
      this.send({ type: 'auth', appUserId: uid.toString(), timestamp: Date.now() });
      this.send({ type: 'auth', appUserId: '12345678', timestamp: Date.now() });
      this.notifyListeners('open', { status: 'connected' });
    });