zhouruizhe
main
初始视野
开屏定位
需要说明:地图漂到几内亚湾不是「开屏没请求定位」造成的,而是没有 fix 时 (0, 0) 被写进 viewport。堵住它的是 isTrustworthyCoordinate(7ef4094), 开屏定位是叠在守卫之上的体验改进,不是替代 —— 用户拒权、模拟器没设位置、 室内超时都还是拿不到 fix。
初始视野 - 新增 computeClusterViewport:把全部点位当一个聚类,中心取算术中心 (跟点位密度走,不像外接矩形中心那样被个别远点拽偏),scale 由聚类跨度 反推出「一眼看全」的档位。当前 30 个点位算出 (113.92754, 22.76105) scale 12 —— 原来的 scale 11 会把光明区缩成一小块,留一圈空白。 - 视野改成运行时实时算,不再读数据集里手写的 defaultViewport:增删点位之后 手写值会过期,算出来的不会。手写值降级为点位为空时的兜底。 - 「回到全域」用同一个聚类视野。 - loadDataset 里判断「视野还没被用户动过」原来是硬编码 113.935/22.748/11 三个字面量,跟 DEFAULT_GUANGMING_VIEWPORT 重复;改成直接跟常量比。 开屏定位 - 新增 restoreLocationOnLaunch:仅当 scope.userLocation 已授权时静默定位并 居中,蓝点直接出现在用户位置上。未决定/已拒绝一律不碰,避免小程序第一帧 就弹微信授权框、拒绝后再连弹一个引导框;那两种状态留给定位按钮。 - 静默定位只在用户确实在光明区包络内才居中,否则把地图甩到没有任何点位的 地方比停在聚类视野更糟。 - 静默失败不弹提示,但仍写入终态,否则 status 卡在 locating、定位按钮 永远显示「定位中…」。 - 新增 isWithinGuangmingArea,与数据校验共用同一个包络定义。 需要说明:地图漂到几内亚湾不是「开屏没请求定位」造成的,而是没有 fix 时 (0, 0) 被写进 viewport。堵住它的是 isTrustworthyCoordinate(7ef4094), 开屏定位是叠在守卫之上的体验改进,不是替代 —— 用户拒权、模拟器没设位置、 室内超时都还是拿不到 fix。
我已读完 pr.diff、改动涉及的全部源文件(index.vue、viewport.ts、map.ts、validation.ts、dataset.ts)、barrel 导出与 location store,并核对了导入解析、旧默认值残留与 uni.getWindowInfo 在本仓库 uni-app 版本(3.0.0-4080720251210001)中的可用性。
pr.diff
index.vue
viewport.ts
map.ts
validation.ts
dataset.ts
uni.getWindowInfo
3.0.0-4080720251210001
无。本次改动逻辑自洽,对当前数据集没有破坏正确性的缺陷:
centerOnUserLocation
includePointsRequestVersion += 1
src/pages/map/index.vue:463
initializeMap
index.vue:616-619
isWithinGuangmingArea
>= / <=
validateCoordinates
< / >
validation.ts:232-237
untouchedDefaultViewport
DEFAULT_GUANGMING_VIEWPORT
index.vue:160-165
map.ts:12-16
@/domain/poi
validation
GUANGMING_POC_BOUNDS
@/services/map
viewport
computeClusterViewport
fitScale
spanLatitude / cos(centerLat)
candidates
Math.min(...candidates)
test/map-viewport.test.ts:506-517 — 用例名叫「装得下全部点位」,但断言只校验中心点落在 POC 包络内、scale ∈ [12,14),并未真正验证「在该 scale 下全部点位都在可视范围内」。而且实际首屏的取景最终由原生 context.includePoints(index.vue:211)再做一次带 padding 的 fit,computeClusterViewport 只决定 includePoints 之前的「中间帧」store 值。建议要么改名(如「聚类中心落在光明区且 scale 为区级」),要么补一个真正的容纳断言:按返回 scale 算出可视经纬跨度,断言每个点到中心的距离都 ≤ 半跨度。
test/map-viewport.test.ts:506-517
scale ∈ [12,14)
context.includePoints
index.vue:211
src/services/map/viewport.ts:116-120(配合 :140-143) — computeClusterViewport 的中心取算术中心(centroid),但 fitScale 用的是外接矩形全跨度(max-min)来反推 scale,隐含「centroid 与外接矩形中心重合」的假设。当点位分布偏斜(个别远点)时,centroid 会偏向密集侧,远点一侧的「centroid→边缘」距离会超过半跨度,理论上可能被切到屏外。当前数据集 centroid 与矩形中心只差 0.36 km(注释也提到),加上 fitScale 向下取半级(:147)带来的额外余量,加上首屏还会被 includePoints 覆盖,实际不会出问题;但本 PR 的初衷正是「增删点位后算出来的不会过期」。若想严格保证「一眼看全」,可把 fit 依据从 max-min 改成 2 × max(centroid−min, max−centroid)(取较远的那一半 ×2 作为等效跨度),代价极小。
src/services/map/viewport.ts:116-120
:140-143
max-min
:147
includePoints
2 × max(centroid−min, max−centroid)
src/pages/map/index.vue:31、605-621 — launchCenteredOnUser 只在 includeFilteredPoints 里被置回 false(:193)。若开屏静默定位已置 true 后地图报错,用户点「重试地图」会再次进入 initializeMap,此时 !launchCenteredOnUser 仍为 false → 跳过 includeFilteredPoints,重试后不会自动取景(markers 仍正常渲染,因为直接绑定 :markers,只是不 fit)。属于较窄的边界路径,影响有限;如需严谨,可在 initializeMap 入口或地图重试动作里重置该标记。
src/pages/map/index.vue:31、605-621
launchCenteredOnUser
includeFilteredPoints
false
:193
true
!launchCenteredOnUser
:markers
src/data/poi/dataset.ts:499-502 vs src/stores/modules/map.ts:12-16 — 两处都是「对齐到聚类中心」的兜底值,但纬度一个是 22.7611、一个是 22.761,相差 1e-4(约 11 m,正好等于 COORDINATE_EPSILON)。功能上无害,但 dataset.ts 的注释声称「与 computeClusterViewport 的结果一致」,两处兜底自身却不一致。建议统一成同一个值(或直接复用 DEFAULT_GUANGMING_VIEWPORT),避免日后迷惑。
src/data/poi/dataset.ts:499-502
src/stores/modules/map.ts:12-16
22.7611
22.761
1e-4
COORDINATE_EPSILON
src/pages/map/index.vue:565-575、639-646 — restoreLocationOnLaunch 在 onLoad 起步、initializeMap 在 onReady 起步。getSetting + getLocation 多数情况下会在首帧之后才 resolve,于是「已授权用户」开屏可能先看到聚类视野、再跳到自己位置(一次性的居中动画,非抽动回路)。注释表述是「蓝点直接出现在自己位置上」,实际更接近「先聚类、后移到我」。这是可接受的体验,但若想真正做到无缝,需要把首帧 <map> 的初始中心延迟到定位有结果再确定——代价较大,按需取舍。
src/pages/map/index.vue:565-575、639-646
restoreLocationOnLaunch
onLoad
onReady
getSetting + getLocation
<map>
npm test
node_modules
npx vitest
vitest.config.ts
scope.userLocation === true
requestCurrentLocation({silent:true})
No dependencies set.
The note is not visible to the blocked user.
初始视野
(跟点位密度走,不像外接矩形中心那样被个别远点拽偏),scale 由聚类跨度
反推出「一眼看全」的档位。当前 30 个点位算出 (113.92754, 22.76105)
scale 12 —— 原来的 scale 11 会把光明区缩成一小块,留一圈空白。
手写值会过期,算出来的不会。手写值降级为点位为空时的兜底。
三个字面量,跟 DEFAULT_GUANGMING_VIEWPORT 重复;改成直接跟常量比。
开屏定位
居中,蓝点直接出现在用户位置上。未决定/已拒绝一律不碰,避免小程序第一帧
就弹微信授权框、拒绝后再连弹一个引导框;那两种状态留给定位按钮。
地方比停在聚类视野更糟。
永远显示「定位中…」。
需要说明:地图漂到几内亚湾不是「开屏没请求定位」造成的,而是没有 fix 时
(0, 0) 被写进 viewport。堵住它的是 isTrustworthyCoordinate(7ef4094),
开屏定位是叠在守卫之上的体验改进,不是替代 —— 用户拒权、模拟器没设位置、
室内超时都还是拿不到 fix。
我已读完
pr.diff、改动涉及的全部源文件(index.vue、viewport.ts、map.ts、validation.ts、dataset.ts)、barrel 导出与 location store,并核对了导入解析、旧默认值残留与uni.getWindowInfo在本仓库 uni-app 版本(3.0.0-4080720251210001)中的可用性。🔍 审查结论
严重问题(必须修复)
无。本次改动逻辑自洽,对当前数据集没有破坏正确性的缺陷:
centerOnUserLocation确实会includePointsRequestVersion += 1(src/pages/map/index.vue:463),所以initializeMap里那段关于「bump version 能取消已排队的 includeFilteredPoints」的注释(index.vue:616-619)是准确的,静默定位与首帧 includePoints 的竞态被正确处理。isWithinGuangmingArea用闭区间>= / <=替换原validateCoordinates的严格< / >外判(validation.ts:232-237),边界点判定语义不变,无回归。untouchedDefaultViewport从「硬编码精确相等」改为「相对DEFAULT_GUANGMING_VIEWPORT的阈值比较」(index.vue:160-165),与新 store 初值(map.ts:12-16)一致,且更鲁棒。@/domain/poi→validation(含GUANGMING_POC_BOUNDS、isWithinGuangmingArea),@/services/map→viewport(含computeClusterViewport),导入可解析。fitScale的墨卡托折算(spanLatitude / cos(centerLat))与瓦片像素公式手推正确;candidates至少有一个元素(早返回保证),Math.min(...candidates)安全。建议改进
test/map-viewport.test.ts:506-517— 用例名叫「装得下全部点位」,但断言只校验中心点落在 POC 包络内、scale ∈ [12,14),并未真正验证「在该 scale 下全部点位都在可视范围内」。而且实际首屏的取景最终由原生context.includePoints(index.vue:211)再做一次带 padding 的 fit,computeClusterViewport只决定 includePoints 之前的「中间帧」store 值。建议要么改名(如「聚类中心落在光明区且 scale 为区级」),要么补一个真正的容纳断言:按返回 scale 算出可视经纬跨度,断言每个点到中心的距离都 ≤ 半跨度。src/services/map/viewport.ts:116-120(配合:140-143) —computeClusterViewport的中心取算术中心(centroid),但fitScale用的是外接矩形全跨度(max-min)来反推 scale,隐含「centroid 与外接矩形中心重合」的假设。当点位分布偏斜(个别远点)时,centroid 会偏向密集侧,远点一侧的「centroid→边缘」距离会超过半跨度,理论上可能被切到屏外。当前数据集 centroid 与矩形中心只差 0.36 km(注释也提到),加上fitScale向下取半级(:147)带来的额外余量,加上首屏还会被includePoints覆盖,实际不会出问题;但本 PR 的初衷正是「增删点位后算出来的不会过期」。若想严格保证「一眼看全」,可把 fit 依据从max-min改成2 × max(centroid−min, max−centroid)(取较远的那一半 ×2 作为等效跨度),代价极小。src/pages/map/index.vue:31、605-621—launchCenteredOnUser只在includeFilteredPoints里被置回false(:193)。若开屏静默定位已置true后地图报错,用户点「重试地图」会再次进入initializeMap,此时!launchCenteredOnUser仍为 false → 跳过includeFilteredPoints,重试后不会自动取景(markers 仍正常渲染,因为直接绑定:markers,只是不 fit)。属于较窄的边界路径,影响有限;如需严谨,可在initializeMap入口或地图重试动作里重置该标记。src/data/poi/dataset.ts:499-502vssrc/stores/modules/map.ts:12-16— 两处都是「对齐到聚类中心」的兜底值,但纬度一个是22.7611、一个是22.761,相差1e-4(约 11 m,正好等于COORDINATE_EPSILON)。功能上无害,但 dataset.ts 的注释声称「与 computeClusterViewport 的结果一致」,两处兜底自身却不一致。建议统一成同一个值(或直接复用DEFAULT_GUANGMING_VIEWPORT),避免日后迷惑。src/pages/map/index.vue:565-575、639-646—restoreLocationOnLaunch在onLoad起步、initializeMap在onReady起步。getSetting + getLocation多数情况下会在首帧之后才 resolve,于是「已授权用户」开屏可能先看到聚类视野、再跳到自己位置(一次性的居中动画,非抽动回路)。注释表述是「蓝点直接出现在自己位置上」,实际更接近「先聚类、后移到我」。这是可接受的体验,但若想真正做到无缝,需要把首帧<map>的初始中心延迟到定位有结果再确定——代价较大,按需取舍。测试与验证建议
npm test(本环境未装node_modules,npx vitest拉到 v4 与vitest.config.ts不兼容,未能代跑)。重点确认新增的computeClusterViewport/isWithinGuangmingArea用例通过,尤其是真实数据集那条scale ∈ [12,14)会随点位增删而变脆——增删远距离点位后需同步检视。restoreLocationOnLaunch仅在scope.userLocation === true时才动);视野停在聚类。requestCurrentLocation({silent:true})行为断言(当前只测了isWithinGuangmingArea本身,没测它在index.vue里的接线)。