refactor: 重构数据层支持 SGS SDK 集成
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -14,6 +14,8 @@
|
||||
:floors="guideFloors"
|
||||
:normalize-floor-id="normalizeGuideFloorId"
|
||||
:target-focus-request="targetFocusRequest"
|
||||
:route-preview="activeRoutePreview"
|
||||
:show-route="Boolean(activeRoutePreview)"
|
||||
@search-tap="handleSearchTap"
|
||||
@mode-change="handleModeChange"
|
||||
@floor-change="handleFloorChange"
|
||||
@@ -108,6 +110,34 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-else-if="routeViewState === 'preview'"
|
||||
class="indoor-guide-card"
|
||||
>
|
||||
<text class="indoor-guide-title">{{ indoorGuideTitle }}</text>
|
||||
<text class="indoor-guide-desc">{{ indoorGuideDesc }}</text>
|
||||
<view v-if="routePlanError" class="indoor-guide-error">
|
||||
<text class="indoor-guide-error-text">{{ routePlanError }}</text>
|
||||
</view>
|
||||
<view class="indoor-guide-actions">
|
||||
<view
|
||||
class="indoor-guide-btn secondary"
|
||||
@tap="handleReturnToPreview"
|
||||
>
|
||||
<text class="indoor-guide-btn-text">查看位置</text>
|
||||
</view>
|
||||
<view
|
||||
class="indoor-guide-btn primary"
|
||||
:class="{ disabled: !routeReady || routePlanning }"
|
||||
@tap="handleStartIndoorGuide"
|
||||
>
|
||||
<text class="indoor-guide-btn-text">
|
||||
{{ routePlanning ? '规划中' : routeReady ? '开始导览' : '导航不可用' }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</GuideMapShell>
|
||||
</GuidePageFrame>
|
||||
</template>
|
||||
@@ -127,9 +157,13 @@ import {
|
||||
import type {
|
||||
GuideLocationPreview,
|
||||
GuideLocationPreviewPlan,
|
||||
GuideRouteResult,
|
||||
GuideStartLocation,
|
||||
MuseumFloor
|
||||
} from '@/domain/museum'
|
||||
import {
|
||||
guideRouteUseCase
|
||||
} from '@/usecases/guideRouteUseCase'
|
||||
import {
|
||||
navigateToGuideTopTab,
|
||||
saveLastGuideLocationPreviewUrl,
|
||||
@@ -166,6 +200,11 @@ const routeViewState = ref<RouteViewState>('preview')
|
||||
const activeFloor = ref('1F')
|
||||
const targetFocusRequest = ref<TargetPoiFocusRequest | null>(null)
|
||||
const targetFocusRequestId = ref(0)
|
||||
const routeReady = ref(false)
|
||||
const routeReadinessMessage = ref('')
|
||||
const routePlanning = ref(false)
|
||||
const routePlanError = ref('')
|
||||
const activeRoutePreview = ref<GuideRouteResult | null>(null)
|
||||
const manualFloors = computed(() => guideFloors.value.map((floor) => floor.label))
|
||||
const guideFloorLabel = (floorId: string) => (
|
||||
guideFloors.value.find((floor) => floor.id === floorId)?.label || floorId
|
||||
@@ -263,7 +302,7 @@ const shellConfig = computed(() => {
|
||||
}
|
||||
|
||||
return {
|
||||
searchText: `位置预览:${route.value.target}`,
|
||||
searchText: routeReady.value ? `室内导览:${route.value.target}` : `位置预览:${route.value.target}`,
|
||||
searchTop: '60px',
|
||||
activeMode: '3d' as GuideMode,
|
||||
activeFloor: activeFloor.value,
|
||||
@@ -281,6 +320,20 @@ const shellConfig = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const indoorGuideTitle = computed(() => (
|
||||
routeReady.value ? `室内导览:${route.value.target}` : `位置预览:${route.value.target}`
|
||||
))
|
||||
|
||||
const indoorGuideDesc = computed(() => {
|
||||
if (activeRoutePreview.value) {
|
||||
return `已规划 ${activeRoutePreview.value.start.name} 至 ${activeRoutePreview.value.end.name},约 ${Math.round(activeRoutePreview.value.distanceMeters)} 米。`
|
||||
}
|
||||
|
||||
return routeReady.value
|
||||
? 'SGS 路线数据已就绪,可从已选起点开始规划馆内路线。'
|
||||
: routeReadinessMessage.value || '当前暂不支持正式导航,可查看位置预览。'
|
||||
})
|
||||
|
||||
const safeDecode = (value: string) => {
|
||||
try {
|
||||
return decodeURIComponent(value)
|
||||
@@ -328,6 +381,19 @@ const saveCurrentLocationPreviewUrl = () => {
|
||||
saveLastGuideLocationPreviewUrl(`/pages/route/detail?${params.toString()}`)
|
||||
}
|
||||
|
||||
const refreshRouteReadinessState = async () => {
|
||||
try {
|
||||
const readiness = await guideRouteUseCase.getRouteReadiness()
|
||||
routeReady.value = readiness.ready
|
||||
routeReadinessMessage.value = readiness.message
|
||||
routePlanError.value = readiness.ready ? '' : readiness.message
|
||||
} catch (error) {
|
||||
routeReady.value = false
|
||||
routeReadinessMessage.value = error instanceof Error ? error.message : '路线能力状态读取失败'
|
||||
routePlanError.value = routeReadinessMessage.value
|
||||
}
|
||||
}
|
||||
|
||||
const createStartLocationFromOptions = (options: Record<string, unknown>) => {
|
||||
const label = normalizeStringOption(options.startLabel)
|
||||
|
||||
@@ -373,6 +439,7 @@ const requestTargetFocus = (target: Partial<RouteTargetLocation> | null, fallbac
|
||||
|
||||
const applyRouteOptions = async (options: Record<string, unknown> = {}) => {
|
||||
await loadGuideFloors()
|
||||
await refreshRouteReadinessState()
|
||||
|
||||
const facilityId = normalizeStringOption(options.facilityId)
|
||||
const target = normalizeStringOption(options.target)
|
||||
@@ -385,6 +452,7 @@ const applyRouteOptions = async (options: Record<string, unknown> = {}) => {
|
||||
routeViewState.value = 'preview'
|
||||
activeFloor.value = defaultGuideFloorLabel()
|
||||
targetFocusRequest.value = null
|
||||
activeRoutePreview.value = null
|
||||
isManualLocationPanelOpen.value = false
|
||||
return false
|
||||
}
|
||||
@@ -397,6 +465,7 @@ const applyRouteOptions = async (options: Record<string, unknown> = {}) => {
|
||||
routeViewState.value = 'preview'
|
||||
activeFloor.value = startLocation?.floor || defaultGuideFloorLabel()
|
||||
targetFocusRequest.value = null
|
||||
activeRoutePreview.value = null
|
||||
isManualLocationPanelOpen.value = false
|
||||
return false
|
||||
}
|
||||
@@ -485,6 +554,7 @@ const handleTargetFocus = (result: TargetPoiFocusResult) => {
|
||||
|
||||
const handleReturnToPreview = () => {
|
||||
isManualLocationPanelOpen.value = false
|
||||
activeRoutePreview.value = null
|
||||
routeViewState.value = 'preview'
|
||||
requestTargetFocus(route.value.targetLocation)
|
||||
saveCurrentLocationPreviewUrl()
|
||||
@@ -527,11 +597,67 @@ const handleConfirmLocation = async () => {
|
||||
)
|
||||
activeFloor.value = startLocation.floor || activeFloor.value
|
||||
isManualLocationPanelOpen.value = false
|
||||
activeRoutePreview.value = null
|
||||
routeViewState.value = 'preview'
|
||||
requestTargetFocus(route.value.targetLocation)
|
||||
saveCurrentLocationPreviewUrl()
|
||||
}
|
||||
|
||||
const handleStartIndoorGuide = async () => {
|
||||
if (routePlanning.value || !route.value.facilityId) return
|
||||
|
||||
await refreshRouteReadinessState()
|
||||
if (!routeReady.value) return
|
||||
|
||||
routePlanning.value = true
|
||||
routePlanError.value = ''
|
||||
|
||||
try {
|
||||
const targets = await guideRouteUseCase.listTargets()
|
||||
const startName = route.value.startLocation?.label || ''
|
||||
const startFloor = route.value.startLocation?.floor || ''
|
||||
const targetLocation = route.value.targetLocation
|
||||
const fallbackStart = targets.find((target) => (
|
||||
startName
|
||||
? target.name.includes(startName) || startName.includes(target.name)
|
||||
: false
|
||||
)) || targets.find((target) => (
|
||||
startFloor
|
||||
? target.floorLabel === startFloor || target.floorId === normalizeGuideFloorId(startFloor)
|
||||
: false
|
||||
)) || targets.find((target) => target.poiId !== route.value.facilityId)
|
||||
|
||||
const endTarget = targets.find((target) => target.poiId === route.value.facilityId)
|
||||
|| (targetLocation
|
||||
? targets.find((target) => target.floorId === targetLocation.floorId && target.name === targetLocation.name)
|
||||
: null)
|
||||
|
||||
if (!fallbackStart || !endTarget) {
|
||||
routePlanError.value = '未找到可用于路线规划的起点或终点'
|
||||
return
|
||||
}
|
||||
|
||||
const result = await guideRouteUseCase.planRoute({
|
||||
startPoiId: fallbackStart.poiId,
|
||||
endPoiId: endTarget.poiId
|
||||
})
|
||||
|
||||
if (!result.route) {
|
||||
routePlanError.value = result.error || '室内导览路线规划失败'
|
||||
activeRoutePreview.value = null
|
||||
return
|
||||
}
|
||||
|
||||
activeRoutePreview.value = result.route
|
||||
activeFloor.value = result.route.start.floorLabel
|
||||
} catch (error) {
|
||||
routePlanError.value = error instanceof Error ? error.message : '室内导览路线规划失败'
|
||||
activeRoutePreview.value = null
|
||||
} finally {
|
||||
routePlanning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleModeChange = (mode: GuideMode) => {
|
||||
if (routeViewState.value === 'outdoor-preview' && mode === '3d') {
|
||||
routeViewState.value = 'preview'
|
||||
@@ -606,6 +732,94 @@ const handlePageBack = () => {
|
||||
z-index: 45;
|
||||
}
|
||||
|
||||
.indoor-guide-card {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
bottom: calc(env(safe-area-inset-bottom) + 34px);
|
||||
min-height: 156px;
|
||||
padding: 18px 18px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
border: 1px solid #e5e6de;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 24px rgba(36, 49, 42, 0.14);
|
||||
z-index: 45;
|
||||
}
|
||||
|
||||
.indoor-guide-title {
|
||||
font-size: 17px;
|
||||
line-height: 24px;
|
||||
font-weight: 700;
|
||||
color: #151713;
|
||||
}
|
||||
|
||||
.indoor-guide-desc {
|
||||
font-size: 13px;
|
||||
line-height: 19px;
|
||||
color: #545861;
|
||||
}
|
||||
|
||||
.indoor-guide-error {
|
||||
padding: 7px 9px;
|
||||
background: #fff4f2;
|
||||
border: 1px solid #ffd2c9;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.indoor-guide-error-text {
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
color: #b44b42;
|
||||
}
|
||||
|
||||
.indoor-guide-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.indoor-guide-btn {
|
||||
flex: 1;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.indoor-guide-btn.secondary {
|
||||
background: #f5f7f2;
|
||||
border: 1px solid #e4e5df;
|
||||
}
|
||||
|
||||
.indoor-guide-btn.primary {
|
||||
background: #151713;
|
||||
}
|
||||
|
||||
.indoor-guide-btn.primary.disabled {
|
||||
background: #d8dbd2;
|
||||
}
|
||||
|
||||
.indoor-guide-btn-text {
|
||||
font-size: 14px;
|
||||
line-height: 19px;
|
||||
font-weight: 500;
|
||||
color: #151713;
|
||||
}
|
||||
|
||||
.indoor-guide-btn.primary .indoor-guide-btn-text {
|
||||
color: var(--museum-accent);
|
||||
}
|
||||
|
||||
.indoor-guide-btn.primary.disabled .indoor-guide-btn-text {
|
||||
color: #8b8b84;
|
||||
}
|
||||
|
||||
.target-required-title {
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
|
||||
Reference in New Issue
Block a user