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