This commit is contained in:
@@ -583,17 +583,21 @@ const markFloorSwitchFailedIfUnrendered = (floorId: string, requestSeq: number)
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFloorChange = (floor: { id: string; label: string }) => {
|
const requestFloorSwitch = (
|
||||||
|
floor: { id: string; label: string },
|
||||||
|
options: { force?: boolean } = {}
|
||||||
|
) => {
|
||||||
const floorId = floor.id
|
const floorId = floor.id
|
||||||
if (!floorId || loadingFloorId.value) return
|
if (!floorId) return Promise.resolve()
|
||||||
|
if (loadingFloorId.value === floorId) return Promise.resolve()
|
||||||
if (
|
if (
|
||||||
floorId === renderedFloorId.value
|
!options.force && floorId === renderedFloorId.value
|
||||||
&& activeFloorId.value === floorId
|
&& activeFloorId.value === floorId
|
||||||
&& props.indoorView === 'floor'
|
&& props.indoorView === 'floor'
|
||||||
&& props.layerMode !== 'multi'
|
&& props.layerMode !== 'multi'
|
||||||
) {
|
) {
|
||||||
emit('floorChange', floorId)
|
emit('floorChange', floorId)
|
||||||
return
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
|
|
||||||
requestedFloorId.value = floorId
|
requestedFloorId.value = floorId
|
||||||
@@ -607,7 +611,7 @@ const handleFloorChange = (floor: { id: string; label: string }) => {
|
|||||||
floorId,
|
floorId,
|
||||||
floorLabel: floor.label
|
floorLabel: floor.label
|
||||||
})
|
})
|
||||||
Promise.resolve(indoorRendererRef.value?.switchFloor?.(floorId))
|
return Promise.resolve(indoorRendererRef.value?.switchFloor?.(floorId))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
markFloorSwitchFailedIfUnrendered(floorId, requestSeq)
|
markFloorSwitchFailedIfUnrendered(floorId, requestSeq)
|
||||||
})
|
})
|
||||||
@@ -623,6 +627,10 @@ const handleFloorChange = (floor: { id: string; label: string }) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleFloorChange = (floor: { id: string; label: string }) => {
|
||||||
|
void requestFloorSwitch(floor)
|
||||||
|
}
|
||||||
|
|
||||||
const handleLayerModeChange = (mode: LayerDisplayMode) => {
|
const handleLayerModeChange = (mode: LayerDisplayMode) => {
|
||||||
// 手动切换展示层数时使用统一的短保护期。
|
// 手动切换展示层数时使用统一的短保护期。
|
||||||
indoorRendererRef.value?.disableAutoSwitchTemporarily?.(manualAutoSwitchPauseMs)
|
indoorRendererRef.value?.disableAutoSwitchTemporarily?.(manualAutoSwitchPauseMs)
|
||||||
@@ -789,7 +797,11 @@ defineExpose({
|
|||||||
indoorRendererRef.value?.clearRoute?.()
|
indoorRendererRef.value?.clearRoute?.()
|
||||||
},
|
},
|
||||||
// 仅发起切换;父级必须以 floor-change 作为已提交的唯一依据。
|
// 仅发起切换;父级必须以 floor-change 作为已提交的唯一依据。
|
||||||
switchFloor: (floorId: string) => indoorRendererRef.value?.switchFloor?.(floorId),
|
switchFloor: (floorId: string) => {
|
||||||
|
const floor = findFloorItemById(floorId)
|
||||||
|
if (!floor) return Promise.resolve()
|
||||||
|
return requestFloorSwitch(floor, { force: true })
|
||||||
|
},
|
||||||
showOverview: handleShowOverview,
|
showOverview: handleShowOverview,
|
||||||
resetToViewBaseline: (options: {
|
resetToViewBaseline: (options: {
|
||||||
view: 'overview' | 'floor'
|
view: 'overview' | 'floor'
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ import type {
|
|||||||
PoiCategoryResultState,
|
PoiCategoryResultState,
|
||||||
PoiSearchContext
|
PoiSearchContext
|
||||||
} from '@/domain/poiSearch'
|
} from '@/domain/poiSearch'
|
||||||
|
import { nextHomeSearchResultVersion } from './homeSearchResultVersion'
|
||||||
import { isEmbeddedInWechatMiniProgram } from '@/utils/hostEnvironment'
|
import { isEmbeddedInWechatMiniProgram } from '@/utils/hostEnvironment'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
@@ -397,7 +398,8 @@ const emitResultsState = () => {
|
|||||||
emit('results-change', {
|
emit('results-change', {
|
||||||
...context,
|
...context,
|
||||||
visiblePoiIds: active ? context.visiblePoiIds : [],
|
visiblePoiIds: active ? context.visiblePoiIds : [],
|
||||||
active
|
active,
|
||||||
|
requestId: props.variant === 'home' ? nextHomeSearchResultVersion() : undefined
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,7 +413,8 @@ const emitPendingHomeSearchResults = (floor?: Pick<MuseumFloor, 'id' | 'label'>)
|
|||||||
floorLabel: floor?.label || context.floorLabel,
|
floorLabel: floor?.label || context.floorLabel,
|
||||||
visiblePoiIds: [],
|
visiblePoiIds: [],
|
||||||
active: true,
|
active: true,
|
||||||
pending: true
|
pending: true,
|
||||||
|
requestId: nextHomeSearchResultVersion()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
src/components/search/homeSearchResultVersion.ts
Normal file
7
src/components/search/homeSearchResultVersion.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// This version survives a PoiSearchPanel remount, unlike its local request sequence.
|
||||||
|
let latestHomeSearchResultVersion = 0
|
||||||
|
|
||||||
|
export const nextHomeSearchResultVersion = () => {
|
||||||
|
latestHomeSearchResultVersion += 1
|
||||||
|
return latestHomeSearchResultVersion
|
||||||
|
}
|
||||||
@@ -28,6 +28,8 @@ export interface PoiSearchSelection {
|
|||||||
export interface PoiCategoryResultState extends PoiSearchContext {
|
export interface PoiCategoryResultState extends PoiSearchContext {
|
||||||
active: boolean
|
active: boolean
|
||||||
pending?: boolean
|
pending?: boolean
|
||||||
|
/** Monotonic home-result event version used to reject stale cross-component state. */
|
||||||
|
requestId?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GuidePoiSearchMode = 'default' | 'category' | 'keyword'
|
export type GuidePoiSearchMode = 'default' | 'category' | 'keyword'
|
||||||
|
|||||||
@@ -373,6 +373,8 @@ const requestedFloorId = ref('')
|
|||||||
const requestedFloorLabel = ref('')
|
const requestedFloorLabel = ref('')
|
||||||
const loadingFloorId = ref('')
|
const loadingFloorId = ref('')
|
||||||
const renderedFloorId = ref('')
|
const renderedFloorId = ref('')
|
||||||
|
// Only GuideMapShell's floor-change confirms that the Three.js floor is usable.
|
||||||
|
const committedFloorId = ref('')
|
||||||
const failedFloorId = ref('')
|
const failedFloorId = ref('')
|
||||||
const selectedGuidePoi = ref<GuideRenderPoi | null>(null)
|
const selectedGuidePoi = ref<GuideRenderPoi | null>(null)
|
||||||
const selectedGuidePoiDetailTarget = ref<PoiDetailTarget | null>(null)
|
const selectedGuidePoiDetailTarget = ref<PoiDetailTarget | null>(null)
|
||||||
@@ -432,6 +434,8 @@ const homeSearchExpanded = ref(false)
|
|||||||
const homeCategoryModeActive = ref(false)
|
const homeCategoryModeActive = ref(false)
|
||||||
const searchVisiblePoiIds = ref<string[] | null>(null)
|
const searchVisiblePoiIds = ref<string[] | null>(null)
|
||||||
const searchTargetFocusRequest = ref<TargetPoiFocusRequestViewModel | null>(null)
|
const searchTargetFocusRequest = ref<TargetPoiFocusRequestViewModel | null>(null)
|
||||||
|
const homeSearchMapState = ref<PoiCategoryResultState | null>(null)
|
||||||
|
let latestHomeSearchMapRequestId = 0
|
||||||
type PoiSearchOverlayHistoryState = {
|
type PoiSearchOverlayHistoryState = {
|
||||||
museumGuideOverlay?: 'poi-search'
|
museumGuideOverlay?: 'poi-search'
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
@@ -853,6 +857,9 @@ const handleTabChange = (tabId: GuideTopTab) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tabId !== currentTab.value) {
|
||||||
|
invalidateHomeSearchFloorCommit()
|
||||||
|
}
|
||||||
currentTab.value = tabId
|
currentTab.value = tabId
|
||||||
syncTopTabToUrl(tabId)
|
syncTopTabToUrl(tabId)
|
||||||
loadTopTabData(tabId)
|
loadTopTabData(tabId)
|
||||||
@@ -875,6 +882,7 @@ const syncTopTabFromHash = () => {
|
|||||||
currentTab.value = tab
|
currentTab.value = tab
|
||||||
loadTopTabData(tab)
|
loadTopTabData(tab)
|
||||||
if (tabChanged) {
|
if (tabChanged) {
|
||||||
|
invalidateHomeSearchFloorCommit()
|
||||||
showRoutePlanner.value = false
|
showRoutePlanner.value = false
|
||||||
isSimulatingRoute.value = false
|
isSimulatingRoute.value = false
|
||||||
isPoiCardCollapsed.value = false
|
isPoiCardCollapsed.value = false
|
||||||
@@ -1000,11 +1008,19 @@ const handleFloorRequest = ({ floorId, floorLabel }: { floorId: string; floorLab
|
|||||||
requestedFloorLabel.value = floorLabel
|
requestedFloorLabel.value = floorLabel
|
||||||
loadingFloorId.value = floorId
|
loadingFloorId.value = floorId
|
||||||
failedFloorId.value = ''
|
failedFloorId.value = ''
|
||||||
|
if (homeSearchMapState.value?.active) {
|
||||||
|
searchVisiblePoiIds.value = []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFloorChange = (floorId: string) => {
|
const handleFloorChange = (floorId: string) => {
|
||||||
activeGuideFloor.value = floorId
|
activeGuideFloor.value = floorId
|
||||||
renderedFloorId.value = floorId
|
renderedFloorId.value = floorId
|
||||||
|
committedFloorId.value = floorId
|
||||||
|
if (requestedFloorId.value === floorId) {
|
||||||
|
requestedFloorId.value = ''
|
||||||
|
requestedFloorLabel.value = ''
|
||||||
|
}
|
||||||
if (loadingFloorId.value === floorId) {
|
if (loadingFloorId.value === floorId) {
|
||||||
loadingFloorId.value = ''
|
loadingFloorId.value = ''
|
||||||
}
|
}
|
||||||
@@ -1014,6 +1030,7 @@ const handleFloorChange = (floorId: string) => {
|
|||||||
indoorView.value = 'floor'
|
indoorView.value = 'floor'
|
||||||
selectedGuidePoi.value = null
|
selectedGuidePoi.value = null
|
||||||
isPoiCardCollapsed.value = false
|
isPoiCardCollapsed.value = false
|
||||||
|
syncHomeSearchMarkersForCommittedFloor(floorId)
|
||||||
showIndoorHint(`已切换到 ${getGuideFloorLabel(floorId)},单指平移、双指可缩放`, 3200)
|
showIndoorHint(`已切换到 ${getGuideFloorLabel(floorId)},单指平移、双指可缩放`, 3200)
|
||||||
console.log('楼层渲染完成:', floorId)
|
console.log('楼层渲染完成:', floorId)
|
||||||
}
|
}
|
||||||
@@ -1023,6 +1040,13 @@ const handleFloorSwitchFailed = ({ floorId, floorLabel }: { floorId: string; flo
|
|||||||
loadingFloorId.value = ''
|
loadingFloorId.value = ''
|
||||||
}
|
}
|
||||||
failedFloorId.value = floorId
|
failedFloorId.value = floorId
|
||||||
|
if (requestedFloorId.value === floorId) {
|
||||||
|
requestedFloorId.value = ''
|
||||||
|
requestedFloorLabel.value = ''
|
||||||
|
}
|
||||||
|
if (homeSearchMapState.value?.floorId === floorId) {
|
||||||
|
searchVisiblePoiIds.value = []
|
||||||
|
}
|
||||||
showIndoorHint(`${floorLabel || getGuideFloorLabel(floorId)} 加载失败,请稍后重试`, 3600)
|
showIndoorHint(`${floorLabel || getGuideFloorLabel(floorId)} 加载失败,请稍后重试`, 3600)
|
||||||
console.warn('楼层渲染失败:', floorId)
|
console.warn('楼层渲染失败:', floorId)
|
||||||
}
|
}
|
||||||
@@ -1030,6 +1054,8 @@ const handleFloorSwitchFailed = ({ floorId, floorLabel }: { floorId: string; flo
|
|||||||
const handleIndoorViewChange = (view: GuideIndoorView) => {
|
const handleIndoorViewChange = (view: GuideIndoorView) => {
|
||||||
indoorView.value = view
|
indoorView.value = view
|
||||||
if (view !== 'floor') {
|
if (view !== 'floor') {
|
||||||
|
committedFloorId.value = ''
|
||||||
|
syncHomeSearchMarkersForCommittedFloor()
|
||||||
selectedGuidePoi.value = null
|
selectedGuidePoi.value = null
|
||||||
isPoiCardCollapsed.value = false
|
isPoiCardCollapsed.value = false
|
||||||
}
|
}
|
||||||
@@ -1045,6 +1071,8 @@ const handleAutoSwitch = (event: { from: 'overview' | 'floor'; to: 'overview' |
|
|||||||
console.log('自动切换视图:', event)
|
console.log('自动切换视图:', event)
|
||||||
indoorView.value = event.to
|
indoorView.value = event.to
|
||||||
if (event.to !== 'floor') {
|
if (event.to !== 'floor') {
|
||||||
|
committedFloorId.value = ''
|
||||||
|
syncHomeSearchMarkersForCommittedFloor()
|
||||||
selectedGuidePoi.value = null
|
selectedGuidePoi.value = null
|
||||||
isPoiCardCollapsed.value = false
|
isPoiCardCollapsed.value = false
|
||||||
}
|
}
|
||||||
@@ -1053,6 +1081,11 @@ const handleAutoSwitch = (event: { from: 'overview' | 'floor'; to: 'overview' |
|
|||||||
|
|
||||||
const handleInitialModelReady = (event: { view: GuideIndoorView; floorId?: string; elapsedMs?: number }) => {
|
const handleInitialModelReady = (event: { view: GuideIndoorView; floorId?: string; elapsedMs?: number }) => {
|
||||||
const readyFloorId = event.floorId || activeGuideFloor.value
|
const readyFloorId = event.floorId || activeGuideFloor.value
|
||||||
|
indoorView.value = event.view
|
||||||
|
if (event.view !== 'floor') {
|
||||||
|
committedFloorId.value = ''
|
||||||
|
syncHomeSearchMarkersForCommittedFloor()
|
||||||
|
}
|
||||||
if (event.view === 'overview' && readyFloorId) {
|
if (event.view === 'overview' && readyFloorId) {
|
||||||
activeGuideFloor.value = readyFloorId
|
activeGuideFloor.value = readyFloorId
|
||||||
renderedFloorId.value = readyFloorId
|
renderedFloorId.value = readyFloorId
|
||||||
@@ -1092,6 +1125,7 @@ const handleGuidePoiClick = (poi: GuideRenderPoi) => {
|
|||||||
isSimulatingRoute.value = false
|
isSimulatingRoute.value = false
|
||||||
activeGuideFloor.value = poi.floorId
|
activeGuideFloor.value = poi.floorId
|
||||||
renderedFloorId.value = poi.floorId
|
renderedFloorId.value = poi.floorId
|
||||||
|
committedFloorId.value = poi.floorId
|
||||||
showIndoorHint(`${poi.name} · ${getGuideFloorLabel(poi.floorId)}`, 3000)
|
showIndoorHint(`${poi.name} · ${getGuideFloorLabel(poi.floorId)}`, 3000)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1519,6 +1553,7 @@ const resetGuideModelToFloorBaseline = async ({
|
|||||||
if (!guideModelState.isCurrentRequest(resetRequest.requestId)) return
|
if (!guideModelState.isCurrentRequest(resetRequest.requestId)) return
|
||||||
if (resetResult === 'applied') {
|
if (resetResult === 'applied') {
|
||||||
renderedFloorId.value = activeGuideFloor.value
|
renderedFloorId.value = activeGuideFloor.value
|
||||||
|
committedFloorId.value = activeGuideFloor.value
|
||||||
guideModelState.completeViewBaselineReset(resetRequest)
|
guideModelState.completeViewBaselineReset(resetRequest)
|
||||||
console.info('馆内三维模型已恢复楼层视觉基线:', { rendererReason })
|
console.info('馆内三维模型已恢复楼层视觉基线:', { rendererReason })
|
||||||
} else if (resetResult === 'not-ready' || resetResult === undefined) {
|
} else if (resetResult === 'not-ready' || resetResult === undefined) {
|
||||||
@@ -1589,12 +1624,20 @@ const clearHomeSearchMapState = () => {
|
|||||||
homeCategoryModeActive.value = false
|
homeCategoryModeActive.value = false
|
||||||
searchVisiblePoiIds.value = null
|
searchVisiblePoiIds.value = null
|
||||||
searchTargetFocusRequest.value = null
|
searchTargetFocusRequest.value = null
|
||||||
|
homeSearchMapState.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const invalidateHomeSearchFloorCommit = () => {
|
||||||
|
clearHomeSearchMapState()
|
||||||
|
committedFloorId.value = ''
|
||||||
|
indoorView.value = 'overview'
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleHomeCategoryModeChange = (active: boolean) => {
|
const handleHomeCategoryModeChange = (active: boolean) => {
|
||||||
homeCategoryModeActive.value = active
|
homeCategoryModeActive.value = active
|
||||||
if (!active) {
|
if (!active) {
|
||||||
searchVisiblePoiIds.value = null
|
searchVisiblePoiIds.value = null
|
||||||
|
homeSearchMapState.value = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1604,19 +1647,72 @@ const handleHomeCategoryModeChange = (active: boolean) => {
|
|||||||
selectedGuidePoi.value = null
|
selectedGuidePoi.value = null
|
||||||
isPoiCardCollapsed.value = false
|
isPoiCardCollapsed.value = false
|
||||||
is3DMode.value = true
|
is3DMode.value = true
|
||||||
indoorView.value = 'floor'
|
searchVisiblePoiIds.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleHomeSearchResultsChange = (state: PoiCategoryResultState) => {
|
const isHomeSearchFloorCommitted = (floorId: string) => (
|
||||||
if (!state.active) {
|
Boolean(floorId)
|
||||||
searchVisiblePoiIds.value = null
|
&& committedFloorId.value === floorId
|
||||||
|
&& indoorView.value === 'floor'
|
||||||
|
)
|
||||||
|
|
||||||
|
const requestHomeSearchFloor = (floorId: string, floorLabel = '') => {
|
||||||
|
if (!floorId || requestedFloorId.value === floorId || loadingFloorId.value === floorId) return
|
||||||
|
|
||||||
|
requestedFloorId.value = floorId
|
||||||
|
requestedFloorLabel.value = floorLabel || getGuideFloorLabel(floorId)
|
||||||
|
loadingFloorId.value = floorId
|
||||||
|
failedFloorId.value = ''
|
||||||
|
void Promise.resolve(guideMapShellRef.value?.switchFloor?.(floorId))
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('快捷入口楼层切换失败:', error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncHomeSearchMarkersForCommittedFloor = (floorId = committedFloorId.value) => {
|
||||||
|
const state = homeSearchMapState.value
|
||||||
|
if (
|
||||||
|
!state?.active
|
||||||
|
|| state.pending
|
||||||
|
|| state.floorId !== floorId
|
||||||
|
|| indoorView.value !== 'floor'
|
||||||
|
) {
|
||||||
|
searchVisiblePoiIds.value = state?.active ? [] : null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
searchVisiblePoiIds.value = [...state.visiblePoiIds]
|
searchVisiblePoiIds.value = [...state.visiblePoiIds]
|
||||||
if (state.floorId && !state.pending) {
|
}
|
||||||
activeGuideFloor.value = state.floorId
|
|
||||||
|
const handleHomeSearchResultsChange = (state: PoiCategoryResultState) => {
|
||||||
|
if (
|
||||||
|
typeof state.requestId === 'number'
|
||||||
|
&& state.requestId < latestHomeSearchMapRequestId
|
||||||
|
) return
|
||||||
|
if (typeof state.requestId === 'number') {
|
||||||
|
latestHomeSearchMapRequestId = state.requestId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!state.active) {
|
||||||
|
clearHomeSearchMapState()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
homeSearchMapState.value = {
|
||||||
|
...state,
|
||||||
|
visiblePoiIds: [...state.visiblePoiIds]
|
||||||
|
}
|
||||||
|
if (state.pending) {
|
||||||
|
searchVisiblePoiIds.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isHomeSearchFloorCommitted(state.floorId)) {
|
||||||
|
searchVisiblePoiIds.value = []
|
||||||
|
requestHomeSearchFloor(state.floorId, state.floorLabel)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
syncHomeSearchMarkersForCommittedFloor(state.floorId)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleHomeSearchResultTap = async (poi: MuseumPoi, context: PoiSearchContext) => {
|
const handleHomeSearchResultTap = async (poi: MuseumPoi, context: PoiSearchContext) => {
|
||||||
@@ -1672,6 +1768,7 @@ const handleExplainHallClick = async (hallId: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleExplainBack = () => {
|
const handleExplainBack = () => {
|
||||||
|
invalidateHomeSearchFloorCommit()
|
||||||
currentTab.value = 'guide'
|
currentTab.value = 'guide'
|
||||||
syncTopTabToUrl('guide')
|
syncTopTabToUrl('guide')
|
||||||
loadTopTabData('guide')
|
loadTopTabData('guide')
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const testState = vi.hoisted(() => ({
|
|||||||
searchRestoreCount: 0,
|
searchRestoreCount: 0,
|
||||||
searchCollapseAfterDetailCount: 0,
|
searchCollapseAfterDetailCount: 0,
|
||||||
searchResetCount: 0,
|
searchResetCount: 0,
|
||||||
|
floorSwitchRequests: [] as string[],
|
||||||
onShowHandler: null as null | (() => Promise<void> | void),
|
onShowHandler: null as null | (() => Promise<void> | void),
|
||||||
halls: [] as Array<{ id: string; poiId?: string; location?: { poiId?: string }; name: string }>,
|
halls: [] as Array<{ id: string; poiId?: string; location?: { poiId?: string }; name: string }>,
|
||||||
hallResolutionPromises: [] as Array<Promise<Array<{
|
hallResolutionPromises: [] as Array<Promise<Array<{
|
||||||
@@ -102,7 +103,7 @@ const GuideMapShellStub = defineComponent({
|
|||||||
visiblePoiIds: { type: Array, default: null },
|
visiblePoiIds: { type: Array, default: null },
|
||||||
targetFocusRequest: { type: Object, default: null }
|
targetFocusRequest: { type: Object, default: null }
|
||||||
},
|
},
|
||||||
emits: ['floor-change', 'poi-click', 'selection-clear', 'indoor-view-change'],
|
emits: ['floor-change', 'floor-request', 'floor-switch-failed', 'initial-model-ready', 'poi-click', 'selection-clear', 'indoor-view-change'],
|
||||||
setup() {
|
setup() {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
testState.mapMountCount += 1
|
testState.mapMountCount += 1
|
||||||
@@ -113,6 +114,10 @@ const GuideMapShellStub = defineComponent({
|
|||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
switchFloor(floorId: string) {
|
||||||
|
testState.floorSwitchRequests.push(floorId)
|
||||||
|
return Promise.resolve()
|
||||||
|
},
|
||||||
resetToViewBaseline(options: {
|
resetToViewBaseline(options: {
|
||||||
view: 'overview' | 'floor'
|
view: 'overview' | 'floor'
|
||||||
floorId?: string
|
floorId?: string
|
||||||
@@ -217,6 +222,7 @@ beforeEach(() => {
|
|||||||
testState.searchRestoreCount = 0
|
testState.searchRestoreCount = 0
|
||||||
testState.searchCollapseAfterDetailCount = 0
|
testState.searchCollapseAfterDetailCount = 0
|
||||||
testState.searchResetCount = 0
|
testState.searchResetCount = 0
|
||||||
|
testState.floorSwitchRequests = []
|
||||||
testState.onShowHandler = null
|
testState.onShowHandler = null
|
||||||
testState.halls = []
|
testState.halls = []
|
||||||
testState.hallResolutionPromises = []
|
testState.hallResolutionPromises = []
|
||||||
@@ -355,7 +361,7 @@ describe('首页搜索与地图闭环', () => {
|
|||||||
expect(wrapper.findAll('.guide-quick-action')[0]?.text()).toBe('导览')
|
expect(wrapper.findAll('.guide-quick-action')[0]?.text()).toBe('导览')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('向搜索面板传入当前楼层,并将分类结果 ID 精确传给地图', async () => {
|
it('等待渲染器提交目标楼层后,才将快捷分类结果 ID 传给地图', 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)
|
||||||
@@ -370,6 +376,13 @@ describe('首页搜索与地图闭环', () => {
|
|||||||
})
|
})
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
|
expect(testState.floorSwitchRequests).toEqual(['L2'])
|
||||||
|
expect(map.props('activeFloor')).toBe('L1')
|
||||||
|
expect(map.props('visiblePoiIds')).toEqual([])
|
||||||
|
|
||||||
|
map.vm.$emit('floor-change', 'L2')
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
expect(map.props('activeFloor')).toBe('L2')
|
expect(map.props('activeFloor')).toBe('L2')
|
||||||
expect(map.props('indoorView')).toBe('floor')
|
expect(map.props('indoorView')).toBe('floor')
|
||||||
expect(map.props('visiblePoiIds')).toEqual(categoryContext.visiblePoiIds)
|
expect(map.props('visiblePoiIds')).toEqual(categoryContext.visiblePoiIds)
|
||||||
@@ -388,6 +401,78 @@ describe('首页搜索与地图闭环', () => {
|
|||||||
expect(map.props('visiblePoiIds')).toEqual([])
|
expect(map.props('visiblePoiIds')).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('三维模型重新以总览就绪后,不将旧单层状态误作楼层提交', async () => {
|
||||||
|
const wrapper = await mountIndex()
|
||||||
|
const search = wrapper.getComponent(PoiSearchPanelStub)
|
||||||
|
const map = wrapper.getComponent(GuideMapShellStub)
|
||||||
|
|
||||||
|
map.vm.$emit('floor-change', 'L1')
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
expect(map.props('indoorView')).toBe('floor')
|
||||||
|
|
||||||
|
map.vm.$emit('initial-model-ready', { view: 'overview', floorId: 'L1' })
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
expect(map.props('indoorView')).toBe('overview')
|
||||||
|
|
||||||
|
search.vm.$emit('category-mode-change', true)
|
||||||
|
search.vm.$emit('results-change', {
|
||||||
|
...categoryContext,
|
||||||
|
floorId: 'L1',
|
||||||
|
floorLabel: '1F',
|
||||||
|
active: true,
|
||||||
|
requestId: 20
|
||||||
|
})
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
|
expect(testState.floorSwitchRequests).toEqual(['L1'])
|
||||||
|
expect(map.props('visiblePoiIds')).toEqual([])
|
||||||
|
|
||||||
|
map.vm.$emit('floor-change', 'L1')
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
expect(map.props('visiblePoiIds')).toEqual(categoryContext.visiblePoiIds)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('忽略过期搜索结果,不允许旧楼层覆盖当前待提交地图状态', async () => {
|
||||||
|
const wrapper = await mountIndex()
|
||||||
|
const search = wrapper.getComponent(PoiSearchPanelStub)
|
||||||
|
const map = wrapper.getComponent(GuideMapShellStub)
|
||||||
|
|
||||||
|
search.vm.$emit('category-mode-change', true)
|
||||||
|
search.vm.$emit('results-change', { ...categoryContext, active: true, requestId: 8 })
|
||||||
|
search.vm.$emit('results-change', {
|
||||||
|
...categoryContext,
|
||||||
|
floorId: 'L1',
|
||||||
|
floorLabel: '1F',
|
||||||
|
visiblePoiIds: ['stale-l1'],
|
||||||
|
active: true,
|
||||||
|
requestId: 7
|
||||||
|
})
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
|
expect(testState.floorSwitchRequests).toEqual(['L2'])
|
||||||
|
map.vm.$emit('floor-change', 'L2')
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
expect(map.props('visiblePoiIds')).toEqual(categoryContext.visiblePoiIds)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('楼层切换失败时清空快捷分类标记,重试提交后恢复与列表一致的 ID', async () => {
|
||||||
|
const wrapper = await mountIndex()
|
||||||
|
const search = wrapper.getComponent(PoiSearchPanelStub)
|
||||||
|
const map = wrapper.getComponent(GuideMapShellStub)
|
||||||
|
|
||||||
|
search.vm.$emit('category-mode-change', true)
|
||||||
|
search.vm.$emit('results-change', { ...categoryContext, active: true, requestId: 12 })
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
map.vm.$emit('floor-switch-failed', { floorId: 'L2', floorLabel: '2F' })
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
expect(map.props('activeFloor')).toBe('L1')
|
||||||
|
expect(map.props('visiblePoiIds')).toEqual([])
|
||||||
|
|
||||||
|
map.vm.$emit('floor-change', 'L2')
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
expect(map.props('visiblePoiIds')).toEqual(categoryContext.visiblePoiIds)
|
||||||
|
})
|
||||||
|
|
||||||
it('楼层数据按高到低返回时,首次进入仍将 1F 作为自动缩放的默认目标', async () => {
|
it('楼层数据按高到低返回时,首次进入仍将 1F 作为自动缩放的默认目标', async () => {
|
||||||
const originalFloors = [...floors]
|
const originalFloors = [...floors]
|
||||||
floors.splice(0, floors.length,
|
floors.splice(0, floors.length,
|
||||||
@@ -417,6 +502,8 @@ describe('首页搜索与地图闭环', () => {
|
|||||||
...categoryContext,
|
...categoryContext,
|
||||||
active: true
|
active: true
|
||||||
})
|
})
|
||||||
|
map.vm.$emit('floor-change', 'L2')
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
search.vm.$emit('result-tap', poi, categoryContext)
|
search.vm.$emit('result-tap', poi, categoryContext)
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
|
|
||||||
@@ -486,6 +573,8 @@ describe('首页搜索与地图闭环', () => {
|
|||||||
|
|
||||||
search.vm.$emit('category-mode-change', true)
|
search.vm.$emit('category-mode-change', true)
|
||||||
search.vm.$emit('results-change', { ...categoryContext, active: true })
|
search.vm.$emit('results-change', { ...categoryContext, active: true })
|
||||||
|
map.vm.$emit('floor-change', 'L2')
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
search.vm.$emit('result-tap', poi, categoryContext)
|
search.vm.$emit('result-tap', poi, categoryContext)
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ const lastResultState = (wrapper: ReturnType<typeof mount>) => (
|
|||||||
categoryId: string
|
categoryId: string
|
||||||
keyword: string
|
keyword: string
|
||||||
pending?: boolean
|
pending?: boolean
|
||||||
|
requestId?: number
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -323,6 +324,36 @@ describe('PoiSearchPanel 搜索状态契约', () => {
|
|||||||
wrapper.unmount()
|
wrapper.unmount()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('首页分类结果在组件重挂载后仍发送递增的跨组件版本号', async () => {
|
||||||
|
const firstWrapper = mount(PoiSearchPanel, {
|
||||||
|
props: {
|
||||||
|
variant: 'home',
|
||||||
|
currentFloorId: 'floor-1',
|
||||||
|
currentFloorLabel: '1F'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await flushMountedSearch()
|
||||||
|
await firstWrapper.get('[data-testid="poi-category-restroom"]').trigger('tap')
|
||||||
|
await flushMountedSearch()
|
||||||
|
const firstRequestId = lastResultState(firstWrapper).requestId
|
||||||
|
firstWrapper.unmount()
|
||||||
|
|
||||||
|
const secondWrapper = mount(PoiSearchPanel, {
|
||||||
|
props: {
|
||||||
|
variant: 'home',
|
||||||
|
currentFloorId: 'floor-1',
|
||||||
|
currentFloorLabel: '1F'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await flushMountedSearch()
|
||||||
|
await secondWrapper.get('[data-testid="poi-category-restroom"]').trigger('tap')
|
||||||
|
await flushMountedSearch()
|
||||||
|
|
||||||
|
expect(firstRequestId).toEqual(expect.any(Number))
|
||||||
|
expect(lastResultState(secondWrapper).requestId).toBeGreaterThan(firstRequestId as number)
|
||||||
|
secondWrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
it('首页快捷分类请求期间清空列表并下发 pending 空地图 ID', async () => {
|
it('首页快捷分类请求期间清空列表并下发 pending 空地图 ID', async () => {
|
||||||
const initialState = createSearchState()
|
const initialState = createSearchState()
|
||||||
const deferredCategory = createDeferred<GuidePoiSearchViewState>()
|
const deferredCategory = createDeferred<GuidePoiSearchViewState>()
|
||||||
|
|||||||
Reference in New Issue
Block a user