| src/pages/robotaxi/home/index.tsx | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/begin-route/components/calling-car.tsx | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/begin-route/components/css-folder/passenger-notice.less | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/begin-route/components/css-folder/release-route.less | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/begin-route/components/passenger-notice.tsx | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/begin-route/index.tsx | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/operating-area/index.less | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/operating-area/index.tsx | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/select-map-site/index.tsx | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/site-search/index.less | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/site-search/index.tsx | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/pages/robotaxi/home/index.tsx
@@ -1208,6 +1208,29 @@ jumpTo({ url: '/pages/user/login?hideBack=1' }); } // 处理点击地图 marker const handleMarkerTap = (e: any) => { if (isLocationAuthDenied) return; const markerId = e?.detail?.markerId; if (markerId === undefined) return; const targetMarker = markers.find(m => m.id === markerId); if (targetMarker) { // 忽略因调用 moveToLocation 引起的 region change,避免重复吸附计算 shouldIgnoreRegionChange.current = true; const mapCtx = Taro.createMapContext('map'); mapCtx.moveToLocation({ latitude: targetMarker.latitude, longitude: targetMarker.longitude, }); setIsAdsorbed(true); setCurrentSite(targetMarker); setShowToolTip(true); } }; return ( <View className="home-container"> <Map @@ -1215,10 +1238,11 @@ longitude={longitude ?? DEFAULT_LONGITUDE} latitude={latitude ?? DEFAULT_LATITUDE} scale={mapScale} markers={isLocationAuthDenied ? [] : markers} markers={isLocationAuthDenied ? [] : markers.map(m => ({ ...m, title: '', name: '' }))} polygons={polygons} showLocation={!!longitude} onRegionChange={handleRegionChange} onMarkerTap={handleMarkerTap} style={{ width: '100%', height: '85vh', flex: 'none' }} > {/* 选择城市 */} src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -576,7 +576,7 @@ } </View> { tripStatus == 4 && <View> (tripStatus == 4 && typeof tempStatus === 'number') && <View> <View className="car-card-line"></View> {/* 温度调节 */} <View className="car-card-temp"> src/robotaxi/pages/begin-route/components/css-folder/passenger-notice.less
@@ -33,12 +33,39 @@ color: rgba(0, 0, 0, 0.9); } &-desc { &-desc-wrap { position: relative; margin-top: 34px; letter-spacing: normal; color: rgba(0, 0, 0, 0.9); height: 342px; overflow: hidden; .passenger-content-desc { height: 100%; overflow-y: scroll; margin-top: 0; } /* 自定义滚动条轨道 */ .custom-scrollbar-track { position: absolute; right: 0; top: 0; width: 6px; height: 100%; background-color: rgba(0, 0, 0, 0.05); border-radius: 4px; z-index: 10; } /* 自定义滚动条滑块 */ .custom-scrollbar-thumb { position: absolute; right: 0; top: 0; width: 6px; background-color: rgba(0, 0, 0, 0.25); border-radius: 4px; z-index: 11; } } &-checkbox { src/robotaxi/pages/begin-route/components/css-folder/release-route.less
@@ -95,7 +95,7 @@ align-items: center; .to-site-text { font-size: 32px; font-size: 28px; font-weight: 500; line-height: 48px; letter-spacing: normal; @@ -141,7 +141,6 @@ } .money-num { margin: 0 4px; font-size: 48px; line-height: 40px; @@ -169,7 +168,7 @@ } view:last-child { font-size: 20px; font-size: 24px; font-weight: normal; line-height: 36px; letter-spacing: normal; @@ -201,7 +200,7 @@ .original-fare { font-size: 28px; font-size: 24px; font-weight: normal; line-height: 41px; letter-spacing: normal; src/robotaxi/pages/begin-route/components/passenger-notice.tsx
@@ -7,13 +7,52 @@ type Props = { show?: boolean; onClose?: () => void; onCheckedChange?: (checked: boolean) => void; } export default function PassengerNotice({ show = false, onClose }: Props) { 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 onScroll = (e) => { const { scrollHeight, scrollTop } = e.detail; if (containerHeight > 0 && scrollHeight > containerHeight) { const newThumbTop = (scrollTop / scrollHeight) * containerHeight; setThumbTop(newThumbTop); } }; useEffect(() => { if (noticeContent && visible) { // Give it a little time to render setTimeout(() => { const query = Taro.createSelectorQuery(); query.select('.passenger-content-desc').boundingClientRect(); query.select('.passenger-content-desc-inner').boundingClientRect(); query.exec((res) => { if (res[0] && res[1]) { const wrapperHeight = res[0].height; const contentHeight = res[1].height; setContainerHeight(wrapperHeight); if (contentHeight > wrapperHeight) { setShowScrollbar(true); // thumb height = (container height / content height) * container height const calculatedThumbHeight = (wrapperHeight / contentHeight) * wrapperHeight; setThumbHeight(Math.max(calculatedThumbHeight, 20)); // min height 20px } else { setShowScrollbar(false); } } }); }, 300); } }, [noticeContent, visible]); useEffect(() => { if (show && !visible) { @@ -60,13 +99,36 @@ if (!visible) return null; return ( <View className='passenger-popup'> <View className='passenger-popup' onClick={() => onCheckedChange?.(true)}> <View className='passenger-mask' catchMove></View> <View className='passenger-content' animation={animationData}> <View className='passenger-content-title'>乘车需知</View> <ScrollView className='passenger-content-desc' scrollY enhanced lowerThreshold={30} upperThreshold={30} showScrollbar={true}> <RichText nodes={noticeContent} /> </ScrollView> <View className='passenger-content-desc-wrap'> <ScrollView className='passenger-content-desc' scrollY enhanced lowerThreshold={30} upperThreshold={30} showScrollbar={false} onScroll={onScroll} > <View className='passenger-content-desc-inner'> <RichText nodes={noticeContent} /> </View> </ScrollView> {showScrollbar && ( <View className='custom-scrollbar-track'> <View className='custom-scrollbar-thumb' style={{ height: `${thumbHeight}px`, transform: `translateY(${thumbTop}px)` }} /> </View> )} </View> <View onClick={() => setChecked(!checked)} className='passenger-content-checkbox'> { checked ? src/robotaxi/pages/begin-route/index.tsx
@@ -50,6 +50,7 @@ import { getOrderDistance } from '../../api/select-map-site'; import { checkOtp } from '../../../utils/tools'; import { jumpTo } from '../../../utils/jump'; import { _ROBOTAXI_WS_HOST } from '../../../constants/api'; export default function SelectMapSite() { const WAITING_TIMER_START_KEY = 'waitingTimerStartAt'; // 等待计时开始时间 @@ -756,7 +757,10 @@ if (durationSeconds !== null) { setRouteDuration(durationSeconds); } setVehicleDirection(data?.direction); const dir = Number(data?.direction); if (Number.isFinite(dir)) { setVehicleDirection(dir); } }) .catch(() => { }); }; @@ -923,11 +927,11 @@ return; } const points = payload?.points || []; const direction = payload?.direction; if (typeof direction === 'number' && Number.isFinite(direction)) { const direction = Number(payload?.direction); if (Number.isFinite(direction)) { setVehicleDirection(direction); } const distance = Number(JSON.parse(payload?.content?.route)?.distance); const distance = Number(JSON.parse(payload?.content?.route || '{}')?.distance); const duration = Number(JSON.parse(payload?.content?.route)?.duration); const distanceKm = normalizeDistanceKm(distance); if (distanceKm !== null) { @@ -954,7 +958,7 @@ applyRoutePoints(points); }); } // 延迟执行车辆位置更新,确保路线擦除(同步调用或极短时间生效)先于小车平移发生 if (firstPoint && typeof firstPoint.latitude === 'number' && typeof firstPoint.longitude === 'number') { setTimeout(() => { @@ -1393,7 +1397,7 @@ if (!carMarkerInitialized.current) return; if (!vehicleLocation) return; const mapCtx = Taro.createMapContext('map'); // 增加延迟,确保 markers 已经在原生视图中完成渲染,避免 translateMarker 找不到 markerId 导致 fail const timer = setTimeout(() => { mapCtx.translateMarker({ @@ -1403,7 +1407,8 @@ longitude: vehicleLocation.longitude, }, autoRotate: false, rotate: typeof vehicleDirection === 'number' ? vehicleDirection : 0, moveWithRotate: true, rotate: typeof vehicleDirection === 'number' ? Number(vehicleDirection.toFixed(0)) : 0, duration: 100, fail: (err) => { console.log('translateMarker fail:', err, 'markerId:', vehicleMarkerId); @@ -1445,6 +1450,7 @@ return; } if (socketOpenHandlerRef.current) { wsManager.connect(_ROBOTAXI_WS_HOST); return; } const handleOpen = () => { @@ -1452,6 +1458,7 @@ }; socketOpenHandlerRef.current = handleOpen; wsManager.on('open', handleOpen); wsManager.connect(_ROBOTAXI_WS_HOST); }; // 页面初始化 @@ -1585,14 +1592,16 @@ // 返回当前位置或车辆位置 const toOrigin = () => { const mapCtx = Taro.createMapContext('map'); // 如果是状态6(车辆行驶中)且有车辆位置,则回到车辆位置 if (tripStatus === 6 && vehicleLocation?.latitude && vehicleLocation?.longitude) { mapCtx.moveToLocation({ latitude: vehicleLocation.latitude, longitude: vehicleLocation.longitude, // 如果是状态6(车辆行驶中)且有路线数据,则展示完整路线 if (tripStatus === 6 && routePoints.length > 0) { const bottomPadding = computeBottomPadding(routePoints, 'route'); const topPadding = Math.max(24, resolveSafeTopInset() + 32); mapCtx.includePoints({ points: routePoints, padding: [topPadding, 100, bottomPadding, 100] }); // 恢复视口锁定状态,让地图重新跟随车辆 tripStatus6ViewportLockedRef.current = false; // 锁定视口状态,防止后续路线数据更新时频繁改变缩放比例 tripStatus6ViewportLockedRef.current = true; } else { mapCtx.moveToLocation(); } @@ -1941,6 +1950,9 @@ url: `/robotaxi/pages/site-search/index?pageType=${pageType}&longitude=${startSite.longitude}&latitude=${startSite.latitude}&electronicAreaId=${startSite.electronicAreaId}` }) } const changeCheckedEnter = (checked: boolean) => { isCheckedEnter.current = checked; } // 点击气泡 const handleCalloutTap = (e) => { @@ -2238,6 +2250,7 @@ <PassengerNotice show={showPassengerNotice} onClose={handleClosePassengerNotice} onCheckedChange={changeCheckedEnter} /> } {/* 评价弹窗 */} src/robotaxi/pages/operating-area/index.less
@@ -16,7 +16,6 @@ background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(40px); box-shadow: 0px 4px 16px 0px rgba(84, 92, 143, 0.1); z-index: 100; .back-icon { width: 40px; src/robotaxi/pages/operating-area/index.tsx
@@ -27,7 +27,7 @@ getOpenCity().then((res: any) => { const openCities = res.cities || []; setCityList(openCities); const cachedCityCode = cachedSelectedCity?.code; const matchedCity = openCities.find((city: any) => (city.code || city.adcode) === cachedCityCode); @@ -240,7 +240,7 @@ setRawGeofenceData(normalizedGeofenceData); const firstPolygonId = normalizedGeofenceData.length > 0 ? normalizedGeofenceData[0].mapPolygonId : null; setSelectedPolygonId(firstPolygonId); if (firstPolygonId !== null) { const firstPolygonData = normalizedGeofenceData.find(item => item.mapPolygonId === firstPolygonId); setOperatingTimeInfo(firstPolygonData); @@ -250,14 +250,14 @@ const newPolygons = updatePolygonsStyle(normalizedGeofenceData, firstPolygonId); setPolygons(newPolygons); setPolylines(updatePolylineStyle(normalizedGeofenceData, firstPolygonId)); if (normalizedGeofenceData.length > 0 && normalizedGeofenceData[0]?.points?.length > 0) { const firstPoint = normalizedGeofenceData[0].points[0]; const lat = Number(firstPoint.lat); const lng = Number(firstPoint.lng); setLatitude(lat); setLongitude(lng); const mapCtx = Taro.createMapContext('map'); const points = extractPointsFromPolygons(newPolygons); if (points.length > 0) { @@ -293,7 +293,7 @@ } else if (e.markerId) { tapPolygonId = e.markerId; } if (tapPolygonId !== null && rawGeofenceData.length > 0) { lastPolygonTapTimeRef.current = Date.now(); selectPolygon(tapPolygonId, rawGeofenceData); @@ -326,9 +326,7 @@ }; const toBack = () => { Taro.navigateBack({ delta: 1, }); Taro.navigateBack(); }; const toOrigin = () => { @@ -339,6 +337,57 @@ points, padding: [10, 10, 10, 10] }); } }; const renderOperatingTime = (info: any) => { if (!info?.rule) { return `全天`; } try { const rules = JSON.parse(info.rule); if (!Array.isArray(rules) || rules.length === 0) { return `全天`; } const getDayText = (typeStr: number[]) => { if (!typeStr || typeStr.length === 0 || typeStr.includes(0)) return '每天'; const dayMap = { 1: '周一', 2: '周二', 3: '周三', 4: '周四', 5: '周五', 6: '周六', 7: '周日' }; const days = typeStr.map(d => dayMap[d]).filter(Boolean); if (days.length === 7) return '每天'; return days.join('、'); }; const parts = rules.map((rule: any) => { let prefix = ''; if (rule.type === 0) { prefix = '工作日'; } else if (rule.type === 1) { prefix = '节假日'; } else if (rule.type === 2) { prefix = '非工作日'; } else if (rule.type === 3) { prefix = getDayText(rule.typeStr); } let timeStr = ''; if (rule.type === 3 && (!rule.time || rule.time.length === 0)) { timeStr = '全天'; } else { timeStr = Array.isArray(rule.time) ? rule.time.join(', ') : rule.time; } return `${prefix}: ${timeStr}`; }); const resultStr = parts.join(' '); if (resultStr.includes('工作日: 全天') && resultStr.includes('非工作日: 全天') || resultStr.includes('每天: 全天') && resultStr.includes('每天: 全天')) { return ''; } return resultStr; } catch (e) { console.error('解析围栏规则失败', e); return ``; } }; @@ -357,66 +406,60 @@ onPolygonTap={handlePolygonTap} onTap={handleMapTap} > {/* 返回按钮 */} <View onClick={toBack} className="back-btn"> <Image className="back-icon" src={backIcon} /> </View> </Map> {/* 返回按钮 */} <View onClick={toBack} className="back-btn"> <Image className="back-icon" src={backIcon} /> </View> {/* 底部面板 */} <View className="bottom-sheet"> <Image className="origin-icon" src={originIcon} onClick={toOrigin} /> <View className="header"> <Text className="title">查看运营区域</Text> {/* <Image onClick={toBack} className="close-icon" src={closeIcon} /> */} </View> <ScrollView scrollX className="city-scroll"> <View className="city-list"> {cityList.map((city, index) => { const code = city.code || city.adcode; return ( <View key={index} className={`city-item ${selectedCityCode === code ? 'active' : ''}`} onClick={() => handleSelectCity(code)} > {city.name} </View> ); })} {/* Fallback mock data if cityList is empty */} {cityList.length === 0 && ( <> <View className="city-item active">北京市</View> <View className="city-item">广州市</View> <View className="city-item">佛山市</View> </> )} { cityList.length != 0 && <View className="bottom-sheet"> <Image className="origin-icon" src={originIcon} onClick={toOrigin} /> <View className="header"> <Text className="title">查看运营区域</Text> </View> </ScrollView> <View className="content-box"> <View className="content-title">区域运营时间:</View> <View className="content-time"> 工作日: {operatingTimeInfo?.workDayOpenTime || '07:00'}-{operatingTimeInfo?.workDayCloseTime || '22:00'} 周末: {operatingTimeInfo?.weekendOpenTime || '07:00'}-{operatingTimeInfo?.weekendCloseTime || '22:00'} </View> <View className="content-desc"> <View className="desc-item"> <Text className="index">1.</Text> <Text className="text">在运营时段可正常使用打车服务,非运营时段服务暂停</Text> <ScrollView scrollX className="city-scroll"> <View className="city-list"> {cityList.map((city, index) => { const code = city.code || city.adcode; return ( <View key={index} className={`city-item ${selectedCityCode === code ? 'active' : ''}`} onClick={() => handleSelectCity(code)} > {city.name} </View> ); })} </View> <View className="desc-item"> <Text className="index">2.</Text> <Text className="text">各站点运营时间可能存在调整,实际运营情况请以页面实时展示为准</Text> </ScrollView> <View className="content-box"> <View className="content-title">区域运营时间:</View> <View className="content-time"> {renderOperatingTime(operatingTimeInfo)} </View> <View className="desc-item"> <Text className="index">3.</Text> <Text className="text">如有问题,请拨打客服热线xxxxxx</Text> <View className="content-desc"> <View className="desc-item"> <Text className="index">1.</Text> <Text className="text">在运营时段可正常使用打车服务,非运营时段服务暂停</Text> </View> <View className="desc-item"> <Text className="index">2.</Text> <Text className="text">各站点运营时间可能存在调整,实际运营情况请以页面实时展示为准</Text> </View> <View className="desc-item"> <Text className="index">3.</Text> <Text className="text">如有问题,请拨打客服热线xxxxxx</Text> </View> </View> </View> </View> </View> } </View> ); } src/robotaxi/pages/select-map-site/index.tsx
@@ -455,6 +455,27 @@ }) } // 处理点击地图 marker const handleMarkerTap = (e: any) => { const markerId = e?.detail?.markerId; if (markerId === undefined) return; const targetMarker = markers.find(m => m.id === markerId); if (targetMarker) { const mapCtx = Taro.createMapContext('map'); mapCtx.moveToLocation({ latitude: targetMarker.latitude, longitude: targetMarker.longitude, }); setSelectedSite(targetMarker); setOutOfServiceRange(getOutOfServiceRange(isOperatingArea, targetMarker)); setLatitude(targetMarker.latitude); setLongitude(targetMarker.longitude); setShowToolTip(true); } }; return ( <View className="home-container"> <Map @@ -462,11 +483,12 @@ longitude={longitude} latitude={latitude} scale={14} markers={markers} markers={markers.map(m => ({ ...m, title: '', name: '' }))} polyline={polyline} polygons={polygons} showLocation={!!longitude} onRegionChange={handleRegionChange} onMarkerTap={handleMarkerTap} style={{ width: '100%', height: '100%' }} > {/* 返回 */} src/robotaxi/pages/site-search/index.less
@@ -180,7 +180,7 @@ } .card-content { padding: 16px 24px; padding: 16px 0; } } src/robotaxi/pages/site-search/index.tsx
@@ -460,9 +460,24 @@ } { (!siteList.length && (!searchValue ? !nearbySiteList.length : true) && !nonOperationalSite.length && (!searchValue ? !historySite.length : true)) && <View className='no-data-box'> <Image className='no-data-image' src={require('../../assets/no-data-site.png')} /> <View>暂无站点</View> <View className='history-card'> <View className='card-header'>暂无搜索结果,为您推荐附近站点</View> <View className='card-content list-content'> { filteredNearbyList.slice(0, MAX_EXPANDED_COUNT).map((item, index) => { return <View hoverClass='common-hover-class' onClick={() => onChangeSelectSite(item)} className='history-item' key={item.id || index}> <Image className='history-icon' src={address}></Image> <View className='history-info'> <View className='history-name'>{item.name || '_'}</View> <View className='history-address-box'> <View className='history-address'>{item.address || '-'}</View> <View className='history-distance'>{item.distance}</View> </View> </View> </View> }) } </View> </View> } <TipPopup