Update guide presentation and route preview gating
This commit is contained in:
@@ -15,7 +15,6 @@
|
||||
mode-status-tone="glass"
|
||||
search-top="46px"
|
||||
search-variant="home"
|
||||
show-voice-icon
|
||||
:show-mode-row="false"
|
||||
mode-top="104px"
|
||||
:map-type="guideMapType"
|
||||
@@ -34,6 +33,7 @@
|
||||
show-floor-header
|
||||
:show-layer-mode-toggle="false"
|
||||
:show-floor="is3DMode"
|
||||
tools-top="154px"
|
||||
show-zoom-controls
|
||||
zoom-controls-top="560px"
|
||||
show-location-control
|
||||
@@ -62,43 +62,68 @@
|
||||
<view
|
||||
v-if="selectedGuidePoi && !showRoutePlanner && !isSimulatingRoute"
|
||||
class="guide-poi-card"
|
||||
:class="{ collapsed: isPoiCardCollapsed }"
|
||||
>
|
||||
<view class="poi-card-handle"></view>
|
||||
<view class="poi-card-header">
|
||||
<view class="poi-card-handle" @tap.stop="togglePoiCardCollapsed"></view>
|
||||
<view v-if="isPoiCardCollapsed" class="poi-card-collapsed" @tap="expandPoiCard">
|
||||
<view class="poi-card-copy">
|
||||
<text class="poi-card-title">{{ selectedGuidePoi.name }}</text>
|
||||
<text class="poi-card-subtitle">{{ selectedGuidePoiSubtitle }}</text>
|
||||
<text class="poi-card-subtitle">位置预览 · 点按展开操作</text>
|
||||
</view>
|
||||
<view class="poi-card-close" @tap="handleGuideSelectionClear">
|
||||
<text class="poi-card-close-text">×</text>
|
||||
<view class="poi-card-mini-action">
|
||||
<text class="poi-card-mini-text">展开</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="poi-card-actions">
|
||||
<view class="poi-action primary" @tap="handlePreviewSelectedPoi">
|
||||
<text class="poi-action-text">查看位置</text>
|
||||
<template v-else>
|
||||
<view class="poi-card-header">
|
||||
<view class="poi-card-copy">
|
||||
<text class="poi-card-title">{{ selectedGuidePoi.name }}</text>
|
||||
<text class="poi-card-subtitle">{{ selectedGuidePoiSubtitle }}</text>
|
||||
</view>
|
||||
<view class="poi-card-header-actions">
|
||||
<view class="poi-card-soft-action" @tap="collapsePoiCard">
|
||||
<text class="poi-card-soft-text">收起</text>
|
||||
</view>
|
||||
<view class="poi-card-close" @tap="handleGuideSelectionClear">
|
||||
<text class="poi-card-close-text">×</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="poi-action" @tap="handleSetSelectedPoiAsStart">
|
||||
<text class="poi-action-text">从这里出发</text>
|
||||
<view class="poi-card-actions">
|
||||
<view class="poi-action primary" @tap="handlePreviewSelectedPoi">
|
||||
<text class="poi-action-text">查看位置</text>
|
||||
</view>
|
||||
<view class="poi-action" @tap="handleSetSelectedPoiAsStart">
|
||||
<text class="poi-action-text">从这里出发</text>
|
||||
</view>
|
||||
<view class="poi-action" @tap="handleSetSelectedPoiAsEnd">
|
||||
<text class="poi-action-text">去这里</text>
|
||||
</view>
|
||||
<view class="poi-action" @tap="handleSelectedPoiRelated">
|
||||
<text class="poi-action-text">相关讲解</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="poi-action" @tap="handleSetSelectedPoiAsEnd">
|
||||
<text class="poi-action-text">去这里</text>
|
||||
</view>
|
||||
<view class="poi-action" @tap="handleSelectedPoiRelated">
|
||||
<text class="poi-action-text">相关讲解</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<view v-if="showGuideMoreMenu && !showRoutePlanner" class="guide-more-popover">
|
||||
<view class="more-popover-handle"></view>
|
||||
<view class="more-popover-grid">
|
||||
<view class="more-action" @tap="handleMoreRouteGuide">
|
||||
<text class="more-action-title">线路导览</text>
|
||||
<text class="more-action-title">线路预览</text>
|
||||
<text class="more-action-desc">室内到室内</text>
|
||||
</view>
|
||||
<view class="more-action" @tap="handleMoreOutdoorReference">
|
||||
<text class="more-action-title">室外参考</text>
|
||||
<text class="more-action-desc">入口位置</text>
|
||||
</view>
|
||||
<view class="more-action" @tap="handleMoreRecommendedEntrance">
|
||||
<text class="more-action-title">推荐入口</text>
|
||||
<text class="more-action-desc">入口参考</text>
|
||||
</view>
|
||||
<view class="more-action" @tap="handleEnter3DMode">
|
||||
<text class="more-action-title">查看位置</text>
|
||||
<text class="more-action-desc">三维预览</text>
|
||||
<text class="more-action-title">室内3D</text>
|
||||
<text class="more-action-desc">位置预览</text>
|
||||
</view>
|
||||
<view class="more-action" @tap="handleMoreToggleLayer">
|
||||
<text class="more-action-title">{{ indoorLayerMode === 'multi' ? '单层' : '多层' }}</text>
|
||||
@@ -115,7 +140,7 @@
|
||||
:point-options="routePointOptions"
|
||||
:has-route-preview="Boolean(activeRoutePreview)"
|
||||
:loading="routePlanning"
|
||||
:error="routePlanError"
|
||||
:error="routePanelError"
|
||||
:summary="routePlanSummary"
|
||||
:picker-loading="routeTargetsLoading"
|
||||
:picker-error="routeTargetsError"
|
||||
@@ -138,7 +163,7 @@
|
||||
<view v-if="isSimulatingRoute && activeRoutePreview" class="route-sim-step-bar">
|
||||
<text class="route-sim-step-text">{{ routeSimulationStepText }}</text>
|
||||
<view class="route-sim-expand" @tap="showRoutePlanner = true">
|
||||
<text class="route-sim-expand-text">展开步骤</text>
|
||||
<text class="route-sim-expand-text">展开预览</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -275,9 +300,11 @@ const routeTargetsLoading = ref(false)
|
||||
const routeTargetsError = ref('')
|
||||
const routePlanning = ref(false)
|
||||
const routePlanError = ref('')
|
||||
const routePreviewUnavailableMessage = ref('')
|
||||
const activeRoutePreview = ref<GuideRouteResult | null>(null)
|
||||
const showGuideMoreMenu = ref(false)
|
||||
const isSimulatingRoute = ref(false)
|
||||
const isPoiCardCollapsed = ref(false)
|
||||
|
||||
const indoorNavAssetBaseUrl = guideUseCase.getAssetBaseUrl()
|
||||
const indoorModelSource = guideUseCase.getModelSource()
|
||||
@@ -287,7 +314,7 @@ const indoorLayerMode = computed<LayerDisplayMode>(() => (
|
||||
indoorView.value === 'multi' ? 'multi' : 'single'
|
||||
))
|
||||
const guide3DTools = computed(() => {
|
||||
return []
|
||||
return is3DMode.value ? ['重置', '俯视', '斜视', '清除', '清路线'] : []
|
||||
})
|
||||
|
||||
const routePlanSummary = computed(() => {
|
||||
@@ -304,6 +331,8 @@ const routePlanSummary = computed(() => {
|
||||
return ''
|
||||
})
|
||||
|
||||
const routePanelError = computed(() => routePlanError.value || routePreviewUnavailableMessage.value)
|
||||
|
||||
const toRoutePointOption = (target: GuideRouteTarget): RoutePointOption => ({
|
||||
poiId: target.poiId,
|
||||
name: target.name,
|
||||
@@ -321,9 +350,9 @@ const selectedGuidePoiSubtitle = computed(() => {
|
||||
})
|
||||
|
||||
const routeSimulationStepText = computed(() => {
|
||||
if (!activeRoutePreview.value) return '下一步 · 查看线路'
|
||||
if (!activeRoutePreview.value) return '路线示意 · 查看预览'
|
||||
const hasConnector = activeRoutePreview.value.connectorPoints.length > 0
|
||||
return hasConnector ? '下一步 · 电梯上行 3/8' : '下一步 · 沿路线前行 1/4'
|
||||
return hasConnector ? '路线示意 · 跨楼层连接点' : '路线示意 · 同层位置关系'
|
||||
})
|
||||
|
||||
// 音频播放器状态
|
||||
@@ -340,12 +369,20 @@ const syncTopTabToUrl = (tabId: GuideTopTab) => {
|
||||
if (typeof window === 'undefined') return
|
||||
if (!isIndexHashRoute()) return
|
||||
|
||||
const url = `#/?tab=${tabId}`
|
||||
const url = `#/pages/index/index?tab=${tabId}`
|
||||
if (window.location.hash !== url) {
|
||||
window.history.replaceState(null, '', url)
|
||||
}
|
||||
}
|
||||
|
||||
const loadTopTabData = (tabId: GuideTopTab) => {
|
||||
if (tabId === 'explain') {
|
||||
void loadExplainExhibits()
|
||||
} else {
|
||||
void loadGuideFloors()
|
||||
}
|
||||
}
|
||||
|
||||
const handleTabChange = (tabId: GuideTopTab) => {
|
||||
if (tabId === 'guide' && currentTab.value === 'explain') {
|
||||
const lastLocationPreviewUrl = getLastGuideLocationPreviewUrl()
|
||||
@@ -359,12 +396,7 @@ const handleTabChange = (tabId: GuideTopTab) => {
|
||||
|
||||
currentTab.value = tabId
|
||||
syncTopTabToUrl(tabId)
|
||||
|
||||
if (tabId === 'explain') {
|
||||
void loadExplainExhibits()
|
||||
} else {
|
||||
void loadGuideFloors()
|
||||
}
|
||||
loadTopTabData(tabId)
|
||||
|
||||
console.log('切换标签:', tabId)
|
||||
|
||||
@@ -372,6 +404,7 @@ const handleTabChange = (tabId: GuideTopTab) => {
|
||||
showGuideMoreMenu.value = false
|
||||
showRoutePlanner.value = false
|
||||
isSimulatingRoute.value = false
|
||||
isPoiCardCollapsed.value = false
|
||||
}
|
||||
|
||||
const syncTopTabFromHash = () => {
|
||||
@@ -379,16 +412,27 @@ const syncTopTabFromHash = () => {
|
||||
|
||||
const tab = topTabFromHash()
|
||||
if (tab) {
|
||||
const tabChanged = currentTab.value !== tab
|
||||
currentTab.value = tab
|
||||
loadTopTabData(tab)
|
||||
if (tabChanged) {
|
||||
showGuideMoreMenu.value = false
|
||||
showRoutePlanner.value = false
|
||||
isSimulatingRoute.value = false
|
||||
isPoiCardCollapsed.value = false
|
||||
}
|
||||
syncTopTabToUrl(tab)
|
||||
return
|
||||
}
|
||||
|
||||
syncTopTabToUrl(currentTab.value)
|
||||
loadTopTabData(currentTab.value)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
syncTopTabFromHash()
|
||||
window.addEventListener('hashchange', syncTopTabFromHash)
|
||||
})
|
||||
|
||||
@@ -407,11 +451,7 @@ onLoad((options: any = {}) => {
|
||||
syncTopTabToUrl(currentTab.value)
|
||||
}
|
||||
|
||||
if (currentTab.value === 'explain') {
|
||||
void loadExplainExhibits()
|
||||
} else {
|
||||
void loadGuideFloors()
|
||||
}
|
||||
loadTopTabData(currentTab.value)
|
||||
})
|
||||
|
||||
const loadGuideFloors = async () => {
|
||||
@@ -462,6 +502,7 @@ const handleFloorChange = (floor: string) => {
|
||||
activeGuideFloor.value = floor
|
||||
indoorView.value = 'floor'
|
||||
selectedGuidePoi.value = null
|
||||
isPoiCardCollapsed.value = false
|
||||
console.log('切换楼层:', floor)
|
||||
}
|
||||
|
||||
@@ -469,6 +510,7 @@ const handleIndoorViewChange = (view: GuideIndoorView) => {
|
||||
indoorView.value = view
|
||||
if (view !== 'floor') {
|
||||
selectedGuidePoi.value = null
|
||||
isPoiCardCollapsed.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,11 +519,13 @@ const handleAutoSwitch = (event: { from: 'overview' | 'floor'; to: 'overview' |
|
||||
indoorView.value = event.to
|
||||
if (event.to !== 'floor') {
|
||||
selectedGuidePoi.value = null
|
||||
isPoiCardCollapsed.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleGuidePoiClick = (poi: GuideRenderPoi) => {
|
||||
selectedGuidePoi.value = poi
|
||||
isPoiCardCollapsed.value = false
|
||||
indoorView.value = 'floor'
|
||||
showGuideMoreMenu.value = false
|
||||
isSimulatingRoute.value = false
|
||||
@@ -491,6 +535,19 @@ const handleGuidePoiClick = (poi: GuideRenderPoi) => {
|
||||
|
||||
const handleGuideSelectionClear = () => {
|
||||
selectedGuidePoi.value = null
|
||||
isPoiCardCollapsed.value = false
|
||||
}
|
||||
|
||||
const collapsePoiCard = () => {
|
||||
isPoiCardCollapsed.value = true
|
||||
}
|
||||
|
||||
const expandPoiCard = () => {
|
||||
isPoiCardCollapsed.value = false
|
||||
}
|
||||
|
||||
const togglePoiCardCollapsed = () => {
|
||||
isPoiCardCollapsed.value = !isPoiCardCollapsed.value
|
||||
}
|
||||
|
||||
const findRouteOptionForPoi = async (poi: GuideRenderPoi) => {
|
||||
@@ -574,16 +631,30 @@ const loadRouteTargets = async (keyword = '') => {
|
||||
}
|
||||
}
|
||||
|
||||
const refreshRouteReadinessState = async () => {
|
||||
try {
|
||||
const readiness = await guideRouteUseCase.getRouteReadiness()
|
||||
routePreviewUnavailableMessage.value = readiness.ready
|
||||
? ''
|
||||
: readiness.message || '当前暂不支持正式导航,可查看位置预览和路线示意'
|
||||
} catch (error) {
|
||||
console.warn('读取路线能力状态失败:', error)
|
||||
routePreviewUnavailableMessage.value = '当前暂不支持正式导航,可查看位置预览和路线示意'
|
||||
}
|
||||
}
|
||||
|
||||
const openRoutePlanner = () => {
|
||||
showRoutePlanner.value = true
|
||||
is3DMode.value = true
|
||||
indoorView.value = indoorView.value === 'overview' ? 'floor' : indoorView.value
|
||||
guideOutdoorState.value = 'home'
|
||||
showGuideMoreMenu.value = false
|
||||
isPoiCardCollapsed.value = false
|
||||
isSimulatingRoute.value = false
|
||||
if (!routePointOptions.value.length) {
|
||||
void loadRouteTargets()
|
||||
}
|
||||
void refreshRouteReadinessState()
|
||||
}
|
||||
|
||||
const clearRoutePreviewState = () => {
|
||||
@@ -639,6 +710,13 @@ const handleRouteView = async ({ startPoint, endPoint }: { startPoint: RoutePoin
|
||||
isSimulatingRoute.value = false
|
||||
|
||||
try {
|
||||
await refreshRouteReadinessState()
|
||||
if (routePreviewUnavailableMessage.value) {
|
||||
activeRoutePreview.value = null
|
||||
routePlanError.value = routePreviewUnavailableMessage.value
|
||||
return
|
||||
}
|
||||
|
||||
const result = await guideRouteUseCase.planRoute({
|
||||
startPoiId: startPoint.poiId,
|
||||
endPoiId: endPoint.poiId
|
||||
@@ -666,6 +744,7 @@ const handleRouteSimulate = () => {
|
||||
if (!activeRoutePreview.value) return
|
||||
showRoutePlanner.value = false
|
||||
selectedGuidePoi.value = null
|
||||
isPoiCardCollapsed.value = false
|
||||
showGuideMoreMenu.value = false
|
||||
isSimulatingRoute.value = true
|
||||
}
|
||||
@@ -689,6 +768,7 @@ const handleModeChange = (mode: '2d' | '3d') => {
|
||||
indoorView.value = 'overview'
|
||||
guideOutdoorState.value = 'home'
|
||||
selectedGuidePoi.value = null
|
||||
isPoiCardCollapsed.value = false
|
||||
showRoutePlanner.value = false
|
||||
showGuideMoreMenu.value = false
|
||||
isSimulatingRoute.value = false
|
||||
@@ -728,7 +808,28 @@ const handleMoreRouteGuide = () => {
|
||||
openRoutePlanner()
|
||||
}
|
||||
|
||||
const openOutdoorReference = (state: 'home' | 'entrance') => {
|
||||
is3DMode.value = false
|
||||
guideOutdoorState.value = state
|
||||
indoorView.value = 'overview'
|
||||
selectedGuidePoi.value = null
|
||||
isPoiCardCollapsed.value = false
|
||||
showRoutePlanner.value = false
|
||||
showGuideMoreMenu.value = false
|
||||
clearRoutePreviewState()
|
||||
}
|
||||
|
||||
const handleMoreOutdoorReference = () => {
|
||||
openOutdoorReference('home')
|
||||
}
|
||||
|
||||
const handleMoreRecommendedEntrance = () => {
|
||||
openOutdoorReference('entrance')
|
||||
}
|
||||
|
||||
const handleMoreToggleLayer = () => {
|
||||
is3DMode.value = true
|
||||
guideOutdoorState.value = 'home'
|
||||
indoorView.value = indoorLayerMode.value === 'multi' ? 'floor' : 'multi'
|
||||
showGuideMoreMenu.value = false
|
||||
}
|
||||
@@ -739,15 +840,7 @@ const guideOutdoorVariant = computed(() => (
|
||||
guideOutdoorState.value === 'entrance' ? 'entrance' : 'home'
|
||||
))
|
||||
const guideSearchText = computed(() => {
|
||||
if (is3DMode.value) {
|
||||
return '搜索设施、展厅、入口'
|
||||
}
|
||||
|
||||
if (guideOutdoorState.value === 'entrance') {
|
||||
return '选择入口或停车位置'
|
||||
}
|
||||
|
||||
return '搜索设施、展厅、入口'
|
||||
return '请输入地点进行搜索'
|
||||
})
|
||||
|
||||
// 音频相关处理
|
||||
@@ -897,9 +990,9 @@ const handleExplainLocationClick = async (exhibit: Exhibit) => {
|
||||
bottom: calc(env(safe-area-inset-bottom) + 30px);
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
border: 1px solid #ffffff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 28px rgba(36, 49, 42, 0.12);
|
||||
border: 1px solid #e5e6de;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 24px rgba(36, 49, 42, 0.12);
|
||||
z-index: 1003;
|
||||
}
|
||||
|
||||
@@ -907,6 +1000,10 @@ const handleExplainLocationClick = async (exhibit: Exhibit) => {
|
||||
padding: 10px 14px 14px;
|
||||
}
|
||||
|
||||
.guide-poi-card.collapsed {
|
||||
padding: 8px 10px 10px;
|
||||
}
|
||||
|
||||
.poi-card-handle,
|
||||
.more-popover-handle {
|
||||
width: 38px;
|
||||
@@ -916,6 +1013,32 @@ const handleExplainLocationClick = async (exhibit: Exhibit) => {
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.poi-card-collapsed {
|
||||
min-height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.poi-card-mini-action {
|
||||
flex: 0 0 auto;
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #151713;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.poi-card-mini-text {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--museum-accent);
|
||||
}
|
||||
|
||||
.poi-card-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
@@ -946,6 +1069,31 @@ const handleExplainLocationClick = async (exhibit: Exhibit) => {
|
||||
color: #696962;
|
||||
}
|
||||
|
||||
.poi-card-header-actions {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.poi-card-soft-action {
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #e4e5df;
|
||||
border-radius: 8px;
|
||||
background: #f5f7f2;
|
||||
}
|
||||
|
||||
.poi-card-soft-text {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
font-weight: 500;
|
||||
color: #545861;
|
||||
}
|
||||
|
||||
.poi-card-close {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
@@ -979,12 +1127,12 @@ const handleExplainLocationClick = async (exhibit: Exhibit) => {
|
||||
box-sizing: border-box;
|
||||
background: #f6f7f4;
|
||||
border: 1px solid #e5e6de;
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.poi-action.primary {
|
||||
background: #000000;
|
||||
border-color: #000000;
|
||||
background: #151713;
|
||||
border-color: #151713;
|
||||
}
|
||||
|
||||
.poi-action-text {
|
||||
@@ -1019,7 +1167,7 @@ const handleExplainLocationClick = async (exhibit: Exhibit) => {
|
||||
box-sizing: border-box;
|
||||
background: #f6f7f4;
|
||||
border: 1px solid #e5e6de;
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.more-action-title {
|
||||
@@ -1048,7 +1196,7 @@ const handleExplainLocationClick = async (exhibit: Exhibit) => {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
background: rgba(21, 23, 19, 0.86);
|
||||
border-radius: 17px;
|
||||
border-radius: 8px;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1003;
|
||||
}
|
||||
@@ -1077,9 +1225,9 @@ const handleExplainLocationClick = async (exhibit: Exhibit) => {
|
||||
gap: 10px;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
border: 1px solid #ffffff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 28px rgba(36, 49, 42, 0.12);
|
||||
border: 1px solid #e5e6de;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 24px rgba(36, 49, 42, 0.12);
|
||||
z-index: 1003;
|
||||
}
|
||||
|
||||
@@ -1103,7 +1251,7 @@ const handleExplainLocationClick = async (exhibit: Exhibit) => {
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
background: #000000;
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.route-sim-expand-text {
|
||||
|
||||
Reference in New Issue
Block a user