uni-app 地图找岗:定位权限、map 组件与半径搜索,从授权到出结果 5 个坑
导读:job-uniapp(小蓝直聘移动端)的"地图找岗"上线后,最多的一类工单是"地图白屏"和"定位在市中心,岗位却在郊区"。把定位权限、map 组件、后端半径搜索串起来走一遍,才发现小程序地图这件事,坑比想象的多。
地图找岗的业务逻辑不复杂:拿到用户坐标,把附近的岗位标在地图上,点标记看详情。但真落地,从"能不能定位"到"搜出来的岗位对不对",每一步都有雷。这篇把我踩过的 5 个坑按链路顺序排出来。
坑 1:定位权限,不是调一下 API 就有的
小程序里uni.getLocation是授权敏感的 API,用户拒绝过一次,后面再调就是失败回调。而且scope.userLocation 的授权状态会变——用户在小程序设置里关掉"位置信息",代码里完全感知不到,只能靠失败回调发现:
// utils/location.jsexportfunctionensureLocation(){returnnewPromise((resolve,reject)=>{uni.getLocation({type:'gcj02',// 国测局坐标,微信地图/高德都用这个isHighAccuracy:true,highAccuracyExpireTime:3000,success:(res)=>resolve({lat:res.latitude,lng:res.longitude}),fail:(err)=>{if(err.errMsg&&err.errMsg.indexOf('auth deny')>-1){// 拒绝授权:引导去设置页开权限uni.showModal({title:'需要位置权限',content:'开启位置权限后才能查看附近岗位',confirmText:'去设置',success:(r)=>{if(r.confirm)uni.openSetting({success:()=>resolve(null)});}});reject('denied');}else{reject('error');}}});});}注意type: 'gcj02'不能省。微信返回的原始坐标是 wgs84(GPS),地图组件和高德接口认的是 gcj02(火星坐标),不转换的话,地图上标出来的点会偏几百米到几公里——这就是"定位在市中心,岗位在郊区"的经典来源。用type: 'gcj02'让微信直接返回火星坐标,省掉手动转换的坑。
坑 2:map 组件白屏,多半是初始化时机
map组件的markers是数组,必须在组件加载完成前/时赋值,等组件渲染完再 setData markers,经常不生效或者白屏。另一个常见问题是:地图容器高度没撑起来(map高度写死 0 或父容器塌陷),组件画不出来:
export default { data() { return { center: { lat: 0, lng: 0 }, markers: [], jobs: [] }; }, onLoad() { this.initMap(); }, async initMap() { const loc = await ensureLocation(); if (!loc) return; this.center = loc; this.fetchNearbyJobs(loc); } }; .map-page { height: 100vh; display: flex; flex-direction: column; } .map-canvas { width: 100%; height: 45vh; } /* 高度必须显式给 */ .job-list { flex: 1; overflow-y: auto; }markers 要带id和callout(气泡),点击才能定位到具体岗位:
constmarkers=jobs.map((j,i)=>({id:j.id,latitude:j.lat,longitude:j.lng,title:j.title,iconPath:'/static/marker.png',width:28,height:34,callout:{content:`${j.title}·${j.salary}元`,color:'#333333',borderRadius:6,padding:6,display:'ALWAYS'}}));坑 3:半径搜索,后端不能只按"每公里 0.01 度"硬算
前端拿到坐标,传给后端查附近岗位。很多初版实现是lat ± 0.01, lng ± 0.01框一个方形——纬度 1 度约 111km,经度 1 度在赤道约 111km、在 30° 纬度约 96km,方形范围在不同纬度差很多,而且方形不是圆。正确做法是球面距离 + 有界索引:
-- jx_job 增加定位字段ALTERTABLEjx_jobADDCOLUMNlatDECIMAL(10,6)NOTNULLDEFAULT0COMMENT'纬度',ADDCOLUMNlngDECIMAL(10,6)NOTNULLDEFAULT0COMMENT'经度';-- 附近岗位查询:先粗筛(方形边界,走索引),再精算(球面距离)SELECTid,title,salary,ROUND(ST_DISTANCE_SPHERE(POINT(#{lng}, #{lat}), POINT(lng, lat))/1000,1)ASdistance_kmFROMjx_jobWHERElatBETWEEN#{latMin} AND #{latMax}ANDlngBETWEEN#{lngMin} AND #{lngMax}ANDstatus=1HAVINGdistance_km<=#{radius}ORDERBYdistance_kmLIMIT50;ST_DISTANCE_SPHERE是 MySQL 8 的内置球面距离函数(米),按地球曲率算,比"0.01 度约等于 1 公里"靠谱得多。粗筛的方形边界用索引扫,精算只在粗筛结果里做,量级可控。注意HAVING里用别名distance_km过滤半径,MySQL 支持 HAVING 用 SELECT 别名,但ORDER BY也别再重复写公式,直接引用别名。
坑 4:经纬度精度,DECIMAL(10,6) 起步
有个真实事故:后端存的坐标是DECIMAL(8,4),前台传的是113.865749, 22.542741,入库被四舍五入成113.8657, 22.5427——看着只差 0.0001 度,实际偏移约 11 米。地图上两个点叠在一起时毫无感知,但距离排序时就出现了"明明在旁边却排到 3 公里外"的诡异结果。坐标字段至少 DECIMAL(10,6)(精度约 0.11 米),接口传输用字符串或保留 6 位小数,别用 float(float 是近似存储)。
坑 5:地图组件在真机上"不显示标记"
现象:开发工具里地图、标记、气泡都正常,一上真机标记全没了,地图空白一片。
排查过程:控制台没报错,markers数据在 setData 里也确认有值。翻社区发现同款问题的帖子不少,共同点是用了本地图片路径做iconPath。
定位思路:小程序 map 组件的iconPath对本地图片的支持在部分机型上有兼容问题——iOS 个别版本不认static/marker.png这种相对路径,只认 base64 或网络图片。
最终解决:把标记图标换成base64 编码(小图标十几 KB,可以接受),或者上传到 CDN 用完整https://地址:
// 方案A:base64 内联(最稳,不依赖网络)iconPath:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAC...',// 方案B:CDN 绝对地址iconPath:'https://cdn.qinkonglan.cn/static/marker.png',换完真机复测,标记正常显示。这条经验也提醒我:地图相关组件,必须真机回归,开发工具模拟不了真机的渲染差异。
可以直接抄的结论
- 定位授权:
type: 'gcj02'必须带,拒绝授权后uni.openSetting引导重开; - map 组件:高度显式给,
markers初始化时赋值,callout带 content 才能点; - 半径搜索:方形粗筛(走索引)+
ST_DISTANCE_SPHERE精算,HAVING distance_km <= radius; - 坐标精度:
DECIMAL(10,6),接口保留 6 位小数,别用 float; - 标记图标:真机上用 base64 或 CDN 绝对地址,别用本地相对路径;
- 任何地图改动都要真机回归一遍。
地图找岗看着是"加个地图页",实际是定位授权、坐标转换、存储精度、距离算法、渲染兼容五件事的接力。把这条链路每一环都钉死,白屏和"岗位在郊区"的工单才能绝迹。
项目源码:https://gitee.com/gzqkl/job-uniapp