From c9f32714aa5f31d53b59679238f54534220461ae Mon Sep 17 00:00:00 2001 From: lyf <2514544224@qq.com> Date: Mon, 29 Jun 2026 02:04:08 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AF=BC=E8=A7=88=E9=A1=B5?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E4=B8=8E=E5=BF=AB=E6=8D=B7=E5=85=A5=E5=8F=A3?= =?UTF-8?q?=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/navigation/GuideMapShell.vue | 101 +++++++++++++- src/pages/index/index.vue | 146 +++++++++++++++++++- 2 files changed, 236 insertions(+), 11 deletions(-) diff --git a/src/components/navigation/GuideMapShell.vue b/src/components/navigation/GuideMapShell.vue index 7d56577..c9fa7ce 100644 --- a/src/components/navigation/GuideMapShell.vue +++ b/src/components/navigation/GuideMapShell.vue @@ -170,9 +170,48 @@ v-for="tool in tools" :key="tool" class="tool-btn" + :aria-label="tool" + :title="tool" @tap="handleToolClick(tool)" > - {{ tool }} + + + + + + + + + + + + + + + + + + {{ tool }} @@ -364,7 +403,7 @@ const indoorRendererRef = ref<{ showMultiFloor?: () => Promise | void resetCamera?: () => void setCameraPreset?: (preset: 'top' | 'oblique') => void - zoomCamera?: (direction: 'in' | 'out') => void + zoomCamera?: (direction: 'in' | 'out', options?: { source?: 'button' | 'gesture' }) => void clearSelection?: (shouldEmit?: boolean) => void clearRoute?: () => Promise | void disableAutoSwitchTemporarily?: (durationMs: number) => void @@ -477,7 +516,7 @@ const handleToolClick = (tool: string) => { if (props.mapType === 'indoor') { indoorRendererRef.value?.disableAutoSwitchTemporarily?.(10000) - if (tool === '重置') { + if (tool === '复位') { indoorRendererRef.value?.resetCamera?.() } else if (tool === '俯视') { indoorRendererRef.value?.setCameraPreset?.('top') @@ -491,8 +530,16 @@ const handleToolClick = (tool: string) => { emit('toolClick', tool) } +const toolIconType = (tool: string) => { + if (tool === '复位') return 'reset' + if (tool === '俯视') return 'top' + if (tool === '斜视') return 'oblique' + if (tool === '清除') return 'clear' + return 'text' +} + const handleZoomClick = (direction: 'in' | 'out') => { - indoorRendererRef.value?.zoomCamera?.(direction) + indoorRendererRef.value?.zoomCamera?.(direction, { source: 'button' }) emit('toolClick', direction === 'in' ? '放大' : '缩小') } @@ -892,9 +939,37 @@ defineExpose({ } .floor-header-icon { - font-size: 16px; - line-height: 16px; + width: 18px; + height: 18px; + position: relative; + display: block; color: #151713; + font-size: 0; + line-height: 0; +} + +.floor-header-icon::before, +.floor-header-icon::after { + content: ''; + position: absolute; + box-sizing: border-box; + border: 1.8px solid currentColor; + border-radius: 2px; +} + +.floor-header-icon::before { + left: 2px; + top: 4px; + width: 12px; + height: 10px; +} + +.floor-header-icon::after { + left: 5px; + top: 1px; + width: 12px; + height: 10px; + background: #ffffff; } .floor-header.active .floor-header-icon { @@ -1001,6 +1076,20 @@ defineExpose({ box-sizing: border-box; } +.tool-btn:active { + background: #f6f7f4; + transform: translateY(1px); +} + +.tool-icon-svg { + width: 22px; + height: 22px; + stroke: #151713; + stroke-width: 1.8; + stroke-linecap: round; + stroke-linejoin: round; +} + .tool-label { font-size: 12px; line-height: 16px; diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue index 1556aa3..356c284 100644 --- a/src/pages/index/index.vue +++ b/src/pages/index/index.vue @@ -36,6 +36,8 @@ show-floor-header :show-layer-mode-toggle="false" :show-floor="is3DMode" + :tools="indoorCameraTools" + tools-top="" tools-bottom="54px" show-zoom-controls zoom-controls-top="calc(100vh - 284px)" @@ -57,6 +59,9 @@ 主入口 约 4 分钟 + + {{ indoorGestureHintText }} + + + + + 室内导览 + + + 来馆 @@ -325,6 +337,24 @@ const normalizeGuideFloorId = (labelOrId: string) => guideUseCase.normalizeFloor const indoorLayerMode = computed(() => ( indoorView.value === 'multi' ? 'multi' : 'single' )) +const indoorCameraTools = computed(() => (is3DMode.value ? ['复位', '俯视', '斜视'] : [])) +const showIndoorGestureHint = ref(true) +const indoorGestureHintText = ref('拖动旋转 · 双指缩放 · 点选标记查看位置') +let indoorGestureHintTimer: ReturnType | null = null + +const showIndoorHint = (text = '拖动旋转 · 双指缩放 · 点选标记查看位置', durationMs = 5200) => { + indoorGestureHintText.value = text + showIndoorGestureHint.value = is3DMode.value + + if (indoorGestureHintTimer) { + clearTimeout(indoorGestureHintTimer) + } + + indoorGestureHintTimer = setTimeout(() => { + showIndoorGestureHint.value = false + indoorGestureHintTimer = null + }, durationMs) +} const routePlanSummary = computed(() => { if (activeRoutePreview.value) { @@ -446,6 +476,8 @@ const syncTopTabFromHash = () => { } onMounted(() => { + showIndoorHint() + if (typeof window === 'undefined') return syncTopTabFromHash() @@ -453,6 +485,11 @@ onMounted(() => { }) onUnmounted(() => { + if (indoorGestureHintTimer) { + clearTimeout(indoorGestureHintTimer) + indoorGestureHintTimer = null + } + if (typeof window === 'undefined') return window.removeEventListener('hashchange', syncTopTabFromHash) @@ -584,6 +621,7 @@ const handleFloorChange = (floor: string) => { indoorView.value = 'floor' selectedGuidePoi.value = null isPoiCardCollapsed.value = false + showIndoorHint(`已切换到 ${floor},可拖动旋转查看`, 3200) console.log('切换楼层:', floor) } @@ -593,6 +631,12 @@ const handleIndoorViewChange = (view: GuideIndoorView) => { selectedGuidePoi.value = null isPoiCardCollapsed.value = false } + + if (view === 'multi') { + showIndoorHint('已切换多层视图,可点选楼层查看', 3200) + } else if (view === 'floor') { + showIndoorHint('已切换单层视图,可点选标记查看位置', 3200) + } } const handleAutoSwitch = (event: { from: 'overview' | 'floor'; to: 'overview' | 'floor'; trigger: string; distance: number }) => { @@ -602,6 +646,7 @@ const handleAutoSwitch = (event: { from: 'overview' | 'floor'; to: 'overview' | selectedGuidePoi.value = null isPoiCardCollapsed.value = false } + showIndoorHint(event.to === 'floor' ? '已进入单层视图,可点选标记查看位置' : '已返回建筑外观', 3600) } const handleGuidePoiClick = (poi: GuideRenderPoi) => { @@ -611,6 +656,7 @@ const handleGuidePoiClick = (poi: GuideRenderPoi) => { isSimulatingRoute.value = false const matchedFloor = guideFloors.value.find((floor) => floor.id === poi.floorId) activeGuideFloor.value = matchedFloor?.label || poi.floorId + showIndoorHint('已选中标记,可查看位置预览', 3200) } const handleGuideSelectionClear = () => { @@ -839,6 +885,7 @@ const handleEnter3DMode = () => { indoorView.value = 'overview' is3DMode.value = true guideOutdoorState.value = 'home' + showIndoorHint() if (!routePointOptions.value.length) { void loadRouteTargets() } @@ -857,6 +904,7 @@ const handleModeChange = (mode: '2d' | '3d') => { clearRoutePreviewState() } else { indoorView.value = 'overview' + showIndoorHint() if (!routePointOptions.value.length) { void loadRouteTargets() } @@ -1273,12 +1321,14 @@ const handleExplainBack = () => { } .guide-quick-action { - width: 84px; - min-height: 38px; - padding: 0 12px; + width: 88px; + min-height: 48px; + padding: 7px 8px 6px; display: flex; + flex-direction: column; align-items: center; justify-content: center; + gap: 4px; box-sizing: border-box; background: rgba(255, 255, 255, 0.95); border: 1px solid #dde5df; @@ -1291,9 +1341,66 @@ const handleExplainBack = () => { border-color: #151713; } +.guide-quick-action:active { + transform: translateY(1px); +} + +.guide-quick-icon { + width: 22px; + height: 22px; + position: relative; + color: #151713; +} + +.guide-quick-action.primary .guide-quick-icon { + color: var(--museum-accent); +} + +.guide-quick-icon-floor { + position: absolute; + left: 3px; + width: 16px; + height: 9px; + box-sizing: border-box; + border: 1.7px solid currentColor; + border-radius: 2px; + transform: skewY(-18deg); +} + +.guide-quick-icon-floor.top { + top: 2px; +} + +.guide-quick-icon-floor.bottom { + top: 10px; +} + +.guide-quick-pin { + position: absolute; + left: 5px; + top: 2px; + width: 12px; + height: 16px; + box-sizing: border-box; + border: 1.8px solid currentColor; + border-radius: 7px 7px 7px 0; + transform: rotate(-45deg); +} + +.guide-quick-pin::after { + content: ''; + position: absolute; + left: 3px; + top: 3px; + width: 4px; + height: 4px; + background: currentColor; + border-radius: 50%; +} + .guide-quick-action-text { - font-size: 13px; - line-height: 18px; + font-size: 12px; + line-height: 16px; font-weight: 600; color: #151713; text-align: center; @@ -1402,6 +1509,35 @@ const handleExplainBack = () => { color: #1f2329; } +.indoor-gesture-hint { + position: absolute; + top: clamp(174px, 23vh, 228px); + left: 50%; + max-width: calc(100% - 136px); + min-height: 34px; + padding: 7px 12px; + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + background: rgba(21, 23, 19, 0.76); + border: 1px solid rgba(255, 255, 255, 0.16); + border-radius: 8px; + transform: translateX(-50%); + pointer-events: none; + z-index: 34; +} + +.indoor-gesture-text { + min-width: 0; + font-size: 12px; + line-height: 17px; + font-weight: 500; + color: #ffffff; + text-align: center; + white-space: normal; +} + .home-shortcuts-card { position: absolute; left: 12px;