feat(robotaxi): 实现主页地图及交互组件
- 添加高德地图 SDK 及地图组件,集成定位和 POI 查询
- 新增隐私协议弹窗,支持多协议勾选和状态管理
- 实现可拖拽内容面板,包含站点选择、活动展示和产品信息
- 添加地图浮层入口、指针动画、复位按钮等交互元素
- 引入相关图片资源,更新页面配置和路由顺序
| | |
| | | if (process.env.TARO_ENV === 'weapp') { |
| | | pages = [ |
| | | // 'pages/home/index', |
| | | 'pages/robotaxi/home/index', |
| | | 'pages/xpeng/index', |
| | | // 'pages/driveNew/index', |
| | | 'pages/user/index', |
| | | 'pages/user/login', |
| | | 'pages/map/index', |
| | | 'pages/robotaxi/home/index', |
| | | ]; |
| | | |
| | | subPackages = [ |
| | |
| | | export const API_REGISTER_REPORT_POSITION= `${_MINI_HOST}/api/register/report` |
| | | |
| | | |
| | | // 高德地图KEY |
| | | export const AMAP_KEY = 'e5212ad7656a00a09b9968dcbe551726' |
| | | |
| | | |
| New file |
| | |
| | | function AMapWX(a){this.key=a.key;this.requestConfig={key:a.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};this.MeRequestConfig={key:a.key,serviceName:"https://restapi.amap.com/rest/me"}} |
| | | AMapWX.prototype.getWxLocation=function(a,b){wx.getLocation({type:"gcj02",success:function(c){c=c.longitude+","+c.latitude;wx.setStorage({key:"userLocation",data:c});b(c)},fail:function(c){wx.getStorage({key:"userLocation",success:function(d){d.data&&b(d.data)}});a.fail({errCode:"0",errMsg:c.errMsg||""})}})}; |
| | | AMapWX.prototype.getMEKeywordsSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.keywords&&(d.keywords=b.keywords);b.city&&(d.city=b.city);b.filter&&(d.filter=b.filter);b.sortrule&&(d.sortrule=b.sortrule);b.pageNum&&(d.pageNum=b.pageNum);b.pageSize&&(d.pageSize=b.pageSize);b.sig&&(d.sig= |
| | | b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/local",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})}; |
| | | AMapWX.prototype.getMEIdSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.id&&(d.id=b.id);b.sig&&(d.sig=b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/id",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&& |
| | | 0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})}; |
| | | AMapWX.prototype.getMEPolygonSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.keywords&&(d.keywords=b.keywords);b.polygon&&(d.polygon=b.polygon);b.filter&&(d.filter=b.filter);b.sortrule&&(d.sortrule=b.sortrule);b.pageNum&&(d.pageNum=b.pageNum);b.pageSize&&(d.pageSize=b.pageSize); |
| | | b.sig&&(d.sig=b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/polygon",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})}; |
| | | AMapWX.prototype.getMEaroundSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.keywords&&(d.keywords=b.keywords);b.center&&(d.center=b.center);b.radius&&(d.radius=b.radius);b.filter&&(d.filter=b.filter);b.sortrule&&(d.sortrule=b.sortrule);b.pageNum&&(d.pageNum=b.pageNum);b.pageSize&& |
| | | (d.pageSize=b.pageSize);b.sig&&(d.sig=b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/around",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})}; |
| | | AMapWX.prototype.getGeo=function(a){var b=this.requestConfig,c=a.options;b={key:this.key,extensions:"all",s:b.s,platform:b.platform,appname:this.key,sdkversion:b.sdkversion,logversion:b.logversion};c.address&&(b.address=c.address);c.city&&(b.city=c.city);c.batch&&(b.batch=c.batch);c.sig&&(b.sig=c.sig);wx.request({url:"https://restapi.amap.com/v3/geocode/geo",data:b,method:"GET",header:{"content-type":"application/json"},success:function(d){(d=d.data)&&d.status&&"1"===d.status?a.success(d):a.fail({errCode:"0", |
| | | errMsg:d})},fail:function(d){a.fail({errCode:"0",errMsg:d.errMsg||""})}})}; |
| | | AMapWX.prototype.getRegeo=function(a){function b(d){var e=c.requestConfig;wx.request({url:"https://restapi.amap.com/v3/geocode/regeo",data:{key:c.key,location:d,extensions:"all",s:e.s,platform:e.platform,appname:c.key,sdkversion:e.sdkversion,logversion:e.logversion},method:"GET",header:{"content-type":"application/json"},success:function(g){if(g.data.status&&"1"==g.data.status){g=g.data.regeocode;var h=g.addressComponent,f=[],k=g.roads[0].name+"\u9644\u8fd1",m=d.split(",")[0],n=d.split(",")[1];if(g.pois&& |
| | | g.pois[0]){k=g.pois[0].name+"\u9644\u8fd1";var l=g.pois[0].location;l&&(m=parseFloat(l.split(",")[0]),n=parseFloat(l.split(",")[1]))}h.provice&&f.push(h.provice);h.city&&f.push(h.city);h.district&&f.push(h.district);h.streetNumber&&h.streetNumber.street&&h.streetNumber.number?(f.push(h.streetNumber.street),f.push(h.streetNumber.number)):f.push(g.roads[0].name);f=f.join("");a.success([{iconPath:a.iconPath,width:a.iconWidth,height:a.iconHeight,name:f,desc:k,longitude:m,latitude:n,id:0,regeocodeData:g}])}else a.fail({errCode:g.data.infocode, |
| | | errMsg:g.data.info})},fail:function(g){a.fail({errCode:"0",errMsg:g.errMsg||""})}})}var c=this;a.location?b(a.location):c.getWxLocation(a,function(d){b(d)})}; |
| | | AMapWX.prototype.getWeather=function(a){function b(g){var h="base";a.type&&"forecast"==a.type&&(h="all");wx.request({url:"https://restapi.amap.com/v3/weather/weatherInfo",data:{key:d.key,city:g,extensions:h,s:e.s,platform:e.platform,appname:d.key,sdkversion:e.sdkversion,logversion:e.logversion},method:"GET",header:{"content-type":"application/json"},success:function(f){if(f.data.status&&"1"==f.data.status)if(f.data.lives){if((f=f.data.lives)&&0<f.length){f=f[0];var k={city:{text:"\u57ce\u5e02",data:f.city}, |
| | | weather:{text:"\u5929\u6c14",data:f.weather},temperature:{text:"\u6e29\u5ea6",data:f.temperature},winddirection:{text:"\u98ce\u5411",data:f.winddirection+"\u98ce"},windpower:{text:"\u98ce\u529b",data:f.windpower+"\u7ea7"},humidity:{text:"\u6e7f\u5ea6",data:f.humidity+"%"}};k.liveData=f;a.success(k)}}else f.data.forecasts&&f.data.forecasts[0]&&a.success({forecast:f.data.forecasts[0]});else a.fail({errCode:f.data.infocode,errMsg:f.data.info})},fail:function(f){a.fail({errCode:"0",errMsg:f.errMsg||""})}})} |
| | | function c(g){wx.request({url:"https://restapi.amap.com/v3/geocode/regeo",data:{key:d.key,location:g,extensions:"all",s:e.s,platform:e.platform,appname:d.key,sdkversion:e.sdkversion,logversion:e.logversion},method:"GET",header:{"content-type":"application/json"},success:function(h){if(h.data.status&&"1"==h.data.status){h=h.data.regeocode;if(h.addressComponent)var f=h.addressComponent.adcode;else h.aois&&0<h.aois.length&&(f=h.aois[0].adcode);b(f)}else a.fail({errCode:h.data.infocode,errMsg:h.data.info})}, |
| | | fail:function(h){a.fail({errCode:"0",errMsg:h.errMsg||""})}})}var d=this,e=d.requestConfig;a.city?b(a.city):d.getWxLocation(a,function(g){c(g)})}; |
| | | AMapWX.prototype.getPoiAround=function(a){function b(e){e={key:c.key,location:e,s:d.s,platform:d.platform,appname:c.key,sdkversion:d.sdkversion,logversion:d.logversion};a.querytypes&&(e.types=a.querytypes);a.querykeywords&&(e.keywords=a.querykeywords);wx.request({url:"https://restapi.amap.com/v3/place/around",data:e,method:"GET",header:{"content-type":"application/json"},success:function(g){if(g.data.status&&"1"==g.data.status){if((g=g.data)&&g.pois){for(var h=[],f=0;f<g.pois.length;f++){var k=0== |
| | | f?a.iconPathSelected:a.iconPath;h.push({latitude:parseFloat(g.pois[f].location.split(",")[1]),longitude:parseFloat(g.pois[f].location.split(",")[0]),iconPath:k,width:22,height:32,id:f,name:g.pois[f].name,address:g.pois[f].address})}a.success({markers:h,poisData:g.pois})}}else a.fail({errCode:g.data.infocode,errMsg:g.data.info})},fail:function(g){a.fail({errCode:"0",errMsg:g.errMsg||""})}})}var c=this,d=c.requestConfig;a.location?b(a.location):c.getWxLocation(a,function(e){b(e)})}; |
| | | AMapWX.prototype.getStaticmap=function(a){function b(e){c.push("location="+e);a.zoom&&c.push("zoom="+a.zoom);a.size&&c.push("size="+a.size);a.scale&&c.push("scale="+a.scale);a.markers&&c.push("markers="+a.markers);a.labels&&c.push("labels="+a.labels);a.paths&&c.push("paths="+a.paths);a.traffic&&c.push("traffic="+a.traffic);e="https://restapi.amap.com/v3/staticmap?"+c.join("&");a.success({url:e})}var c=[];c.push("key="+this.key);var d=this.requestConfig;c.push("s="+d.s);c.push("platform="+d.platform); |
| | | c.push("appname="+d.appname);c.push("sdkversion="+d.sdkversion);c.push("logversion="+d.logversion);a.location?b(a.location):this.getWxLocation(a,function(e){b(e)})}; |
| | | AMapWX.prototype.getInputtips=function(a){var b=Object.assign({},this.requestConfig);a.location&&(b.location=a.location);a.keywords&&(b.keywords=a.keywords);a.type&&(b.type=a.type);a.city&&(b.city=a.city);a.citylimit&&(b.citylimit=a.citylimit);wx.request({url:"https://restapi.amap.com/v3/assistant/inputtips",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.tips&&a.success({tips:c.data.tips})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg|| |
| | | ""})}})}; |
| | | AMapWX.prototype.getDrivingRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);a.strategy&&(b.strategy=a.strategy);a.waypoints&&(b.waypoints=a.waypoints);a.avoidpolygons&&(b.avoidpolygons=a.avoidpolygons);a.avoidroad&&(b.avoidroad=a.avoidroad);wx.request({url:"https://restapi.amap.com/v3/direction/driving",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&a.success({paths:c.data.route.paths, |
| | | taxi_cost:c.data.route.taxi_cost||""})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})}; |
| | | AMapWX.prototype.getWalkingRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);wx.request({url:"https://restapi.amap.com/v3/direction/walking",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&a.success({paths:c.data.route.paths})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})}; |
| | | AMapWX.prototype.getTransitRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);a.strategy&&(b.strategy=a.strategy);a.city&&(b.city=a.city);a.cityd&&(b.cityd=a.cityd);wx.request({url:"https://restapi.amap.com/v3/direction/transit/integrated",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&(c=c.data.route,a.success({distance:c.distance||"",taxi_cost:c.taxi_cost|| |
| | | "",transits:c.transits}))},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})}; |
| | | AMapWX.prototype.getRidingRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);wx.request({url:"https://restapi.amap.com/v3/direction/riding",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&a.success({paths:c.data.route.paths})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})};module.exports.AMapWX=AMapWX; |
| | |
| | | color: #FFFFFF; |
| | | } |
| | | |
| | | .btn-no-agree { |
| | | background: rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .close-txt { |
| | | text-align: center; |
| | | margin-top: 40px; |
| | |
| | | import { View, Text, Image } from '@tarojs/components'; |
| | | import { useEffect, useState } from 'react'; |
| | | import './index.less'; |
| | | import check from '../../../../robotaxi/assets/check.png' |
| | | import uncheck from '../../../../robotaxi/assets/uncheck.png' |
| | | import { privicyShowManager } from './utils' |
| | | |
| | | type Props = { |
| | | show?: boolean; |
| | |
| | | export default function PrivacyPopup({ show = false, onClose, onAgree }: Props) { |
| | | const [visible, setVisible] = useState(false); |
| | | const [animationData, setAnimationData] = useState<any>(); |
| | | const [isAgree, setIsAgree] = useState({ |
| | | 0: false, |
| | | 1: false, |
| | | 2: false, |
| | | }); |
| | | const [isAllAgree, setIsAllAgree] = useState(false); |
| | | |
| | | useEffect(() => { |
| | | if (show && !visible) { |
| | |
| | | } |
| | | }, [show]); |
| | | |
| | | useEffect(() => { |
| | | setIsAllAgree(Object.values(isAgree).every((item) => item)); |
| | | }, [isAgree]); |
| | | |
| | | const handleClose = () => { |
| | | privicyShowManager.setHasShow(true) |
| | | onClose?.(); |
| | | }; |
| | | |
| | | const handleAgree = () => { |
| | | if (!isAllAgree) { |
| | | Taro.showToast({ title: '请先同意全部协议', icon: 'none' }); |
| | | return |
| | | }; |
| | | privicyShowManager.setHasShow(true) |
| | | onAgree?.(); |
| | | }; |
| | | |
| | | const handleLinkClick = (url: string) => { |
| | | }; |
| | | |
| | | const checkItem = (key: keyof typeof isAgree) => () => { |
| | | setIsAgree({ |
| | | ...isAgree, |
| | | [key]: !isAgree[key], |
| | | }) |
| | | } |
| | | |
| | | const checkAll = () => { |
| | | setIsAllAgree(!isAllAgree); |
| | | setIsAgree({ |
| | | 0: !isAllAgree, |
| | | 1: !isAllAgree, |
| | | 2: !isAllAgree, |
| | | }) |
| | | } |
| | | |
| | | if (!visible) return null; |
| | | |
| | |
| | | </View> |
| | | |
| | | <View className='privacy-link'> |
| | | { |
| | | Object.keys(isAgree).map((key) => ( |
| | | <View className='link-item' key={key}> |
| | | <Image onClick={checkItem(key)} className='item-radio' src={isAgree[key] ? check : uncheck} |
| | | mode='aspectFill' /> |
| | | <Text className='item-name'>《 {key === '0' ? '用户服务协议' : '小鹏Robotaxi 平台隐私政策'} 》</Text> |
| | | </View> |
| | | )) |
| | | } |
| | | <View className='link-item'> |
| | | <Image className='item-radio' src={`https://s.xiaopeng.com/xp-fe/mini/common/xp-logo-circle-20250728.svg`} |
| | | mode='aspectFill' /> |
| | | <Text className='item-name'>《 用户服务协议 》</Text> |
| | | </View> |
| | | <View className='link-item'> |
| | | <Image className='item-radio' src={`https://s.xiaopeng.com/xp-fe/mini/common/xp-logo-circle-20250728.svg`} |
| | | mode='aspectFill' /> |
| | | <Text className='item-name'>《 小鹏Robotaxi 平台隐私政策 》</Text> |
| | | </View> |
| | | <View className='link-item'> |
| | | <Image className='item-radio' src={`https://s.xiaopeng.com/xp-fe/mini/common/xp-logo-circle-20250728.svg`} |
| | | mode='aspectFill' /> |
| | | <Text className='item-name'>《 小鹏Robotaxi 平台隐私政策 》</Text> |
| | | </View> |
| | | <View className='link-item'> |
| | | <Image className='item-radio' src={`https://s.xiaopeng.com/xp-fe/mini/common/xp-logo-circle-20250728.svg`} |
| | | mode='aspectFill' /> |
| | | <Text className='item-name'>《 小鹏Robotaxi 平台隐私政策 》</Text> |
| | | </View> |
| | | <View className='link-item'> |
| | | <Image className='item-radio' src={`https://s.xiaopeng.com/xp-fe/mini/common/xp-logo-circle-20250728.svg`} |
| | | <Image onClick={checkAll} className='item-radio' src={isAllAgree ? check : uncheck} |
| | | mode='aspectFill' /> |
| | | <Text className='item-name'>全部勾选</Text> |
| | | </View> |
| | | </View> |
| | | <View className='agree-btn' onClick={handleAgree}> |
| | | |
| | | <View className={'agree-btn ' + (!isAllAgree && 'btn-no-agree')} onClick={handleAgree}> |
| | | 同意并进入 |
| | | </View> |
| | | <View className='close-txt'>暂不同意,留在游客模式</View> |
| | | |
| | | <View className='close-txt' onClick={handleClose}>暂不同意,留在游客模式</View> |
| | | </View> |
| | | </View> |
| | | ) |
| | |
| | | return Object.values(privacyUrls).includes(url) |
| | | } |
| | | |
| | | const STORAGE_KEY = 'has_show_privicy_v1' |
| | | const STORAGE_KEY = 'has_show_privicy_Robotaxi' |
| | | export const privicyShowManager = { |
| | | getHasShow: () => { |
| | | try { |
| | |
| | | export default { |
| | | navigationStyle: "custom" |
| | | navigationStyle: "custom", |
| | | disableScroll: true |
| | | }; |
| | |
| | | .demo { |
| | | text-align: center; |
| | | .select-city { |
| | | position: fixed; |
| | | top: 124px; |
| | | left: 40px; |
| | | display: flex; |
| | | align-items: center; |
| | | font-size: 28px; |
| | | font-weight: bold; |
| | | line-height: 48px; |
| | | letter-spacing: normal; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | |
| | | .select-city-icon { |
| | | width: 28px; |
| | | height: 28px; |
| | | margin-left: 4px; |
| | | } |
| | | } |
| | | |
| | | .map-entry { |
| | | position: fixed; |
| | | top: 236px; |
| | | right: 32px; |
| | | display: flex; |
| | | align-items: center; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | border-radius: 138px; |
| | | background: rgba(255, 255, 255, 1); |
| | | overflow: hidden; |
| | | padding: 8px 12px; |
| | | |
| | | .map-entry-icon { |
| | | width: 64px; |
| | | height: 64px; |
| | | } |
| | | |
| | | .split-line { |
| | | width: 44px; |
| | | height: 1px; |
| | | background-color: rgba(0, 0, 0, 0.1); |
| | | } |
| | | } |
| | | |
| | | .pointer-box { |
| | | position: absolute; |
| | | top: 50%; |
| | | left: 50%; |
| | | transform: translate(-50%, -100%); |
| | | display: flex; |
| | | align-items: center; |
| | | flex-direction: column; |
| | | justify-content: center; |
| | | |
| | | .toolTip { |
| | | |
| | | .toolTip-bg { |
| | | background-image: url(../../../robotaxi/assets/tooltip-bg.png); |
| | | background-size: cover; |
| | | height: 70px; |
| | | min-width: 155px; |
| | | text-align: center; |
| | | width: fit-content; |
| | | |
| | | .toolTip-bg-text { |
| | | font-size: 22px; |
| | | font-weight: 500; |
| | | letter-spacing: normal; |
| | | color: #FFFFFF; |
| | | line-height: 48px; |
| | | } |
| | | } |
| | | |
| | | .toolTip-txt { |
| | | margin-top: -26px; |
| | | display: flex; |
| | | align-items: center; |
| | | border-radius: 16px; |
| | | background: rgba(255, 255, 255, 0.9); |
| | | backdrop-filter: blur(40px); |
| | | box-shadow: 0px 4px 16px 0px rgba(45, 48, 64, 0.2); |
| | | padding: 12px 12px 12px 20px; |
| | | |
| | | .toolTip-txt-content { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | letter-spacing: normal; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | line-height: 48px; |
| | | } |
| | | |
| | | .toolTip-icon { |
| | | margin-left: 8px; |
| | | width: 32px; |
| | | height: 32px; |
| | | flex-shrink: 0; |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | .pointer-icon { |
| | | width: 52px; |
| | | height: 76px; |
| | | margin-top: -54px; |
| | | |
| | | &.bounce { |
| | | animation: pointer-bounce 600ms ease-in-out; |
| | | will-change: transform; |
| | | } |
| | | } |
| | | } |
| | | |
| | | @keyframes pointer-bounce { |
| | | 0% { |
| | | transform: translateY(0); |
| | | } |
| | | |
| | | 30% { |
| | | transform: translateY(-16px); |
| | | } |
| | | |
| | | 60% { |
| | | transform: translateY(0); |
| | | } |
| | | |
| | | 100% { |
| | | transform: translateY(0); |
| | | } |
| | | } |
| | | |
| | | |
| | | .home-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 100vh; |
| | | overflow: hidden; |
| | | position: relative; |
| | | |
| | | .origin-box { |
| | | position: absolute; |
| | | bottom: 72px; |
| | | right: 32px; |
| | | z-index: 101; |
| | | |
| | | .origin-icon { |
| | | width: 88px; |
| | | height: 88px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .content-box { |
| | | width: 100%; |
| | | z-index: 100; |
| | | background-color: transparent; |
| | | overflow: hidden; |
| | | margin-top: -52px; |
| | | border-radius: 32px 32px 0 0; |
| | | |
| | | .select-site { |
| | | position: relative; |
| | | padding: 32px; |
| | | padding-top: 40px; |
| | | background: #F2F3F7; |
| | | border-top: 2px solid #fff; |
| | | border-radius: 32px 32px 0 0; |
| | | |
| | | .site-top-bg { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | height: 326px; |
| | | } |
| | | |
| | | .placeholder-box { |
| | | width: 100%; |
| | | height: 326px; |
| | | } |
| | | |
| | | .site-boarding-point { |
| | | border-radius: 24px; |
| | | background: rgba(255, 255, 255, 0.9); |
| | | box-sizing: border-box; |
| | | border: 1px solid #FFFFFF; |
| | | backdrop-filter: blur(20px); |
| | | box-shadow: 0px 40px 60px 0px rgba(87, 99, 147, 0.1); |
| | | padding: 24px; |
| | | |
| | | .now-site { |
| | | display: flex; |
| | | align-items: center; |
| | | padding-left: 28px; |
| | | |
| | | .now-site-dot { |
| | | width: 12px; |
| | | height: 12px; |
| | | border-radius: 50%; |
| | | background: #6D7FD9; |
| | | margin-right: 24px; |
| | | } |
| | | |
| | | .now-site-address { |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .now-site-address-content { |
| | | font-size: 28px; |
| | | font-weight: bold; |
| | | line-height: 44px; |
| | | letter-spacing: normal; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | |
| | | .now-site-address-text { |
| | | margin: 0 2px; |
| | | color: #6D7FD9; |
| | | } |
| | | } |
| | | |
| | | .now-site-icon { |
| | | margin-left: 2px; |
| | | width: 32px; |
| | | height: 32px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .to-site { |
| | | margin-top: 22px; |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 20px 0 20px 28px; |
| | | border-radius: 20px; |
| | | background: #F1F2F6; |
| | | |
| | | .to-site-dot { |
| | | width: 12px; |
| | | height: 12px; |
| | | border-radius: 50%; |
| | | background: #8CB654; |
| | | margin-right: 24px; |
| | | } |
| | | |
| | | .to-site-address { |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .to-site-text { |
| | | font-size: 32px; |
| | | font-weight: bold; |
| | | line-height: 48px; |
| | | letter-spacing: normal; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | margin-right: 22px; |
| | | } |
| | | |
| | | .to-site-fast { |
| | | border-radius: 12px; |
| | | background: #FFFFFF; |
| | | padding: 8px 21px; |
| | | font-size: 24px; |
| | | font-weight: normal; |
| | | line-height: 48px; |
| | | letter-spacing: normal; |
| | | color: #3D3D3D; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .activity { |
| | | margin-top: 24px; |
| | | |
| | | .activity-item { |
| | | margin-bottom: 24px; |
| | | border-top: 2px solid #fff; |
| | | |
| | | .activity-img { |
| | | border-radius: 32px; |
| | | width: 100%; |
| | | height: 190px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .product-name { |
| | | text-align: center; |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | line-height: 40px; |
| | | letter-spacing: normal; |
| | | background: linear-gradient(99deg, #B7BEDC -8%, #CDD2EB 56%, #C7CDE7 92%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | text-fill-color: transparent; |
| | | } |
| | | } |
| | |
| | | import { useEffect, useState } from 'react'; |
| | | import { View } from '@tarojs/components'; |
| | | import Taro from "@tarojs/taro" |
| | | import { useEffect, useState, useRef } from 'react'; |
| | | import { View, Map, Text, Image } from '@tarojs/components'; |
| | | import './index.less'; |
| | | import PrivacyPopup from '../components/privacy-popup'; |
| | | import { AMAP_KEY } from '../../../constants/api'; |
| | | import selectCityIcon from '../../../robotaxi/assets/select-city.png' |
| | | import contactService from '../../../robotaxi/assets/contact-service.png' |
| | | import coupon from '../../../robotaxi/assets/coupon.png' |
| | | import suggest from '../../../robotaxi/assets/suggest.png' |
| | | import originIcon from '../../../robotaxi/assets/origin-icon.png' |
| | | import pointer from '../../../robotaxi/assets/pointer.png' |
| | | import rightIcon from '../../../robotaxi/assets/right-icon.png' |
| | | import siteTopBg from '../../../robotaxi/assets/site-top-bg.png' |
| | | import amapFile from '../../../libs/amap-wx.130' |
| | | |
| | | export default function Home() { |
| | | |
| | | const [showPrivacy, setShowPrivacy] = useState(true); |
| | | const [showPrivacy, setShowPrivacy] = useState(false); |
| | | const [longitude, setLongitude] = useState<number | null>(null); |
| | | const [latitude, setLatitude] = useState<number | null>(null); |
| | | const [showToolTip, setShowToolTip] = useState(true); |
| | | const [bounce, setBounce] = useState(false); |
| | | |
| | | const [boxHeight, setBoxHeight] = useState<number | string>('auto'); |
| | | const startY = useRef(0); |
| | | const startHeight = useRef(0); |
| | | const fullHeight = useRef(0); |
| | | const minHeight = useRef(0); |
| | | const isDragging = useRef(false); |
| | | |
| | | useEffect(() => { |
| | | setShowPrivacy(Taro.getStorageSync('has_show_privicy_Robotaxi') != 'true'); |
| | | |
| | | // 计算活动区域高度,用于初始隐藏 |
| | | setTimeout(() => { |
| | | const query = Taro.createSelectorQuery(); |
| | | query.select('.content-box').boundingClientRect(rect => { |
| | | if (rect) { |
| | | fullHeight.current = rect.height; |
| | | const query2 = Taro.createSelectorQuery(); |
| | | query2.select('.site-boarding-point').boundingClientRect(pointRect => { |
| | | if (pointRect) { |
| | | //计算显示站点停靠点所需的高度 |
| | | //pointRect.bottom 是元素相对于视口的底部边缘 |
| | | //rect.top 是内容框相对于视口的顶部边缘 |
| | | //添加一些边距(例如从 select - site 边距处增加 152 像素)以使其视觉效果更佳 |
| | | minHeight.current = (pointRect.bottom - rect.top) + 152; |
| | | setBoxHeight(minHeight.current); |
| | | } else { |
| | | minHeight.current = 536; |
| | | setBoxHeight(536); |
| | | } |
| | | }).exec(); |
| | | } |
| | | }).exec(); |
| | | }, 500); |
| | | |
| | | Taro.getLocation({ type: 'gcj02' }) |
| | | .then(res => { |
| | | setLatitude(res.latitude); |
| | | setLongitude(res.longitude); |
| | | const myAmapFun = new amapFile.AMapWX({ key: AMAP_KEY }); |
| | | myAmapFun.getPoiAround({ |
| | | location: `${res.latitude},${res.longitude}`, |
| | | success: () => { }, |
| | | fail: () => { } |
| | | }); |
| | | }) |
| | | .catch(() => { }); |
| | | }, []); |
| | | |
| | | const handleClosePrivacy = () => { |
| | | setShowPrivacy(false); |
| | |
| | | |
| | | const handleAgreePrivacy = () => { |
| | | // 处理用户同意逻辑 |
| | | console.log('用户同意了隐私政策'); |
| | | setShowPrivacy(false); |
| | | }; |
| | | |
| | | return ( |
| | | <View> |
| | | 11 |
| | | <PrivacyPopup |
| | | const toOrigin = () => { |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | mapCtx.moveToLocation(); |
| | | } |
| | | |
| | | const handleRegionChange = (e: any) => { |
| | | const type = e?.type; |
| | | if (type === 'begin') { |
| | | setShowToolTip(false); |
| | | } else if (type === 'end') { |
| | | setBounce(true); |
| | | setTimeout(() => { |
| | | setBounce(false); |
| | | setShowToolTip(true); |
| | | }, 600); |
| | | } |
| | | } |
| | | |
| | | const handleTouchStart = (e: any) => { |
| | | isDragging.current = true; |
| | | startY.current = e.touches[0].clientY; |
| | | startHeight.current = typeof boxHeight === 'number' ? boxHeight : fullHeight.current; |
| | | } |
| | | |
| | | const handleTouchMove = (e: any) => { |
| | | if (!isDragging.current) return; |
| | | const deltaY = e.touches[0].clientY - startY.current; |
| | | let newHeight = startHeight.current - deltaY; |
| | | |
| | | if (newHeight < minHeight.current) newHeight = minHeight.current; |
| | | if (newHeight > fullHeight.current) newHeight = fullHeight.current; |
| | | |
| | | setBoxHeight(newHeight); |
| | | } |
| | | |
| | | const handleTouchEnd = () => { |
| | | isDragging.current = false; |
| | | const movableRange = fullHeight.current - minHeight.current; |
| | | if (typeof boxHeight === 'number' && boxHeight > minHeight.current + movableRange / 2) { |
| | | setBoxHeight(fullHeight.current); |
| | | } else { |
| | | setBoxHeight(minHeight.current); |
| | | } |
| | | } |
| | | |
| | | return ( |
| | | <View className="home-container"> |
| | | <Map |
| | | id="map" |
| | | longitude={longitude ?? 113.32452} |
| | | latitude={latitude ?? 23.099994} |
| | | scale={14} |
| | | showLocation |
| | | onRegionChange={handleRegionChange} |
| | | style={{ width: '100%', flex: 1 }} |
| | | > |
| | | {/* 选择城市 */} |
| | | <View className="select-city"> |
| | | <Text>成都</Text> |
| | | <Image className="select-city-icon" src={selectCityIcon} /> |
| | | </View> |
| | | {/* 地图浮层入口 */} |
| | | <View className="map-entry"> |
| | | <Image className="map-entry-icon" src={contactService} /> |
| | | <View className="split-line"></View> |
| | | <Image className="map-entry-icon" src={coupon} /> |
| | | <View className="split-line"></View> |
| | | <Image className="map-entry-icon" src={suggest} /> |
| | | </View> |
| | | {/* 指针 */} |
| | | <View className="pointer-box"> |
| | | {showToolTip && ( |
| | | <View className="toolTip"> |
| | | <View className="toolTip-bg"> |
| | | <Text className="toolTip-bg-text">限停3分钟</Text> |
| | | </View> |
| | | <View className="toolTip-txt"> |
| | | <View className="toolTip-txt-content">中国南方电网·2号门</View> |
| | | <Image className="toolTip-icon" src={rightIcon} /> |
| | | </View> |
| | | </View> |
| | | )} |
| | | <Image className={`pointer-icon ${bounce ? 'bounce' : ''}`} src={pointer} /> |
| | | </View> |
| | | {/* 复位按钮 */} |
| | | <View className="origin-box"> |
| | | <Image onClick={toOrigin} className="origin-icon" src={originIcon} /> |
| | | </View> |
| | | </Map> |
| | | {/* 选择站点 */} |
| | | <View |
| | | className="content-box" |
| | | style={{ height: typeof boxHeight === 'number' ? `${boxHeight}px` : 'auto', transition: isDragging.current ? 'none' : 'height 0.3s ease-out' }} |
| | | onTouchStart={handleTouchStart} |
| | | onTouchMove={handleTouchMove} |
| | | onTouchEnd={handleTouchEnd} |
| | | > |
| | | <View className="select-site"> |
| | | <Image className="site-top-bg" src={siteTopBg} /> |
| | | <View className="site-boarding-point"> |
| | | {/* 起点 */} |
| | | <View className="now-site"> |
| | | <View className="now-site-dot"></View> |
| | | <View className="now-site-address"> |
| | | <View className="now-site-address-content"> |
| | | 从<Text className="now-site-address-text">中国南方电网·2号门</Text>上车 |
| | | </View> |
| | | <Image className="now-site-icon" src={rightIcon} /> |
| | | </View> |
| | | </View> |
| | | {/* 终点 */} |
| | | <View className="to-site"> |
| | | <View className="to-site-dot"></View> |
| | | <View className="to-site-address"> |
| | | <View className="to-site-text">你想去哪?</View> |
| | | <View className="to-site-fast">去:体育西·珠江新城</View> |
| | | </View> |
| | | </View> |
| | | </View> |
| | | {/* 运营活动 */} |
| | | <View className="activity"> |
| | | { |
| | | [1, 2].map(item => ( |
| | | <View key={item} className="activity-item"> |
| | | <Image className="activity-img" src={'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/1bcd9db5-c909-4edc-a3d2-61cc33c54462.jpg'} /> |
| | | </View> |
| | | )) |
| | | } |
| | | </View> |
| | | {/* 产品名 */} |
| | | <View className="product-name">XPENG Robotaxi</View> |
| | | </View> |
| | | </View> |
| | | {/* 隐私政策弹窗 */} |
| | | <PrivacyPopup |
| | | show={showPrivacy} |
| | | onClose={handleClosePrivacy} |
| | | onAgree={handleAgreePrivacy} |
| | | /> |
| | | </View> |
| | | ) |
| | | </View> |
| | | ) |
| | | } |