优化搜索点位详情页面切换体验
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
lyf
2026-07-14 10:00:30 +08:00
parent abaeda189e
commit ef59570b0e
4 changed files with 95 additions and 51 deletions

View File

@@ -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;

View File

@@ -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'

View File

@@ -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()

View File

@@ -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()