This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
@back="handlePageBack"
|
||||
>
|
||||
<GuideMapShell
|
||||
v-if="mapContextReady"
|
||||
:search-text="facility.name"
|
||||
search-top="20px"
|
||||
floor-bottom="153px"
|
||||
@@ -70,6 +71,9 @@
|
||||
</view>
|
||||
</view>
|
||||
</GuideMapShell>
|
||||
<view v-else class="detail-map-loading" data-testid="facility-map-loading">
|
||||
<text class="detail-map-loading-text">正在准备位置预览...</text>
|
||||
</view>
|
||||
</GuidePageFrame>
|
||||
</template>
|
||||
|
||||
@@ -117,6 +121,7 @@ const targetFocusRequest = ref<TargetPoiFocusRequestViewModel | null>(null)
|
||||
const targetFocusRequestId = ref(0)
|
||||
const locationErrorMessage = ref('')
|
||||
const resolutionLoadFailed = ref(false)
|
||||
const mapContextReady = ref(false)
|
||||
|
||||
interface TargetPoiFocusResult {
|
||||
requestId: number | string
|
||||
@@ -340,39 +345,40 @@ const tryResolveFacilityPoi = async () => {
|
||||
}
|
||||
|
||||
onLoad(async (options: Record<string, unknown> = {}) => {
|
||||
mapContextReady.value = false
|
||||
searchContext.value = parseFacilityDetailSearchContext(options)
|
||||
resolutionLoadFailed.value = false
|
||||
await loadGuideFloors()
|
||||
|
||||
if (options.id) {
|
||||
facility.value.id = decodeRouteText(options.id)
|
||||
}
|
||||
|
||||
if (options.target) {
|
||||
facility.value.name = decodeRouteText(options.target)
|
||||
}
|
||||
|
||||
const requestedFloorId = decodeRouteText(options.floorId)
|
||||
|| searchContext.value.searchFloorId
|
||||
if (requestedFloorId) {
|
||||
requestedTargetFloorId.value = normalizeGuideFloorId(requestedFloorId)
|
||||
if (requestedTargetFloorId.value) {
|
||||
activeFloor.value = requestedTargetFloorId.value
|
||||
}
|
||||
}
|
||||
|
||||
const requestedFloorLabel = decodeRouteText(options.floorLabel)
|
||||
|| searchContext.value.searchFloorLabel
|
||||
if (requestedFloorLabel) {
|
||||
facility.value.floor = requestedFloorLabel
|
||||
}
|
||||
|
||||
if (!facility.value.id && !options.target) {
|
||||
locationErrorMessage.value = '未提供可定位的点位信息。'
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await loadGuideFloors()
|
||||
|
||||
if (options.id) {
|
||||
facility.value.id = decodeRouteText(options.id)
|
||||
}
|
||||
|
||||
if (options.target) {
|
||||
facility.value.name = decodeRouteText(options.target)
|
||||
}
|
||||
|
||||
const requestedFloorId = decodeRouteText(options.floorId)
|
||||
|| searchContext.value.searchFloorId
|
||||
if (requestedFloorId) {
|
||||
requestedTargetFloorId.value = normalizeGuideFloorId(requestedFloorId)
|
||||
if (requestedTargetFloorId.value) {
|
||||
activeFloor.value = requestedTargetFloorId.value
|
||||
}
|
||||
}
|
||||
|
||||
const requestedFloorLabel = decodeRouteText(options.floorLabel)
|
||||
|| searchContext.value.searchFloorLabel
|
||||
if (requestedFloorLabel) {
|
||||
facility.value.floor = requestedFloorLabel
|
||||
}
|
||||
|
||||
if (!facility.value.id && !options.target) {
|
||||
locationErrorMessage.value = '未提供可定位的点位信息。'
|
||||
return
|
||||
}
|
||||
|
||||
const poi = await tryResolveFacilityPoi()
|
||||
if (poi) {
|
||||
facility.value = toFacilityDetailViewModel(poi)
|
||||
@@ -396,6 +402,9 @@ onLoad(async (options: Record<string, unknown> = {}) => {
|
||||
} catch (error) {
|
||||
console.error('加载设施位置详情失败:', error)
|
||||
locationErrorMessage.value = '位置数据加载失败,请稍后重试。'
|
||||
} finally {
|
||||
// 目标楼层与聚焦请求准备完成后再挂载 ThreeMap,避免默认楼层的中间帧。
|
||||
mapContextReady.value = true
|
||||
}
|
||||
})
|
||||
|
||||
@@ -443,6 +452,20 @@ const handlePageBack = () => {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.detail-map-loading {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f3f3f3;
|
||||
}
|
||||
|
||||
.detail-map-loading-text {
|
||||
color: #424754;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.detail-sheet {
|
||||
position: absolute;
|
||||
left: 84px;
|
||||
|
||||
@@ -428,6 +428,7 @@ const searchVisiblePoiIds = ref<string[] | null>(null)
|
||||
const searchTargetFocusRequest = ref<TargetPoiFocusRequestViewModel | null>(null)
|
||||
let searchTargetFocusRequestId = 0
|
||||
let pendingCollapseSearchAfterDetail = false
|
||||
let pendingSearchDetailFocusRequest: TargetPoiFocusRequestViewModel | null = null
|
||||
|
||||
let launchOverlayFallbackTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let launchOverlayFadeTimer: ReturnType<typeof setTimeout> | null = null
|
||||
@@ -1358,10 +1359,23 @@ onShow(async () => {
|
||||
await nextTick()
|
||||
if (pendingCollapseSearchAfterDetail) {
|
||||
pendingCollapseSearchAfterDetail = false
|
||||
const pendingFocusRequest = pendingSearchDetailFocusRequest
|
||||
pendingSearchDetailFocusRequest = null
|
||||
await homeSearchPanelRef.value?.returnToCollapsedAfterDetail?.()
|
||||
homeSearchExpanded.value = false
|
||||
homeCategoryModeActive.value = false
|
||||
searchVisiblePoiIds.value = null
|
||||
if (pendingFocusRequest) {
|
||||
closeArrivalPanel()
|
||||
showRoutePlanner.value = false
|
||||
isSimulatingRoute.value = false
|
||||
selectedGuidePoi.value = null
|
||||
isPoiCardCollapsed.value = false
|
||||
is3DMode.value = true
|
||||
indoorView.value = 'floor'
|
||||
activeGuideFloor.value = pendingFocusRequest.floorId
|
||||
searchTargetFocusRequest.value = pendingFocusRequest
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1455,16 +1469,8 @@ const createSearchDetailUrl = (poi: MuseumPoi, context: PoiSearchContext) => {
|
||||
}
|
||||
|
||||
const handleHomeSearchResultTap = async (poi: MuseumPoi, context: PoiSearchContext) => {
|
||||
closeArrivalPanel()
|
||||
showRoutePlanner.value = false
|
||||
isSimulatingRoute.value = false
|
||||
selectedGuidePoi.value = null
|
||||
isPoiCardCollapsed.value = false
|
||||
is3DMode.value = true
|
||||
indoorView.value = 'floor'
|
||||
activeGuideFloor.value = poi.floorId
|
||||
searchTargetFocusRequestId += 1
|
||||
searchTargetFocusRequest.value = toTargetFocusRequestViewModel({
|
||||
pendingSearchDetailFocusRequest = toTargetFocusRequestViewModel({
|
||||
poiId: poi.id,
|
||||
name: poi.name,
|
||||
floorId: poi.floorId,
|
||||
@@ -1478,13 +1484,14 @@ const handleHomeSearchResultTap = async (poi: MuseumPoi, context: PoiSearchConte
|
||||
entrances: poi.entrances
|
||||
}, searchTargetFocusRequestId)
|
||||
|
||||
// 先把楼层与目标请求交给地图,再进入详情页;返回时仅收起搜索 UI,不清焦点。
|
||||
await nextTick()
|
||||
// 详情页会一次性挂载目标楼层;首页不再先启动并销毁 ThreeMap。
|
||||
searchTargetFocusRequest.value = null
|
||||
pendingCollapseSearchAfterDetail = true
|
||||
uni.navigateTo({
|
||||
url: createSearchDetailUrl(poi, context),
|
||||
fail: () => {
|
||||
pendingCollapseSearchAfterDetail = false
|
||||
pendingSearchDetailFocusRequest = null
|
||||
uni.showToast({
|
||||
title: '点位详情打开失败,请重试',
|
||||
icon: 'none'
|
||||
|
||||
@@ -143,6 +143,26 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
describe('点位详情实际渲染与返回闭环', () => {
|
||||
it('解析目标楼层和点位后才挂载地图,避免默认楼层中间帧', async () => {
|
||||
const wrapper = mountDetail()
|
||||
|
||||
expect(wrapper.find('[data-testid="guide-map-shell"]').exists()).toBe(false)
|
||||
expect(wrapper.get('[data-testid="facility-map-loading"]').text()).toContain('正在准备位置预览')
|
||||
|
||||
await loadDetail({
|
||||
id: encodeURIComponent(facilityPoi.id),
|
||||
target: encodeURIComponent(facilityPoi.name),
|
||||
floorId: facilityPoi.floorId,
|
||||
floorLabel: facilityPoi.floorLabel
|
||||
})
|
||||
|
||||
expect(wrapper.getComponent(GuideMapShellStub).props('activeFloor')).toBe('floor-l2')
|
||||
expect(wrapper.getComponent(GuideMapShellStub).props('targetFocusRequest')).toMatchObject({
|
||||
poiId: facilityPoi.id,
|
||||
floorId: facilityPoi.floorId
|
||||
})
|
||||
})
|
||||
|
||||
it('只展示真实详情字段,并向地图发送正确楼层与点位定位请求', async () => {
|
||||
const wrapper = mountDetail()
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ describe('首页搜索与地图闭环', () => {
|
||||
expect(map.props('visiblePoiIds')).toEqual(categoryContext.visiblePoiIds)
|
||||
})
|
||||
|
||||
it('分类结果进入详情并返回后收起搜索、清空 marker,但保留目标焦点与楼层', async () => {
|
||||
it('分类结果进入详情时不触发首页地图,返回后才恢复目标焦点与楼层', async () => {
|
||||
const wrapper = await mountIndex()
|
||||
const search = wrapper.getComponent(PoiSearchPanelStub)
|
||||
const map = wrapper.getComponent(GuideMapShellStub)
|
||||
@@ -215,13 +215,7 @@ describe('首页搜索与地图闭环', () => {
|
||||
search.vm.$emit('result-tap', poi, categoryContext)
|
||||
await flushPromises()
|
||||
|
||||
expect(map.props('activeFloor')).toBe('L2')
|
||||
expect(map.props('targetFocusRequest')).toMatchObject({
|
||||
poiId: poi.id,
|
||||
floorId: 'L2',
|
||||
floorLabel: '2F',
|
||||
positionGltf: poi.positionGltf
|
||||
})
|
||||
expect(map.props('targetFocusRequest')).toBeNull()
|
||||
|
||||
const navigateTo = vi.mocked(uni.navigateTo)
|
||||
const detailUrl = navigateTo.mock.calls[0]?.[0]?.url as string
|
||||
@@ -298,7 +292,7 @@ describe('首页搜索与地图闭环', () => {
|
||||
expect(testState.searchCollapseAfterDetailCount).toBe(0)
|
||||
expect(testState.searchRestoreCount).toBe(1)
|
||||
expect(map.props('visiblePoiIds')).toEqual(categoryContext.visiblePoiIds)
|
||||
expect(map.props('targetFocusRequest')).toMatchObject({ poiId: poi.id })
|
||||
expect(map.props('targetFocusRequest')).toBeNull()
|
||||
})
|
||||
|
||||
it('地图点位卡隐藏首页 dock 时仍保留搜索组件,关闭后可继续使用原列表', async () => {
|
||||
@@ -342,7 +336,7 @@ describe('首页搜索与地图闭环', () => {
|
||||
|
||||
const detailUrl = vi.mocked(uni.navigateTo).mock.calls[0]?.[0]?.url as string
|
||||
expect(new URLSearchParams(detailUrl.split('?')[1]).get('resultCount')).toBe('1')
|
||||
expect(map.props('targetFocusRequest')).not.toBeNull()
|
||||
expect(map.props('targetFocusRequest')).toBeNull()
|
||||
|
||||
search.vm.$emit('cancel')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
Reference in New Issue
Block a user