提交室内导览交互与讲解优化

This commit is contained in:
lyf
2026-06-14 23:48:13 +08:00
parent a7c1879f60
commit feb7310a46
33 changed files with 3257 additions and 361 deletions

View File

@@ -3,9 +3,20 @@
<view class="map-layer" :class="`map-${mapType}`">
<template v-if="mapType === 'indoor'">
<!-- #ifdef H5 -->
<SgsMapRenderer
v-if="useSgsMapRenderer"
ref="indoorRendererRef"
class="indoor-three-map"
:initial-floor-id="activeFloorId"
:initial-view="indoorInitialView"
:floors="props.floors"
:target-focus="targetFocusRequest"
@target-focus="handleTargetFocus"
@floor-change="handleSdkFloorChange"
/>
<ThreeMap
v-if="indoorModelSource"
ref="threeMapRef"
v-else-if="indoorModelSource"
ref="indoorRendererRef"
class="indoor-three-map"
:asset-base-url="indoorAssetBaseUrl"
:model-source="indoorModelSource"
@@ -14,7 +25,11 @@
:show-controls="false"
:show-poi="shouldShowIndoorPois"
:target-focus="targetFocusRequest"
@floor-change="handleThreeFloorChange"
@poi-click="handlePoiClick"
@selection-clear="handleSelectionClear"
@target-focus="handleTargetFocus"
@auto-switch="handleAutoSwitch"
/>
<!-- #endif -->
<!-- #ifndef H5 -->
@@ -89,27 +104,15 @@
<slot name="overlay"></slot>
<view
v-if="mapType === 'indoor' && showIndoorViewToggle"
class="indoor-view-toggle"
:style="indoorViewToggleStyle"
v-if="showIndoorRightControls && showLayerModeToggle"
class="layer-mode-toggle"
:style="layerModeToggleStyle"
@tap="handleLayerModeToggle"
>
<view
class="indoor-view-item"
:class="{ active: indoorView === 'overview' }"
@tap="handleIndoorViewChange('overview')"
>
<text class="indoor-view-label">全馆</text>
</view>
<view
class="indoor-view-item"
:class="{ active: indoorView === 'floor' }"
@tap="handleIndoorViewChange('floor')"
>
<text class="indoor-view-label">楼层</text>
</view>
<text class="layer-mode-label">{{ layerModeActionLabel }}</text>
</view>
<view v-if="showFloor" class="floor-switcher" :style="floorSwitcherStyle">
<view v-if="showIndoorRightControls && showFloor" class="floor-switcher" :style="floorSwitcherStyle">
<view
v-for="floor in floorLabels"
:key="floor"
@@ -141,9 +144,14 @@ import { computed, ref } from 'vue'
import TencentMap from '@/components/map/TencentMap.vue'
// #ifdef H5
import ThreeMap from '@/components/map/ThreeMap.vue'
import SgsMapRenderer from '@/components/map/SgsMapRenderer.vue'
import {
isSgsSdkMode
} from '@/config/dataSource'
// #endif
import type {
GuideModelSource
GuideModelSource,
GuideRenderPoi
} from '@/domain/guideModel'
interface GuideFloorOption {
@@ -169,7 +177,8 @@ interface TargetPoiFocusResult {
message?: string
}
type IndoorViewMode = 'overview' | 'floor'
type IndoorViewMode = 'overview' | 'floor' | 'multi'
type LayerDisplayMode = 'single' | 'multi'
const props = withDefaults(defineProps<{
searchText?: string
@@ -177,14 +186,15 @@ const props = withDefaults(defineProps<{
activeFloor?: string
indoorView?: IndoorViewMode
indoorInitialView?: IndoorViewMode
layerMode?: LayerDisplayMode
searchTop?: string
floorTop?: string
indoorViewToggleTop?: string
layerModeToggleTop?: string
toolsTop?: string
tools?: string[]
showSearch?: boolean
showFloor?: boolean
showIndoorViewToggle?: boolean
showLayerModeToggle?: boolean
modeTop?: string
modeLayout?: 'full' | 'status'
modeStatus?: string
@@ -203,14 +213,15 @@ const props = withDefaults(defineProps<{
activeFloor: '1F',
indoorView: 'floor',
indoorInitialView: 'floor',
layerMode: 'single',
searchTop: '16px',
floorTop: '164px',
indoorViewToggleTop: '154px',
layerModeToggleTop: '154px',
toolsTop: '406px',
tools: () => [] as string[],
showSearch: true,
showFloor: true,
showIndoorViewToggle: false,
showLayerModeToggle: false,
modeTop: '60px',
modeLayout: 'full',
modeStatus: '',
@@ -231,16 +242,31 @@ const emit = defineEmits<{
floorChange: [floor: string]
toolClick: [tool: string]
indoorViewChange: [view: IndoorViewMode]
layerModeChange: [mode: LayerDisplayMode]
poiClick: [poi: GuideRenderPoi]
selectionClear: []
targetFocus: [result: TargetPoiFocusResult]
autoSwitch: [event: { from: 'overview' | 'floor'; to: 'overview' | 'floor'; trigger: string; distance: number }]
}>()
const threeMapRef = ref<{
const indoorRendererRef = ref<{
switchFloor?: (floorId: string) => Promise<void> | void
showOverview?: () => Promise<void> | void
showMultiFloor?: () => Promise<void> | void
resetCamera?: () => void
setCameraPreset?: (preset: 'top' | 'oblique') => void
clearSelection?: (shouldEmit?: boolean) => void
disableAutoSwitchTemporarily?: (durationMs: number) => void
} | null>(null)
const floorLabels = computed(() => props.floors.map((floor) => floor.label))
const indoorModelSource = computed(() => props.indoorModelSource)
// #ifdef H5
const useSgsMapRenderer = computed(() => isSgsSdkMode())
// #endif
const showIndoorRightControls = computed(() => (
props.mapType === 'indoor' && props.indoorView !== 'overview'
))
const activeFloorId = computed(() => props.normalizeFloorId(props.activeFloor))
@@ -252,8 +278,8 @@ const floorSwitcherStyle = computed(() => ({
top: props.floorTop
}))
const indoorViewToggleStyle = computed(() => ({
top: props.indoorViewToggleTop
const layerModeToggleStyle = computed(() => ({
top: props.layerModeToggleTop
}))
const modeRowStyle = computed(() => ({
@@ -264,7 +290,15 @@ const toolStackStyle = computed(() => ({
top: props.toolsTop
}))
const shouldShowIndoorPois = computed(() => Boolean(props.targetFocusRequest))
const shouldShowIndoorPois = computed(() => (
props.mapType === 'indoor'
) || Boolean(props.targetFocusRequest))
const nextLayerMode = computed<LayerDisplayMode>(() => (
props.layerMode === 'multi' ? 'single' : 'multi'
))
const layerModeActionLabel = computed(() => (
nextLayerMode.value === 'multi' ? '多层' : '单层'
))
const handleSearchTap = () => {
emit('searchTap')
@@ -276,28 +310,79 @@ const handleModeChange = (mode: '2d' | '3d') => {
const handleFloorChange = (floor: string) => {
const floorId = props.normalizeFloorId(floor)
void threeMapRef.value?.switchFloor?.(floorId)
indoorRendererRef.value?.disableAutoSwitchTemporarily?.(10000)
void indoorRendererRef.value?.switchFloor?.(floorId)
emit('indoorViewChange', 'floor')
emit('layerModeChange', 'single')
emit('floorChange', floor)
}
const handleIndoorViewChange = (view: IndoorViewMode) => {
if (view === 'overview') {
void threeMapRef.value?.showOverview?.()
const handleLayerModeChange = (mode: LayerDisplayMode) => {
// 手动切换展示层数时临时禁用缩放自动切换 10 秒
indoorRendererRef.value?.disableAutoSwitchTemporarily?.(10000)
if (mode === 'multi') {
void indoorRendererRef.value?.showMultiFloor?.()
emit('indoorViewChange', 'multi')
} else {
void threeMapRef.value?.switchFloor?.(activeFloorId.value)
void indoorRendererRef.value?.switchFloor?.(activeFloorId.value)
emit('indoorViewChange', 'floor')
}
emit('indoorViewChange', view)
emit('layerModeChange', mode)
}
const handleLayerModeToggle = () => {
handleLayerModeChange(nextLayerMode.value)
}
const handleToolClick = (tool: string) => {
if (props.mapType === 'indoor') {
indoorRendererRef.value?.disableAutoSwitchTemporarily?.(10000)
if (tool === '重置') {
indoorRendererRef.value?.resetCamera?.()
} else if (tool === '俯视') {
indoorRendererRef.value?.setCameraPreset?.('top')
} else if (tool === '斜视') {
indoorRendererRef.value?.setCameraPreset?.('oblique')
} else if (tool === '清除') {
indoorRendererRef.value?.clearSelection?.()
}
}
emit('toolClick', tool)
}
const handleThreeFloorChange = (floorId: string) => {
const floor = props.floors.find((item) => item.id === floorId)
emit('floorChange', floor?.label || floorId)
emit('indoorViewChange', 'floor')
emit('layerModeChange', 'single')
}
const handlePoiClick = (poi: GuideRenderPoi) => {
emit('poiClick', poi)
}
const handleSelectionClear = () => {
emit('selectionClear')
}
const handleTargetFocus = (result: TargetPoiFocusResult) => {
emit('targetFocus', result)
}
const handleSdkFloorChange = (floorId: string) => {
const floor = props.floors.find((item) => item.id === floorId)
if (floor) {
emit('floorChange', floor.label)
}
}
const handleAutoSwitch = (event: { from: 'overview' | 'floor'; to: 'overview' | 'floor'; trigger: string; distance: number }) => {
emit('autoSwitch', event)
}
</script>
<style scoped lang="scss">
@@ -536,40 +621,31 @@ const handleTargetFocus = (result: TargetPoiFocusResult) => {
z-index: 35;
}
.indoor-view-toggle {
.layer-mode-toggle {
position: absolute;
right: 16px;
width: 58px;
padding: 5px;
width: 46px;
height: 36px;
display: flex;
flex-direction: column;
gap: 5px;
background: rgba(255, 255, 255, 0.94);
align-items: center;
justify-content: center;
box-sizing: border-box;
background: #ffffff;
border: 1px solid #dde5df;
border-radius: 10px;
box-shadow: 0 8px 18px rgba(110, 127, 115, 0.12);
z-index: 36;
}
.indoor-view-item {
min-height: 34px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 7px;
color: #59645d;
.layer-mode-toggle:active {
background: #f5f7f2;
}
.indoor-view-item.active {
background: var(--museum-accent);
color: #ffffff;
box-shadow: 0 6px 12px rgba(82, 107, 91, 0.18);
}
.indoor-view-label {
.layer-mode-label {
font-size: 12px;
font-weight: 700;
line-height: 1;
line-height: 16px;
font-weight: 500;
color: #545861;
}
.floor-item {
@@ -616,7 +692,7 @@ const handleTargetFocus = (result: TargetPoiFocusResult) => {
.tool-stack {
position: absolute;
right: 18px;
left: 16px;
display: flex;
flex-direction: column;
gap: 8px;