调整导览讲解为底部主导航

This commit is contained in:
lyf
2026-06-29 20:55:13 +08:00
parent 81b9eac2f7
commit 4c9d480cce
12 changed files with 271 additions and 64 deletions

View File

@@ -1,6 +1,10 @@
<template> <template>
<view class="audio-player-host"> <view class="audio-player-host">
<view class="audio-player" v-if="visible && currentAudio"> <view
class="audio-player"
:class="{ 'avoid-bottom-nav': avoidBottomNav }"
v-if="visible && currentAudio"
>
<!-- 迷你播放器 (底部固定) --> <!-- 迷你播放器 (底部固定) -->
<view class="mini-player" @tap="handleExpand"> <view class="mini-player" @tap="handleExpand">
<!-- 封面图 --> <!-- 封面图 -->
@@ -65,6 +69,7 @@ const props = defineProps<{
visible?: boolean visible?: boolean
audio?: AudioItem | null audio?: AudioItem | null
autoPlay?: boolean autoPlay?: boolean
avoidBottomNav?: boolean
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
@@ -517,6 +522,11 @@ defineExpose({
padding-bottom: env(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom);
} }
.audio-player.avoid-bottom-nav {
bottom: calc(82px + env(safe-area-inset-bottom));
padding-bottom: 0;
}
@media (min-width: 768px) { @media (min-width: 768px) {
.audio-player { .audio-player {
left: 50%; left: 50%;

View File

@@ -120,7 +120,7 @@ const handleBack = () => {
.explain-scroll { .explain-scroll {
height: 100%; height: 100%;
padding: 76px 20px calc(28px + env(safe-area-inset-bottom)); padding: 76px 20px calc(112px + env(safe-area-inset-bottom));
box-sizing: border-box; box-sizing: border-box;
} }

View File

@@ -526,7 +526,7 @@ const handleBrowseAll = () => {
.explain-scroll { .explain-scroll {
height: 100%; height: 100%;
padding: 0 16px calc(28px + env(safe-area-inset-bottom)); padding: 0 16px calc(110px + env(safe-area-inset-bottom));
box-sizing: border-box; box-sizing: border-box;
} }
@@ -897,7 +897,7 @@ const handleBrowseAll = () => {
} }
.card-list { .card-list {
padding-bottom: calc(28px + env(safe-area-inset-bottom)); padding-bottom: calc(110px + env(safe-area-inset-bottom));
} }
.list-more { .list-more {

View File

@@ -0,0 +1,173 @@
<template>
<view class="guide-bottom-nav">
<view class="guide-bottom-nav-inner">
<view
v-for="tab in GUIDE_TOP_TABS"
:key="tab.id"
class="guide-bottom-tab"
:class="[`tab-${tab.id}`, { active: activeTab === tab.id }]"
@tap="handleTabTap(tab.id)"
>
<view class="guide-bottom-icon" aria-hidden="true">
<template v-if="tab.id === 'guide'">
<view class="guide-icon-pin"></view>
<view class="guide-icon-path"></view>
</template>
<template v-else>
<view class="explain-icon-wave top"></view>
<view class="explain-icon-wave middle"></view>
<view class="explain-icon-wave bottom"></view>
</template>
</view>
<text class="guide-bottom-label">{{ tab.label }}</text>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import {
GUIDE_TOP_TABS,
type GuideTopTab
} from '@/utils/guideTopTabs'
withDefaults(defineProps<{
activeTab?: GuideTopTab
}>(), {
activeTab: 'guide'
})
const emit = defineEmits<{
tabChange: [tab: GuideTopTab]
}>()
const handleTabTap = (tab: GuideTopTab) => {
emit('tabChange', tab)
}
</script>
<style scoped lang="scss">
.guide-bottom-nav {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 8px 14px calc(8px + env(safe-area-inset-bottom));
box-sizing: border-box;
background: rgba(255, 255, 255, 0.96);
border-top: 1px solid #e6e8df;
box-shadow: 0 -10px 24px rgba(36, 49, 42, 0.1);
z-index: 2200;
transform: translateZ(0);
}
.guide-bottom-nav-inner {
height: 56px;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
padding: 4px;
box-sizing: border-box;
background: #f4f5ef;
border: 1px solid #dfe2d9;
border-radius: 8px;
}
.guide-bottom-tab {
min-width: 0;
height: 46px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
box-sizing: border-box;
border-radius: 6px;
color: #515151;
transition: background-color 0.18s ease, color 0.18s ease, transform 0.18s ease;
}
.guide-bottom-tab.active {
background: #000000;
color: var(--museum-accent);
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.14);
}
.guide-bottom-tab:active {
transform: translateY(1px);
}
.guide-bottom-icon {
position: relative;
width: 22px;
height: 22px;
flex: 0 0 22px;
color: currentColor;
}
.guide-icon-pin {
position: absolute;
left: 4px;
top: 1px;
width: 12px;
height: 15px;
box-sizing: border-box;
border: 2px solid currentColor;
border-radius: 8px 8px 8px 0;
transform: rotate(-45deg);
}
.guide-icon-pin::after {
content: '';
position: absolute;
left: 3px;
top: 3px;
width: 3px;
height: 3px;
background: currentColor;
border-radius: 50%;
}
.guide-icon-path {
position: absolute;
left: 1px;
right: 1px;
bottom: 2px;
height: 6px;
border-bottom: 2px solid currentColor;
border-left: 2px solid currentColor;
border-radius: 0 0 0 8px;
}
.explain-icon-wave {
position: absolute;
left: 2px;
right: 2px;
height: 4px;
border: 2px solid currentColor;
border-left: 0;
border-right: 0;
border-bottom: 0;
border-radius: 999px 999px 0 0;
}
.explain-icon-wave.top {
top: 3px;
}
.explain-icon-wave.middle {
top: 9px;
left: 5px;
}
.explain-icon-wave.bottom {
top: 15px;
right: 6px;
}
.guide-bottom-label {
font-size: 15px;
line-height: 20px;
font-weight: 700;
color: currentColor;
}
</style>

View File

@@ -18,6 +18,7 @@
:target-focus-distance-factor="targetFocusDistanceFactor" :target-focus-distance-factor="targetFocusDistanceFactor"
:route-preview="routePreview" :route-preview="routePreview"
:show-route="showRoute" :show-route="showRoute"
:disable-auto-exit="disableAutoExit"
:auto-switch-threshold-low="autoSwitchThresholdLow" :auto-switch-threshold-low="autoSwitchThresholdLow"
:auto-switch-threshold-high="autoSwitchThresholdHigh" :auto-switch-threshold-high="autoSwitchThresholdHigh"
@floor-change="handleThreeFloorChange" @floor-change="handleThreeFloorChange"
@@ -29,7 +30,7 @@
<!-- #endif --> <!-- #endif -->
<!-- #ifndef H5 --> <!-- #ifndef H5 -->
<view class="indoor-platform-fallback"> <view class="indoor-platform-fallback">
<text class="indoor-platform-title">内三维导览</text> <text class="indoor-platform-title">内三维导览</text>
<text class="indoor-platform-desc">当前端暂不支持 WebGL 三维展示请在 H5 端查看</text> <text class="indoor-platform-desc">当前端暂不支持 WebGL 三维展示请在 H5 端查看</text>
</view> </view>
<!-- #endif --> <!-- #endif -->
@@ -81,14 +82,14 @@
:class="{ active: activeMode === '2d' }" :class="{ active: activeMode === '2d' }"
@tap="handleModeChange('2d')" @tap="handleModeChange('2d')"
> >
<text class="mode-label">外2D</text> <text class="mode-label">外2D</text>
</view> </view>
<view <view
class="mode-option" class="mode-option"
:class="{ active: activeMode === '3d' }" :class="{ active: activeMode === '3d' }"
@tap="handleModeChange('3d')" @tap="handleModeChange('3d')"
> >
<text class="mode-label">内3D</text> <text class="mode-label">内3D</text>
</view> </view>
</template> </template>
<template v-else> <template v-else>
@@ -98,14 +99,14 @@
:class="{ active: activeMode === '2d' }" :class="{ active: activeMode === '2d' }"
@tap="handleModeChange('2d')" @tap="handleModeChange('2d')"
> >
<text class="mode-label">外2D</text> <text class="mode-label">外2D</text>
</view> </view>
<view <view
class="mode-option" class="mode-option"
:class="{ active: activeMode === '3d' }" :class="{ active: activeMode === '3d' }"
@tap="handleModeChange('3d')" @tap="handleModeChange('3d')"
> >
<text class="mode-label">内3D</text> <text class="mode-label">内3D</text>
</view> </view>
</view> </view>
<view class="mode-status-chip" :class="`tone-${modeStatusTone}`"> <view class="mode-status-chip" :class="`tone-${modeStatusTone}`">
@@ -316,6 +317,7 @@ const props = withDefaults(defineProps<{
targetFocusDistanceFactor?: number targetFocusDistanceFactor?: number
routePreview?: GuideRouteResult | null routePreview?: GuideRouteResult | null
showRoute?: boolean showRoute?: boolean
disableAutoExit?: boolean
autoSwitchThresholdLow?: number autoSwitchThresholdLow?: number
autoSwitchThresholdHigh?: number autoSwitchThresholdHigh?: number
outdoorNavPolylines?: OutdoorNavPolyline[] outdoorNavPolylines?: OutdoorNavPolyline[]
@@ -363,6 +365,7 @@ const props = withDefaults(defineProps<{
targetFocusDistanceFactor: 0.22, targetFocusDistanceFactor: 0.22,
routePreview: null, routePreview: null,
showRoute: false, showRoute: false,
disableAutoExit: false,
autoSwitchThresholdLow: 1.0, autoSwitchThresholdLow: 1.0,
autoSwitchThresholdHigh: 1.3, autoSwitchThresholdHigh: 1.3,
outdoorNavPolylines: () => [] as OutdoorNavPolyline[] outdoorNavPolylines: () => [] as OutdoorNavPolyline[]
@@ -590,8 +593,7 @@ defineExpose({
position: relative; position: relative;
z-index: 0; z-index: 0;
width: 100%; width: 100%;
height: 100vh; height: 100%;
height: 100dvh;
overflow: hidden; overflow: hidden;
background: #ffffff; background: #ffffff;
color: #000000; color: #000000;

View File

@@ -1,7 +1,7 @@
<template> <template>
<view <view
class="guide-page-frame" class="guide-page-frame"
:class="[`variant-${variant}`, { 'no-top-tabs': !showTopTabs }]" :class="[`variant-${variant}`, { 'no-top-tabs': !showTopTabs, 'has-bottom-nav': showBottomNav }]"
> >
<GuideTopTabs <GuideTopTabs
v-if="showTopTabs" v-if="showTopTabs"
@@ -32,10 +32,16 @@
<view class="guide-page-frame-body"> <view class="guide-page-frame-body">
<slot></slot> <slot></slot>
</view> </view>
<GuideBottomNav
v-if="showBottomNav"
:active-tab="activeTab"
@tab-change="handleTabChange"
/>
</view> </view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import GuideBottomNav from '@/components/navigation/GuideBottomNav.vue'
import GuideTopTabs from '@/components/navigation/GuideTopTabs.vue' import GuideTopTabs from '@/components/navigation/GuideTopTabs.vue'
import type { GuideTopTab } from '@/utils/guideTopTabs' import type { GuideTopTab } from '@/utils/guideTopTabs'
@@ -45,6 +51,7 @@ withDefaults(defineProps<{
topTabsVariant?: 'underline' | 'segmented' topTabsVariant?: 'underline' | 'segmented'
topTabsTop?: string topTabsTop?: string
showTopTabs?: boolean showTopTabs?: boolean
showBottomNav?: boolean
showBack?: boolean showBack?: boolean
showCancel?: boolean showCancel?: boolean
backLabel?: string backLabel?: string
@@ -55,6 +62,7 @@ withDefaults(defineProps<{
topTabsVariant: 'underline', topTabsVariant: 'underline',
topTabsTop: '0', topTabsTop: '0',
showTopTabs: true, showTopTabs: true,
showBottomNav: false,
showBack: false, showBack: false,
showCancel: false, showCancel: false,
backLabel: '返回', backLabel: '返回',

View File

@@ -14,7 +14,7 @@
<view v-if="isCollapsed" class="panel-collapsed" @tap="expandPanel"> <view v-if="isCollapsed" class="panel-collapsed" @tap="expandPanel">
<view class="panel-collapsed-copy"> <view class="panel-collapsed-copy">
<text class="panel-title">外导览</text> <text class="panel-title">外导览</text>
<text class="panel-summary">{{ collapsedSummary }}</text> <text class="panel-summary">{{ collapsedSummary }}</text>
</view> </view>
<view class="panel-expand"> <view class="panel-expand">
@@ -25,7 +25,7 @@
<template v-else> <template v-else>
<view class="panel-header"> <view class="panel-header">
<view class="panel-title-group"> <view class="panel-title-group">
<text class="panel-title">外导览</text> <text class="panel-title">外导览</text>
<text v-if="routeSummary" class="panel-summary">{{ routeSummary }}</text> <text v-if="routeSummary" class="panel-summary">{{ routeSummary }}</text>
</view> </view>
<view class="panel-actions"> <view class="panel-actions">
@@ -244,7 +244,7 @@ const collapsedSummary = computed(() => {
if (effectiveStartMode.value === 'gps') { if (effectiveStartMode.value === 'gps') {
return '使用 GPS 定位' return '使用 GPS 定位'
} }
return '外导览到馆' return '外导览到馆'
}) })
const formatStartPointName = () => { const formatStartPointName = () => {
@@ -411,7 +411,7 @@ const handlePanelMouseEnd = (event: MouseEvent) => {
position: absolute; position: absolute;
left: 15px; left: 15px;
right: 15px; right: 15px;
bottom: calc(env(safe-area-inset-bottom) + 24px); bottom: calc(env(safe-area-inset-bottom) + 106px);
padding: 12px 15px 15px; padding: 12px 15px 15px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -14,7 +14,7 @@
<view v-if="isCollapsed" class="panel-collapsed" @tap="expandPanel"> <view v-if="isCollapsed" class="panel-collapsed" @tap="expandPanel">
<view class="panel-collapsed-copy"> <view class="panel-collapsed-copy">
<text class="panel-title">内导览</text> <text class="panel-title">内导览</text>
<text class="panel-summary">{{ collapsedSummary }}</text> <text class="panel-summary">{{ collapsedSummary }}</text>
</view> </view>
<view class="panel-expand"> <view class="panel-expand">
@@ -25,7 +25,7 @@
<template v-else> <template v-else>
<view class="panel-header"> <view class="panel-header">
<view class="panel-title-group"> <view class="panel-title-group">
<text class="panel-title">内导览</text> <text class="panel-title">内导览</text>
<text v-if="summary" class="panel-summary">{{ summary }}</text> <text v-if="summary" class="panel-summary">{{ summary }}</text>
</view> </view>
<view class="panel-actions"> <view class="panel-actions">
@@ -201,10 +201,10 @@ const primaryActionText = computed(() => (
: (props.routeReady ? '查看路线' : '查看位置关系') : (props.routeReady ? '查看路线' : '查看位置关系')
)) ))
const routeOptionsTitle = computed(() => '内导览') const routeOptionsTitle = computed(() => '内导览')
const loadingText = computed(() => ( const loadingText = computed(() => (
props.routeReady ? '正在规划内导览路线' : '正在生成内位置关系' props.routeReady ? '正在规划内导览路线' : '正在生成内位置关系'
)) ))
const collapsedSummary = computed(() => { const collapsedSummary = computed(() => {
@@ -403,7 +403,7 @@ const handlePrimaryAction = () => {
position: absolute; position: absolute;
left: 12px; left: 12px;
right: 12px; right: 12px;
bottom: calc(env(safe-area-inset-bottom) + 30px); bottom: calc(env(safe-area-inset-bottom) + 112px);
padding: 10px 14px 14px; padding: 10px 14px 14px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -2,6 +2,8 @@
<GuidePageFrame <GuidePageFrame
active-tab="explain" active-tab="explain"
variant="static" variant="static"
:show-top-tabs="false"
show-bottom-nav
show-back show-back
back-label="讲解首页" back-label="讲解首页"
@tab-change="handleTopTabChange" @tab-change="handleTopTabChange"
@@ -27,6 +29,7 @@
:visible="showAudioPlayer" :visible="showAudioPlayer"
:audio="currentAudio" :audio="currentAudio"
:auto-play="true" :auto-play="true"
avoid-bottom-nav
@update:visible="handleAudioVisibleChange" @update:visible="handleAudioVisibleChange"
@play="handleAudioPlay" @play="handleAudioPlay"
@pause="handleAudioPause" @pause="handleAudioPause"

View File

@@ -2,6 +2,8 @@
<GuidePageFrame <GuidePageFrame
active-tab="guide" active-tab="guide"
variant="overlay" variant="overlay"
:show-top-tabs="false"
show-bottom-nav
show-back show-back
:show-cancel="isStartPanelOpen" :show-cancel="isStartPanelOpen"
@tab-change="handleTopTabChange" @tab-change="handleTopTabChange"
@@ -283,7 +285,7 @@ const handleStartNavigation = () => {
const handleModeChange = (mode: '2d' | '3d') => { const handleModeChange = (mode: '2d' | '3d') => {
activeMode.value = mode activeMode.value = mode
uni.showToast({ uni.showToast({
title: mode === '2d' ? '已切换外入口参考' : '已切换内三维位置', title: mode === '2d' ? '已切换外入口参考' : '已切换内三维位置',
icon: 'none' icon: 'none'
}) })
} }
@@ -335,7 +337,7 @@ const handlePageBack = () => {
position: absolute; position: absolute;
left: 0; left: 0;
right: 0; right: 0;
bottom: calc(env(safe-area-inset-bottom) + 34px); bottom: calc(env(safe-area-inset-bottom) + 116px);
height: 252px; height: 252px;
padding: 22px 20px 20px; padding: 22px 20px 20px;
box-sizing: border-box; box-sizing: border-box;
@@ -478,7 +480,7 @@ const handlePageBack = () => {
position: absolute; position: absolute;
left: 0; left: 0;
right: 0; right: 0;
bottom: calc(env(safe-area-inset-bottom) + 34px); bottom: calc(env(safe-area-inset-bottom) + 116px);
height: 276px; height: 276px;
padding: 22px 20px 16px; padding: 22px 20px 16px;
box-sizing: border-box; box-sizing: border-box;

View File

@@ -2,9 +2,8 @@
<GuidePageFrame <GuidePageFrame
:active-tab="currentTab" :active-tab="currentTab"
variant="overlay" variant="overlay"
:top-tabs-variant="currentTab === 'guide' ? 'segmented' : 'underline'" :show-top-tabs="false"
:top-tabs-top="currentTab === 'guide' ? '80px' : '0'" show-bottom-nav
:show-top-tabs="currentTab === 'guide'"
@tab-change="handleTabChange" @tab-change="handleTabChange"
> >
<GuideMapShell <GuideMapShell
@@ -38,11 +37,12 @@
:show-floor="is3DMode" :show-floor="is3DMode"
:tools="indoorCameraTools" :tools="indoorCameraTools"
tools-top="" tools-top=""
tools-bottom="54px" tools-bottom="138px"
show-zoom-controls show-zoom-controls
zoom-controls-top="calc(100vh - 284px)" zoom-controls-top="calc(100vh - 284px)"
:route-preview="activeRoutePreview" :route-preview="activeRoutePreview"
:show-route="Boolean(activeRoutePreview)" :show-route="Boolean(activeRoutePreview)"
:disable-auto-exit="disableIndoorAutoExit"
:auto-switch-threshold-low="0.58" :auto-switch-threshold-low="0.58"
:auto-switch-threshold-high="1.18" :auto-switch-threshold-high="1.18"
:outdoor-nav-polylines="outdoorNavPolylines" :outdoor-nav-polylines="outdoorNavPolylines"
@@ -171,14 +171,14 @@
<view v-if="guideOutdoorState === 'entrance' && !is3DMode" class="entrance-bottom-card"> <view v-if="guideOutdoorState === 'entrance' && !is3DMode" class="entrance-bottom-card">
<text class="entrance-card-title">主入口</text> <text class="entrance-card-title">主入口</text>
<text class="entrance-card-desc"> <text class="entrance-card-desc">
可参考主入口位置也可手动进入内3D并选择楼层/区域 可参考主入口位置也可手动进入内3D并选择楼层/区域
</text> </text>
<view class="entrance-actions"> <view class="entrance-actions">
<view class="entrance-btn secondary" @tap="handleSwitchEntrance"> <view class="entrance-btn secondary" @tap="handleSwitchEntrance">
<text class="entrance-btn-text">切换入口</text> <text class="entrance-btn-text">切换入口</text>
</view> </view>
<view class="entrance-btn primary" @tap="handleEnter3DMode"> <view class="entrance-btn primary" @tap="handleEnter3DMode">
<text class="entrance-btn-text">进入内3D</text> <text class="entrance-btn-text">进入内3D</text>
</view> </view>
</view> </view>
</view> </view>
@@ -320,7 +320,7 @@ const guideMapShellRef = ref<{
clearRoute?: () => void clearRoute?: () => void
} | null>(null) } | null>(null)
// 外导航相关状态 // 外导航相关状态
const showOutdoorNavPanel = ref(false) const showOutdoorNavPanel = ref(false)
const outdoorNavLoading = ref(false) const outdoorNavLoading = ref(false)
const outdoorNavError = ref('') const outdoorNavError = ref('')
@@ -373,6 +373,13 @@ const routePlanSummary = computed(() => {
const routePanelError = computed(() => routePlanError.value) const routePanelError = computed(() => routePlanError.value)
const disableIndoorAutoExit = computed(() => (
Boolean(selectedGuidePoi.value)
|| Boolean(activeRoutePreview.value)
|| showRoutePlanner.value
|| isSimulatingRoute.value
))
const showGuideQuickActions = computed(() => ( const showGuideQuickActions = computed(() => (
currentTab.value === 'guide' currentTab.value === 'guide'
&& !showRoutePlanner.value && !showRoutePlanner.value
@@ -398,12 +405,12 @@ const selectedGuidePoiSubtitle = computed(() => {
}) })
const routeSimulationStepText = computed(() => { const routeSimulationStepText = computed(() => {
if (!activeRoutePreview.value) return routeReady.value ? '内导览 · 查看路线' : '内导览 · 查看位置' if (!activeRoutePreview.value) return routeReady.value ? '内导览 · 查看路线' : '内导览 · 查看位置'
const hasConnector = activeRoutePreview.value.connectorPoints.length > 0 const hasConnector = activeRoutePreview.value.connectorPoints.length > 0
if (routeReady.value) { if (routeReady.value) {
return hasConnector ? '内导览 · 跨楼层路线' : '内导览 · 同层路线' return hasConnector ? '内导览 · 跨楼层路线' : '内导览 · 同层路线'
} }
return hasConnector ? '内导览 · 跨楼层位置关系' : '内导览 · 同层位置关系' return hasConnector ? '内导览 · 跨楼层位置关系' : '内导览 · 同层位置关系'
}) })
const explainHallItems = ref<ExplainHallSelectItem[]>([]) const explainHallItems = ref<ExplainHallSelectItem[]>([])
@@ -868,7 +875,7 @@ const handleRouteView = async ({ startPoint, endPoint }: { startPoint: RoutePoin
if (!result.route) { if (!result.route) {
activeRoutePreview.value = null activeRoutePreview.value = null
routePlanError.value = result.error || '内导览位置关系生成失败' routePlanError.value = result.error || '内导览位置关系生成失败'
return return
} }
@@ -876,9 +883,9 @@ const handleRouteView = async ({ startPoint, endPoint }: { startPoint: RoutePoin
focusRouteFloor(result.route) focusRouteFloor(result.route)
showRoutePlanner.value = true showRoutePlanner.value = true
} catch (error) { } catch (error) {
console.error('内导览位置关系生成失败:', error) console.error('内导览位置关系生成失败:', error)
activeRoutePreview.value = null activeRoutePreview.value = null
routePlanError.value = error instanceof Error ? error.message : '内导览位置关系生成失败' routePlanError.value = error instanceof Error ? error.message : '内导览位置关系生成失败'
} finally { } finally {
routePlanning.value = false routePlanning.value = false
} }
@@ -892,9 +899,9 @@ const handleRouteSimulate = () => {
isSimulatingRoute.value = true isSimulatingRoute.value = true
} }
// 进入 3D 内模式 // 进入 3D 内模式
const handleEnter3DMode = () => { const handleEnter3DMode = () => {
console.log('进入 3D 内模式') console.log('进入 3D 内模式')
indoorView.value = 'overview' indoorView.value = 'overview'
is3DMode.value = true is3DMode.value = true
guideOutdoorState.value = 'home' guideOutdoorState.value = 'home'
@@ -927,15 +934,15 @@ const handleModeChange = (mode: '2d' | '3d') => {
const guideStatusLabel = computed(() => { const guideStatusLabel = computed(() => {
if (is3DMode.value) { if (is3DMode.value) {
if (indoorView.value === 'overview') return '建筑外观' if (indoorView.value === 'overview') return '建筑外观'
if (indoorView.value === 'multi') return '内多层' if (indoorView.value === 'multi') return '内多层'
return '内单层' return '内单层'
} }
if (guideOutdoorState.value === 'entrance') { if (guideOutdoorState.value === 'entrance') {
return '推荐入口' return '推荐入口'
} }
return '外参考' return '外参考'
}) })
const handleSwitchEntrance = () => { const handleSwitchEntrance = () => {
@@ -946,13 +953,13 @@ const handleMoreRouteGuide = () => {
openRoutePlanner() openRoutePlanner()
} }
// 外导航处理函数 // 外导航处理函数
const openOutdoorNavPanel = () => { const openOutdoorNavPanel = () => {
showOutdoorNavPanel.value = true showOutdoorNavPanel.value = true
showRoutePlanner.value = false showRoutePlanner.value = false
selectedGuidePoi.value = null selectedGuidePoi.value = null
isPoiCardCollapsed.value = false isPoiCardCollapsed.value = false
// 切换到外 2D 地图 // 切换到外 2D 地图
is3DMode.value = false is3DMode.value = false
clearOutdoorNavState() clearOutdoorNavState()
} }
@@ -1163,7 +1170,7 @@ const handleExplainBack = () => {
position: absolute; position: absolute;
left: 12px; left: 12px;
right: 12px; right: 12px;
bottom: calc(env(safe-area-inset-bottom) + 30px); bottom: calc(env(safe-area-inset-bottom) + 112px);
box-sizing: border-box; box-sizing: border-box;
background: rgba(255, 255, 255, 0.96); background: rgba(255, 255, 255, 0.96);
border: 1px solid #e5e6de; border: 1px solid #e5e6de;
@@ -1325,7 +1332,7 @@ const handleExplainBack = () => {
.guide-quick-actions { .guide-quick-actions {
position: absolute; position: absolute;
right: 18px; right: 18px;
bottom: calc(env(safe-area-inset-bottom) + 54px); bottom: calc(env(safe-area-inset-bottom) + 136px);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: stretch;
@@ -1454,7 +1461,7 @@ const handleExplainBack = () => {
position: absolute; position: absolute;
left: 12px; left: 12px;
right: 12px; right: 12px;
bottom: calc(env(safe-area-inset-bottom) + 30px); bottom: calc(env(safe-area-inset-bottom) + 112px);
min-height: 54px; min-height: 54px;
padding: 10px 10px 10px 14px; padding: 10px 10px 10px 14px;
display: flex; display: flex;
@@ -1556,7 +1563,7 @@ const handleExplainBack = () => {
position: absolute; position: absolute;
left: 12px; left: 12px;
right: 12px; right: 12px;
bottom: calc(env(safe-area-inset-bottom) + 134px); bottom: calc(env(safe-area-inset-bottom) + 216px);
height: 84px; height: 84px;
padding: 16px 10px 8px; padding: 16px 10px 8px;
display: flex; display: flex;
@@ -1694,7 +1701,7 @@ const handleExplainBack = () => {
position: absolute; position: absolute;
left: 12px; left: 12px;
right: 12px; right: 12px;
bottom: calc(env(safe-area-inset-bottom) + 134px); bottom: calc(env(safe-area-inset-bottom) + 216px);
z-index: 1003; z-index: 1003;
} }
@@ -1702,7 +1709,7 @@ const handleExplainBack = () => {
position: absolute; position: absolute;
left: 12px; left: 12px;
right: 12px; right: 12px;
bottom: calc(env(safe-area-inset-bottom) + 30px); bottom: calc(env(safe-area-inset-bottom) + 112px);
height: 94px; height: 94px;
padding: 16px 16px 10px; padding: 16px 16px 10px;
display: flex; display: flex;
@@ -1758,7 +1765,7 @@ const handleExplainBack = () => {
position: absolute; position: absolute;
left: 12px; left: 12px;
right: 12px; right: 12px;
bottom: calc(env(safe-area-inset-bottom) + 34px); bottom: calc(env(safe-area-inset-bottom) + 116px);
height: 158px; height: 158px;
padding: 18px 16px 14px; padding: 18px 16px 14px;
box-sizing: border-box; box-sizing: border-box;

View File

@@ -2,6 +2,8 @@
<GuidePageFrame <GuidePageFrame
active-tab="guide" active-tab="guide"
variant="overlay" variant="overlay"
:show-top-tabs="false"
show-bottom-nav
show-back show-back
:show-cancel="isManualLocationPanelOpen" :show-cancel="isManualLocationPanelOpen"
@tab-change="handleTopTabChange" @tab-change="handleTopTabChange"
@@ -97,16 +99,16 @@
</view> </view>
<view v-else-if="routeViewState === 'outdoor-preview'" class="outdoor-preview-card"> <view v-else-if="routeViewState === 'outdoor-preview'" class="outdoor-preview-card">
<text class="outdoor-preview-title">外地图预览{{ route.target }}</text> <text class="outdoor-preview-title">外地图预览{{ route.target }}</text>
<text class="outdoor-preview-desc"> <text class="outdoor-preview-desc">
外地图仅作入口参考馆内当前支持三维位置预览 外地图仅作入口参考馆内当前支持三维位置预览
</text> </text>
<view class="outdoor-preview-actions"> <view class="outdoor-preview-actions">
<view class="outdoor-preview-btn secondary" @tap="handleReturnToPreview"> <view class="outdoor-preview-btn secondary" @tap="handleReturnToPreview">
<text class="outdoor-preview-btn-text">返回预览</text> <text class="outdoor-preview-btn-text">返回预览</text>
</view> </view>
<view class="outdoor-preview-btn primary" @tap="handleReturnToPreview"> <view class="outdoor-preview-btn primary" @tap="handleReturnToPreview">
<text class="outdoor-preview-btn-text">返回内3D</text> <text class="outdoor-preview-btn-text">返回内3D</text>
</view> </view>
</view> </view>
</view> </view>
@@ -283,7 +285,7 @@ const shellConfig = computed(() => {
if (routeViewState.value === 'outdoor-preview') { if (routeViewState.value === 'outdoor-preview') {
return { return {
searchText: `外地图参考:${route.value.target}`, searchText: `外地图参考:${route.value.target}`,
searchTop: '60px', searchTop: '60px',
activeMode: '2d' as GuideMode, activeMode: '2d' as GuideMode,
activeFloor: activeFloor.value, activeFloor: activeFloor.value,
@@ -295,14 +297,14 @@ const shellConfig = computed(() => {
showModeRow: false, showModeRow: false,
modeTop: '104px', modeTop: '104px',
modeLayout: 'status' as const, modeLayout: 'status' as const,
modeStatus: '外参考', modeStatus: '外参考',
mapType: 'outdoor' as const, mapType: 'outdoor' as const,
outdoorVariant: 'entrance' as const outdoorVariant: 'entrance' as const
} }
} }
return { return {
searchText: routeReady.value ? `内导览:${route.value.target}` : `位置预览:${route.value.target}`, searchText: routeReady.value ? `内导览:${route.value.target}` : `位置预览:${route.value.target}`,
searchTop: '60px', searchTop: '60px',
activeMode: '3d' as GuideMode, activeMode: '3d' as GuideMode,
activeFloor: activeFloor.value, activeFloor: activeFloor.value,
@@ -321,7 +323,7 @@ const shellConfig = computed(() => {
}) })
const indoorGuideTitle = computed(() => ( const indoorGuideTitle = computed(() => (
routeReady.value ? `内导览:${route.value.target}` : `位置预览:${route.value.target}` routeReady.value ? `内导览:${route.value.target}` : `位置预览:${route.value.target}`
)) ))
const indoorGuideDesc = computed(() => { const indoorGuideDesc = computed(() => {
@@ -643,7 +645,7 @@ const handleStartIndoorGuide = async () => {
}) })
if (!result.route) { if (!result.route) {
routePlanError.value = result.error || '内导览路线规划失败' routePlanError.value = result.error || '内导览路线规划失败'
activeRoutePreview.value = null activeRoutePreview.value = null
return return
} }
@@ -651,7 +653,7 @@ const handleStartIndoorGuide = async () => {
activeRoutePreview.value = result.route activeRoutePreview.value = result.route
activeFloor.value = result.route.start.floorLabel activeFloor.value = result.route.start.floorLabel
} catch (error) { } catch (error) {
routePlanError.value = error instanceof Error ? error.message : '内导览路线规划失败' routePlanError.value = error instanceof Error ? error.message : '内导览路线规划失败'
activeRoutePreview.value = null activeRoutePreview.value = null
} finally { } finally {
routePlanning.value = false routePlanning.value = false
@@ -719,7 +721,7 @@ const handlePageBack = () => {
position: absolute; position: absolute;
left: 16px; left: 16px;
right: 16px; right: 16px;
bottom: calc(env(safe-area-inset-bottom) + 34px); bottom: calc(env(safe-area-inset-bottom) + 116px);
min-height: 170px; min-height: 170px;
padding: 22px 20px 20px; padding: 22px 20px 20px;
display: flex; display: flex;
@@ -736,7 +738,7 @@ const handlePageBack = () => {
position: absolute; position: absolute;
left: 16px; left: 16px;
right: 16px; right: 16px;
bottom: calc(env(safe-area-inset-bottom) + 34px); bottom: calc(env(safe-area-inset-bottom) + 116px);
min-height: 156px; min-height: 156px;
padding: 18px 18px 16px; padding: 18px 18px 16px;
display: flex; display: flex;
@@ -958,7 +960,7 @@ const handlePageBack = () => {
position: absolute; position: absolute;
left: 0; left: 0;
right: 0; right: 0;
bottom: calc(env(safe-area-inset-bottom) + 34px); bottom: calc(env(safe-area-inset-bottom) + 116px);
height: 196px; height: 196px;
padding: 21px 20px 14px; padding: 21px 20px 14px;
box-sizing: border-box; box-sizing: border-box;
@@ -1098,7 +1100,7 @@ const handlePageBack = () => {
position: absolute; position: absolute;
left: 12px; left: 12px;
right: 12px; right: 12px;
bottom: calc(env(safe-area-inset-bottom) + 34px); bottom: calc(env(safe-area-inset-bottom) + 116px);
height: 158px; height: 158px;
padding: 18px 16px 14px; padding: 18px 16px 14px;
box-sizing: border-box; box-sizing: border-box;