@@ -833,6 +833,7 @@ const emitFloorChange = (floorId: string, sceneRevision = props.sceneRevision) =
|
|||||||
}
|
}
|
||||||
const currentFloor = ref(props.initialFloorId)
|
const currentFloor = ref(props.initialFloorId)
|
||||||
const selectedPOI = ref<RenderPoi | null>(null)
|
const selectedPOI = ref<RenderPoi | null>(null)
|
||||||
|
let routeStartSelectionGeneration = 0
|
||||||
const activeFocusPoiId = ref('')
|
const activeFocusPoiId = ref('')
|
||||||
const floorIndex = ref<FloorIndexItem[]>([])
|
const floorIndex = ref<FloorIndexItem[]>([])
|
||||||
const renderPackage = ref<GuideModelRenderPackage | null>(null)
|
const renderPackage = ref<GuideModelRenderPackage | null>(null)
|
||||||
@@ -8157,6 +8158,14 @@ const selectRouteStartCandidateNear = (
|
|||||||
const handleSceneTap = async (event: PointerEvent) => {
|
const handleSceneTap = async (event: PointerEvent) => {
|
||||||
if (!camera || !renderer || !getContainerElement()) return
|
if (!camera || !renderer || !getContainerElement()) return
|
||||||
|
|
||||||
|
const routeStartSelectionActiveAtTap = props.routeStartSelectionActive
|
||||||
|
const routeStartSelectionGenerationAtTap = routeStartSelectionGeneration
|
||||||
|
const canResolveRouteStartForTap = () => (
|
||||||
|
routeStartSelectionActiveAtTap
|
||||||
|
&& props.routeStartSelectionActive
|
||||||
|
&& routeStartSelectionGeneration === routeStartSelectionGenerationAtTap
|
||||||
|
)
|
||||||
|
|
||||||
const rect = renderer.domElement.getBoundingClientRect()
|
const rect = renderer.domElement.getBoundingClientRect()
|
||||||
const pointer = new THREE.Vector2(
|
const pointer = new THREE.Vector2(
|
||||||
((event.clientX - rect.left) / rect.width) * 2 - 1,
|
((event.clientX - rect.left) / rect.width) * 2 - 1,
|
||||||
@@ -8215,7 +8224,8 @@ const handleSceneTap = async (event: PointerEvent) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const poi = findLoadedPoi(selectedPoi.id) || selectedPoi
|
const poi = findLoadedPoi(selectedPoi.id) || selectedPoi
|
||||||
if (props.routeStartSelectionActive) {
|
if (routeStartSelectionActiveAtTap) {
|
||||||
|
if (!canResolveRouteStartForTap()) return
|
||||||
if (poi.positionGltf) {
|
if (poi.positionGltf) {
|
||||||
selectRouteStartCandidateNear(
|
selectRouteStartCandidateNear(
|
||||||
poi.floorId,
|
poi.floorId,
|
||||||
@@ -8248,7 +8258,8 @@ const handleSceneTap = async (event: PointerEvent) => {
|
|||||||
: []
|
: []
|
||||||
const matchedModelPoi = findPoiForModelHits(modelHits)
|
const matchedModelPoi = findPoiForModelHits(modelHits)
|
||||||
|
|
||||||
if (props.routeStartSelectionActive) {
|
if (routeStartSelectionActiveAtTap) {
|
||||||
|
if (!canResolveRouteStartForTap()) return
|
||||||
if (matchedModelPoi?.poi.positionGltf) {
|
if (matchedModelPoi?.poi.positionGltf) {
|
||||||
selectRouteStartCandidateNear(
|
selectRouteStartCandidateNear(
|
||||||
matchedModelPoi.poi.floorId,
|
matchedModelPoi.poi.floorId,
|
||||||
@@ -9426,11 +9437,13 @@ watch(() => props.visiblePoiIds, () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
watch(() => props.routeStartSelectionActive, (isSelectingStart) => {
|
watch(() => props.routeStartSelectionActive, (isSelectingStart) => {
|
||||||
|
routeStartSelectionGeneration += 1
|
||||||
if (isSelectingStart) {
|
if (isSelectingStart) {
|
||||||
clearMapSelection(false)
|
clearMapSelection(false)
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
immediate: true
|
immediate: true,
|
||||||
|
flush: 'sync'
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => [props.routePreview, props.showRoute, props.routeNavigationActive], () => {
|
watch(() => [props.routePreview, props.showRoute, props.routeNavigationActive], () => {
|
||||||
|
|||||||
@@ -38,15 +38,15 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="startCandidate" class="route-confirm-mask" @tap.stop="emit('cancelStart')">
|
<view v-if="startCandidate" class="route-confirm-mask" @tap.stop="handleCancelStart">
|
||||||
<view class="route-confirm-card" @tap.stop>
|
<view class="route-confirm-card" @tap.stop>
|
||||||
<text class="route-confirm-title">确认选择</text>
|
<text class="route-confirm-title">确认选择</text>
|
||||||
<text class="route-confirm-message">{{ normalizeVisitorPoiDisplayName(startCandidate.name) }} 为起点吗?</text>
|
<text class="route-confirm-message">{{ normalizeVisitorPoiDisplayName(startCandidate.name) }} 为起点吗?</text>
|
||||||
<view class="route-confirm-actions">
|
<view class="route-confirm-actions">
|
||||||
<view class="route-confirm-action secondary" @tap="emit('cancelStart')">
|
<view class="route-confirm-action secondary" @tap.stop="handleCancelStart">
|
||||||
<text class="route-confirm-action-text">取消</text>
|
<text class="route-confirm-action-text">取消</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="route-confirm-action primary" @tap="emit('confirmStart', startCandidate)">
|
<view class="route-confirm-action primary" @tap.stop="handleConfirmStart">
|
||||||
<text class="route-confirm-action-text">确定</text>
|
<text class="route-confirm-action-text">确定</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
||||||
import type { RoutePointOption } from '@/components/navigation/RoutePointPicker.vue'
|
import type { RoutePointOption } from '@/components/navigation/RoutePointPicker.vue'
|
||||||
import { normalizeVisitorPoiDisplayName } from '@/view-models/visitorPoiPresentation'
|
import { normalizeVisitorPoiDisplayName } from '@/view-models/visitorPoiPresentation'
|
||||||
|
|
||||||
@@ -87,6 +87,42 @@ const emit = defineEmits<{
|
|||||||
back: []
|
back: []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const confirmationInteractive = ref(false)
|
||||||
|
let confirmationInteractiveTimer: ReturnType<typeof window.setTimeout> | null = null
|
||||||
|
const confirmationInteractionGuardMs = 350
|
||||||
|
|
||||||
|
const clearConfirmationInteractiveTimer = () => {
|
||||||
|
if (confirmationInteractiveTimer === null) return
|
||||||
|
window.clearTimeout(confirmationInteractiveTimer)
|
||||||
|
confirmationInteractiveTimer = null
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.startCandidate, (candidate) => {
|
||||||
|
clearConfirmationInteractiveTimer()
|
||||||
|
confirmationInteractive.value = false
|
||||||
|
if (!candidate) return
|
||||||
|
|
||||||
|
confirmationInteractiveTimer = window.setTimeout(() => {
|
||||||
|
confirmationInteractiveTimer = null
|
||||||
|
confirmationInteractive.value = Boolean(props.startCandidate)
|
||||||
|
}, confirmationInteractionGuardMs)
|
||||||
|
}, {
|
||||||
|
immediate: true,
|
||||||
|
flush: 'sync'
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(clearConfirmationInteractiveTimer)
|
||||||
|
|
||||||
|
const handleCancelStart = () => {
|
||||||
|
if (!confirmationInteractive.value) return
|
||||||
|
emit('cancelStart')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirmStart = () => {
|
||||||
|
if (!confirmationInteractive.value || !props.startCandidate) return
|
||||||
|
emit('confirmStart', props.startCandidate)
|
||||||
|
}
|
||||||
|
|
||||||
const pointMeta = (point: RoutePointOption) => (
|
const pointMeta = (point: RoutePointOption) => (
|
||||||
point.categoryLabel ? `${point.floorLabel} · ${point.categoryLabel}` : point.floorLabel
|
point.categoryLabel ? `${point.floorLabel} · ${point.categoryLabel}` : point.floorLabel
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,6 +13,23 @@ const point = (name: string, floorLabel: string) => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('RoutePlannerPanel', () => {
|
describe('RoutePlannerPanel', () => {
|
||||||
|
it('忽略打开确认层的同一次点击,再响应后续确认或取消操作', async () => {
|
||||||
|
const wrapper = mount(RoutePlannerPanel, {
|
||||||
|
props: {
|
||||||
|
startCandidate: point('服务台', '1F')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await wrapper.get('.route-confirm-mask').trigger('tap')
|
||||||
|
await wrapper.get('.route-confirm-action.primary').trigger('tap')
|
||||||
|
expect(wrapper.emitted('cancelStart')).toBeUndefined()
|
||||||
|
expect(wrapper.emitted('confirmStart')).toBeUndefined()
|
||||||
|
|
||||||
|
await new Promise<void>((resolve) => window.setTimeout(resolve, 350))
|
||||||
|
await wrapper.get('.route-confirm-action.primary').trigger('tap')
|
||||||
|
expect(wrapper.emitted('confirmStart')).toEqual([[expect.objectContaining({ poiId: '服务台' })]])
|
||||||
|
})
|
||||||
|
|
||||||
it('在导航副标题中显示起点和终点楼层', () => {
|
it('在导航副标题中显示起点和终点楼层', () => {
|
||||||
const wrapper = mount(RoutePlannerPanel, {
|
const wrapper = mount(RoutePlannerPanel, {
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
Reference in New Issue
Block a user