修复:首页点位讲解入口展示逻辑

This commit is contained in:
cxk
2026-07-20 01:50:14 +08:00
parent e2bf84aec6
commit 5331d6e0af
2 changed files with 95 additions and 62 deletions

View File

@@ -99,15 +99,12 @@
</view>
</view>
<view
v-if="selectedGuidePoiIsHall"
v-if="selectedGuidePoiExplainHall"
class="poi-card-actions"
>
<view class="poi-action primary" @tap="handleViewSelectedHall">
<text class="poi-action-text">查看展厅</text>
</view>
<view class="poi-action" @tap="handleSelectedPoiRelated">
<text class="poi-action-text">相关讲解</text>
</view>
</view>
</template>
</view>
@@ -290,7 +287,7 @@
</template>
<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
import GuidePageFrame from '@/components/navigation/GuidePageFrame.vue'
import GuideMapShell from '@/components/navigation/GuideMapShell.vue'
@@ -400,6 +397,8 @@ const loadingFloorId = ref('')
const renderedFloorId = ref('')
const failedFloorId = ref('')
const selectedGuidePoi = ref<GuideRenderPoi | null>(null)
const selectedGuidePoiExplainHall = ref<MuseumHall | null>(null)
let selectedGuidePoiExplainHallRequestId = 0
// 状态
const currentTab = ref<GuideTopTab>(initialTopTab())
@@ -714,9 +713,7 @@ const selectedGuidePoiSubtitle = computed(() => {
return `${floorLabel} · ${poi.primaryCategoryZh || '位置预览'}`
})
const selectedGuidePoiIsHall = computed(() => {
const poi = selectedGuidePoi.value
return Boolean(
const isGuidePoiHallLike = (poi: GuideRenderPoi | null) => Boolean(
poi
&& (
poi.kind === 'hall'
@@ -725,10 +722,11 @@ const selectedGuidePoiIsHall = computed(() => {
|| poi.primaryCategory === 'exhibition_hall_entrance'
)
)
})
const selectedGuidePoiIsHall = computed(() => isGuidePoiHallLike(selectedGuidePoi.value))
const selectedGuidePoiCollapsedSubtitle = computed(() => (
selectedGuidePoiIsHall.value ? '点按展开查看展厅' : selectedGuidePoiSubtitle.value
selectedGuidePoiExplainHall.value ? '点按展开查看展厅' : selectedGuidePoiSubtitle.value
))
const routeSimulationStepText = computed(() => {
@@ -1132,12 +1130,10 @@ const togglePoiCardCollapsed = () => {
const normalizeHallMatchText = (value?: string) => (value || '')
.trim()
.replace(/[\s()【】[\]_-]/g, '')
.replace(/^展厅\d+/, '')
.toLowerCase()
const resolveSelectedPoiHall = async (): Promise<MuseumHall | null> => {
const poi = selectedGuidePoi.value
if (!poi) return null
const resolvePoiExplainHall = async (poi: GuideRenderPoi): Promise<MuseumHall | null> => {
const halls = await explainUseCase.listHalls()
const matchedByPoiId = halls.find((hall) => (
hall.poiId === poi.id
@@ -1162,10 +1158,30 @@ const resolveSelectedPoiHall = async (): Promise<MuseumHall | null> => {
const poiHallName = normalizeHallMatchText(poi.hallName || poi.name)
return halls.find((hall) => {
const hallName = normalizeHallMatchText(hall.name)
return hallName && poiHallName && (hallName.includes(poiHallName) || poiHallName.includes(hallName))
return hallName && poiHallName && hallName === poiHallName
}) || null
}
watch(selectedGuidePoi, (poi) => {
const requestId = ++selectedGuidePoiExplainHallRequestId
selectedGuidePoiExplainHall.value = null
if (!poi || !isGuidePoiHallLike(poi)) return
void resolvePoiExplainHall(poi)
.then((hall) => {
if (
requestId !== selectedGuidePoiExplainHallRequestId
|| selectedGuidePoi.value?.id !== poi.id
) return
selectedGuidePoiExplainHall.value = hall
})
.catch((error) => {
if (requestId !== selectedGuidePoiExplainHallRequestId) return
console.warn('点位讲解展厅关联解析失败:', error)
})
}, { flush: 'sync' })
const navigateToPoiDetail = ({
url,
floorId,
@@ -1209,43 +1225,12 @@ const navigateToHallExplainObjects = (hallId: string, hallName = '讲解') => {
)
}
const handleViewSelectedHall = async () => {
const hall = await resolveSelectedPoiHall()
if (!hall) {
uni.showToast({
title: '该展厅详情暂未接入',
icon: 'none'
})
return
}
const handleViewSelectedHall = () => {
const hall = selectedGuidePoiExplainHall.value
if (!hall) return
navigateToHallExplainObjects(hall.id, hall.name)
}
const handleSelectedPoiRelated = async () => {
if (!selectedGuidePoi.value) return
const hall = await resolveSelectedPoiHall()
if (hall) {
navigateToHallExplainObjects(hall.id, hall.name)
return
}
const keyword = selectedGuidePoi.value.hallName || selectedGuidePoi.value.name
const results = await explainUseCase.searchExplain(keyword)
const firstHall = results.find((item) => item.type === 'hall' && item.hallId)
if (firstHall?.hallId) {
navigateToHallExplainObjects(firstHall.hallId, selectedGuidePoi.value.hallName || '讲解')
return
}
uni.showToast({
title: '该展厅暂无对应讲解',
icon: 'none'
})
}
const loadRouteTargets = async (keyword = '') => {
routeTargetsLoading.value = true
routeTargetsError.value = ''

View File

@@ -528,7 +528,7 @@ describe('首页搜索与地图闭环', () => {
expect(testState.resetToViewBaselineCalls).toHaveLength(1)
})
it('模型点选展厅的相关讲解进入该展厅的讲解对象列表,并在返回时复位原楼层', async () => {
it('模型点选展厅只显示一个讲解入口,并在返回时复位原楼层', async () => {
testState.halls = [{ id: 'hall-l2', poiId: poi.id, name: '二层展厅' }]
const wrapper = await mountIndex()
const map = wrapper.getComponent(GuideMapShellStub)
@@ -541,9 +541,10 @@ describe('首页搜索与地图闭环', () => {
})
await flushPromises()
const relatedActions = wrapper.findAll('.poi-action')
expect(relatedActions).toHaveLength(2)
await relatedActions[1]!.trigger('tap')
const actions = wrapper.findAll('.poi-action')
expect(actions).toHaveLength(1)
expect(wrapper.text()).not.toContain('相关讲解')
await wrapper.find('.poi-action.primary').trigger('tap')
await flushPromises()
expect(vi.mocked(uni.navigateTo).mock.calls[0]?.[0]?.url).toContain('/pages/explain/guide-stop-list?hallId=hall-l2')
@@ -559,7 +560,54 @@ describe('首页搜索与地图闭环', () => {
expect(wrapper.find('.guide-poi-card').exists()).toBe(false)
})
it('模型点选展厅的相关讲解优先进入讲解对象列表并保留一次性返回上下文', async () => {
it('展厅类型空间没有讲解展厅关联时不显示展厅与讲解按钮', async () => {
testState.halls = [{ id: 'hall-biology', poiId: 'another-poi', name: '生物厅' }]
const wrapper = await mountIndex()
const map = wrapper.getComponent(GuideMapShellStub)
map.vm.$emit('poi-click', {
...poi,
id: 'space-l2-temporary',
name: '生物厅临展空间',
kind: 'space',
hallId: 'space-l2-temporary',
primaryCategory: 'exhibition_hall',
primaryCategoryZh: '展厅'
})
await flushPromises()
expect(wrapper.find('.guide-poi-card').exists()).toBe(true)
expect(wrapper.find('.poi-card-actions').exists()).toBe(false)
expect(wrapper.text()).not.toContain('查看展厅')
expect(wrapper.text()).not.toContain('相关讲解')
})
it('展厅编号名称规范化后仍能关联真实讲解展厅', async () => {
testState.halls = [{ id: 'hall-biology', name: '生物厅' }]
const wrapper = await mountIndex()
const map = wrapper.getComponent(GuideMapShellStub)
map.vm.$emit('poi-click', {
...poi,
id: 'hall-space-biology',
name: '展厅_6生物厅',
kind: 'space',
hallId: 'space-biology',
primaryCategory: 'exhibition_hall',
primaryCategoryZh: '展厅'
})
await flushPromises()
const actions = wrapper.findAll('.poi-action')
expect(actions).toHaveLength(1)
await actions[0]!.trigger('tap')
await flushPromises()
expect(vi.mocked(uni.navigateTo).mock.calls[0]?.[0]?.url).toContain(
'/pages/explain/guide-stop-list?hallId=hall-biology'
)
})
it('模型点选展厅的唯一讲解入口进入讲解对象列表并保留一次性返回上下文', async () => {
testState.halls = [{ id: 'hall-l2', poiId: poi.id, name: '二层展厅' }]
const wrapper = await mountIndex()
const map = wrapper.getComponent(GuideMapShellStub)
@@ -571,7 +619,7 @@ describe('首页搜索与地图闭环', () => {
primaryCategoryZh: '展厅'
})
await flushPromises()
await wrapper.findAll('.poi-action')[1]!.trigger('tap')
await wrapper.find('.poi-action.primary').trigger('tap')
await flushPromises()
expect(vi.mocked(uni.navigateTo).mock.calls[0]?.[0]?.url).toContain('/pages/explain/guide-stop-list?hallId=hall-l2')