fix(map): 收敛 regionchange 回写回路,修复选中标点后地图抽动

选中标点后地图在左右/上下反复抽动,根因是视口回写形成了自激回路:
map 的 longitude/latitude/scale 绑定到 store,而 regionchange 又无条件把
地图当前中心点写回 store —— 写 store 触发地图移动,移动结束回传一个
「差一点点」的中心点,再写回,再移动。上一个提交把中心点改成从
detail.centerLocation 同步读取后,回路里原本靠 getCenterLocation 异步跳变
掩盖住的这一点噪声就直接闭合了。

两道闸:
- 只接受手势造成的变化。causedBy 为 update(我们自己改绑定值或调
  includePoints 触发)时 store 已是权威值,不回写。老基础库拿不到
  causedBy 时退化到下一道闸。
- 所有回写走 commitViewport,经 isSignificantViewportChange 过滤掉小于
  1e-4 度 / 0.01 级的变化。远小于任何一次真实拖动,足以吸收量化噪声。

顺带修一个 H5 构建回归(1bcdd40 引入):uni 的 H5 路由生成器按路径推导
组件标识,pages/poi/detail 与 pages-poi/detail 都归一成 PagesPoiDetail,
重复声明导致 build:h5 失败。兼容跳板必须留在旧路径上,所以改名分包路由
pages-poi/detail -> pages-poi/poi-detail。
This commit is contained in:
周瑞哲
2026-08-03 11:08:39 +08:00
parent 1bcdd40729
commit 7ef4094788
12 changed files with 101 additions and 18 deletions
+1 -1
View File
@@ -66,7 +66,7 @@ function openItinerary() {
}
function openDetail(poiId: string) {
uni.navigateTo({ url: `/pages-poi/detail?poiId=${encodeURIComponent(poiId)}` })
uni.navigateTo({ url: `/pages-poi/poi-detail?poiId=${encodeURIComponent(poiId)}` })
}
function focusOnMap(poiId: string) {
+1 -1
View File
@@ -53,7 +53,7 @@ function openPoi(poiId: string) {
})
return
}
uni.navigateTo({ url: `/pages-poi/detail?poiId=${encodeURIComponent(poiId)}` })
uni.navigateTo({ url: `/pages-poi/poi-detail?poiId=${encodeURIComponent(poiId)}` })
}
function loadProfile() {
+1 -1
View File
@@ -284,7 +284,7 @@ function openPlanner() {
}
function openDetail(poiId: string) {
uni.navigateTo({ url: `/pages-poi/detail?poiId=${encodeURIComponent(poiId)}` })
uni.navigateTo({ url: `/pages-poi/poi-detail?poiId=${encodeURIComponent(poiId)}` })
}
function focusOnMap(poiId: string) {
+32 -10
View File
@@ -7,7 +7,7 @@ import PoiSummaryCard from '@/components/map/PoiSummaryCard.vue'
import PageState from '@/components/poi/PageState.vue'
import { getPoiRepository } from '@/data/poi'
import { isTrustworthyCoordinate } from '@/domain/poi'
import { buildMarkerIdMap, createPoiMarkers } from '@/services/map'
import { buildMarkerIdMap, createPoiMarkers, isSignificantViewportChange } from '@/services/map'
import { getSessionPlanningOrigin, loadPlan } from '@/services/travel-assistant'
import { useLocationStore, useMapStore } from '@/stores'
@@ -292,7 +292,11 @@ function readEventCenter(detail: Record<string, unknown>): GeoPoint | null {
return null
}
function updateViewport(event: { type: string, detail: Record<string, unknown> }) {
function updateViewport(event: {
type: string
causedBy?: unknown
detail: Record<string, unknown>
}) {
const changeType = String(event.detail.type ?? event.type ?? '')
if (changeType !== 'end')
return
@@ -300,6 +304,15 @@ function updateViewport(event: { type: string, detail: Record<string, unknown> }
if (!mapReady.value || navigating.value)
return
// 只接受用户手势造成的视野变化。
//
// `causedBy: 'update'` 是我们自己改 longitude/latitude/scale 或调 includePoints
// 触发的回调 —— 此时 store 已经是权威值,再把地图回传的落点写回去会形成
// 「写 store → 地图动 → regionchange → 写 store」的自激回路,表现为地图反复抽动。
const causedBy = String(event.causedBy ?? event.detail.causedBy ?? '')
if (causedBy && causedBy !== 'drag' && causedBy !== 'scale' && causedBy !== 'gesture')
return
const eventScale = Number(event.detail.scale)
const fallbackScale = Number.isFinite(eventScale) ? eventScale : viewport.value.scale
@@ -319,6 +332,19 @@ function updateViewport(event: { type: string, detail: Record<string, unknown> }
})
}
/**
* regionchange 回写 store 的唯一入口。
*
* 只有超过阈值的变化才写:地图的中心点是绑定到 store 的,写回去会再次触发地图移动,
* 把量化噪声原样回灌就会自激。见 isSignificantViewportChange 的注释。
*/
function commitViewport(longitude: number, latitude: number, scale: number) {
const next = { longitude, latitude, scale }
if (!isSignificantViewportChange(viewport.value, next))
return
mapStore.updateViewport(next)
}
function updateViewportWithScale(longitude: number, latitude: number, fallbackScale: number) {
const context = mapContext.value as (ReturnType<typeof uni.createMapContext> & {
getScale?: (options: {
@@ -328,21 +354,17 @@ function updateViewportWithScale(longitude: number, latitude: number, fallbackSc
}) | null
if (!context?.getScale) {
mapStore.updateViewport({ longitude, latitude, scale: fallbackScale })
commitViewport(longitude, latitude, fallbackScale)
return
}
context.getScale({
success: (result) => {
const scale = Number(result.scale)
mapStore.updateViewport({
longitude,
latitude,
scale: Number.isFinite(scale) ? scale : fallbackScale,
})
commitViewport(longitude, latitude, Number.isFinite(scale) ? scale : fallbackScale)
},
fail: () => {
mapStore.updateViewport({ longitude, latitude, scale: fallbackScale })
commitViewport(longitude, latitude, fallbackScale)
},
})
}
@@ -506,7 +528,7 @@ function openDetail(poiId: string) {
return
navigating.value = true
uni.navigateTo({
url: `/pages-poi/detail?poiId=${encodeURIComponent(poiId)}`,
url: `/pages-poi/poi-detail?poiId=${encodeURIComponent(poiId)}`,
complete: () => {
setTimeout(() => {
navigating.value = false
+1 -1
View File
@@ -26,7 +26,7 @@ onLoad((query) => {
// redirectTo 而非 navigateTo:跳板本身不该留在页面栈里。
uni.redirectTo({
url: `/pages-poi/detail?poiId=${encodeURIComponent(rawPoiId)}`,
url: `/pages-poi/poi-detail?poiId=${encodeURIComponent(rawPoiId)}`,
fail: () => {
uni.showToast({ title: REDIRECT_FAILURE_TOAST, icon: 'none' })
fallbackToMap()