refactor: 重构数据层支持 SGS SDK 集成
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -44,6 +44,7 @@ const copyStaticSubtree = (subtree) => {
|
||||
copyStaticSubtree('nav-assets')
|
||||
copyStaticSubtree('guide-data')
|
||||
copyStaticSubtree('sgs-map-sdk')
|
||||
copyStaticSubtree('three')
|
||||
|
||||
if (fs.existsSync(faviconSource)) {
|
||||
fs.copyFileSync(faviconSource, faviconTarget)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
:scale="mapScale"
|
||||
:markers="markers"
|
||||
:polygons="polygons"
|
||||
:polylines="displayPolylines"
|
||||
:show-location="!isH5"
|
||||
:enable-3D="false"
|
||||
:enable-overlooking="false"
|
||||
@@ -59,6 +60,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { getMuseumEntranceLocation } from '@/services/tencent/TencentMapService'
|
||||
|
||||
const isH5 = typeof window !== 'undefined'
|
||||
|
||||
@@ -94,6 +96,13 @@ interface Polygon {
|
||||
zIndex: number
|
||||
}
|
||||
|
||||
interface Polyline {
|
||||
points: Array<{ latitude: number; longitude: number }>
|
||||
color: string
|
||||
width: number
|
||||
dottedLine?: boolean
|
||||
}
|
||||
|
||||
interface MapFloor {
|
||||
id: string
|
||||
label: string
|
||||
@@ -102,15 +111,18 @@ interface MapFloor {
|
||||
const props = withDefaults(defineProps<{
|
||||
activeFloor?: string
|
||||
floors?: MapFloor[]
|
||||
polylines?: Polyline[]
|
||||
}>(), {
|
||||
activeFloor: '1F',
|
||||
floors: () => [] as MapFloor[]
|
||||
floors: () => [] as MapFloor[],
|
||||
polylines: () => [] as Polyline[]
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
markerClick: [markerId: number]
|
||||
floorChange: [floor: string]
|
||||
enter3DMode: []
|
||||
mapTap: [location: { latitude: number; longitude: number }]
|
||||
}>()
|
||||
|
||||
// 深圳自然博物馆实际坐标
|
||||
@@ -157,36 +169,77 @@ const polygons = ref<Polygon[]>([
|
||||
}
|
||||
])
|
||||
|
||||
// 地图标记点(博物馆入口和主要位置)
|
||||
const markers = ref<Marker[]>([
|
||||
{
|
||||
id: 0,
|
||||
latitude: 22.692763,
|
||||
longitude: 114.363487,
|
||||
iconPath: '/static/icons/marker-museum.svg',
|
||||
// 博物馆标记点(初始占位,API 获取后更新)
|
||||
const museumMarker = ref<Marker>({
|
||||
id: 0,
|
||||
latitude: 22.692763,
|
||||
longitude: 114.363487,
|
||||
iconPath: '/static/icons/marker-museum.svg',
|
||||
width: 40,
|
||||
height: 40,
|
||||
title: '深圳自然博物馆',
|
||||
callout: {
|
||||
content: '深圳自然博物馆',
|
||||
display: 'ALWAYS',
|
||||
padding: 12,
|
||||
borderRadius: 6,
|
||||
bgColor: '#E0E100',
|
||||
color: '#000000',
|
||||
fontSize: 14
|
||||
}
|
||||
})
|
||||
|
||||
// 主入口标记点
|
||||
const entranceMarker = ref<Marker>({
|
||||
id: 1,
|
||||
latitude: 22.692363,
|
||||
longitude: 114.363487,
|
||||
iconPath: '/static/icons/marker-entrance.svg',
|
||||
width: 30,
|
||||
height: 30,
|
||||
title: '主入口',
|
||||
callout: {
|
||||
content: '主入口',
|
||||
display: 'BYCLICK',
|
||||
padding: 10,
|
||||
borderRadius: 5,
|
||||
bgColor: '#000000',
|
||||
color: '#FFFFFF',
|
||||
fontSize: 12
|
||||
}
|
||||
})
|
||||
|
||||
// 地图标记点列表
|
||||
const markers = computed(() => [museumMarker.value, entranceMarker.value])
|
||||
|
||||
// 显示的路线列表(转换格式以适配 uni-app map 组件)
|
||||
const displayPolylines = computed(() => {
|
||||
return props.polylines.map((polyline, index) => ({
|
||||
...polyline,
|
||||
id: index,
|
||||
// 确保点格式正确
|
||||
points: polyline.points.map((p) => ({
|
||||
latitude: p.latitude,
|
||||
longitude: p.longitude
|
||||
}))
|
||||
}))
|
||||
})
|
||||
|
||||
/**
|
||||
* 更新用户位置标记
|
||||
*/
|
||||
const updateUserLocation = (location: { latitude: number; longitude: number }) => {
|
||||
const userMarkerIndex = markers.value.findIndex((m) => m.id === 999)
|
||||
const userMarker: Marker = {
|
||||
id: 999,
|
||||
latitude: location.latitude,
|
||||
longitude: location.longitude,
|
||||
iconPath: '/static/icons/marker-user-location.svg',
|
||||
width: 40,
|
||||
height: 40,
|
||||
title: '深圳自然博物馆',
|
||||
title: '我的位置',
|
||||
callout: {
|
||||
content: '深圳自然博物馆',
|
||||
display: 'ALWAYS',
|
||||
padding: 12,
|
||||
borderRadius: 6,
|
||||
bgColor: '#E0E100',
|
||||
color: '#000000',
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
latitude: 22.692363,
|
||||
longitude: 114.363487,
|
||||
iconPath: '/static/icons/marker-entrance.svg',
|
||||
width: 30,
|
||||
height: 30,
|
||||
title: '主入口',
|
||||
callout: {
|
||||
content: '主入口',
|
||||
content: '您在这里',
|
||||
display: 'BYCLICK',
|
||||
padding: 10,
|
||||
borderRadius: 5,
|
||||
@@ -194,8 +247,41 @@ const markers = ref<Marker[]>([
|
||||
color: '#FFFFFF',
|
||||
fontSize: 12
|
||||
}
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
if (userMarkerIndex >= 0) {
|
||||
markers.value[userMarkerIndex] = userMarker
|
||||
} else {
|
||||
markers.value.push(userMarker)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除用户位置标记
|
||||
*/
|
||||
const clearUserLocation = () => {
|
||||
const userMarkerIndex = markers.value.findIndex((m) => m.id === 999)
|
||||
if (userMarkerIndex >= 0) {
|
||||
markers.value.splice(userMarkerIndex, 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动地图中心到指定位置
|
||||
*/
|
||||
const moveTo = (location: { latitude: number; longitude: number }) => {
|
||||
mapCenter.value = {
|
||||
latitude: location.latitude,
|
||||
longitude: location.longitude
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
updateUserLocation,
|
||||
clearUserLocation,
|
||||
moveTo
|
||||
})
|
||||
|
||||
// 处理标记点点击
|
||||
const handleMarkerTap = (e: any) => {
|
||||
@@ -214,6 +300,10 @@ const handleRegionChange = (e: any) => {
|
||||
// 处理地图点击
|
||||
const handleMapTap = (e: any) => {
|
||||
console.log('点击地图:', e.detail)
|
||||
const { latitude, longitude } = e.detail || {}
|
||||
if (latitude !== undefined && longitude !== undefined) {
|
||||
emit('mapTap', { latitude, longitude })
|
||||
}
|
||||
}
|
||||
|
||||
// 处理楼层点击
|
||||
@@ -294,8 +384,20 @@ const handleEnter3D = () => {
|
||||
emit('enter3DMode')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log('地图组件已挂载')
|
||||
onMounted(async () => {
|
||||
console.log('地图组件已挂载,开始获取入口位置...')
|
||||
|
||||
// 从腾讯地图 API 获取入口位置
|
||||
const entranceLocation = await getMuseumEntranceLocation()
|
||||
|
||||
if (entranceLocation) {
|
||||
entranceMarker.value = {
|
||||
...entranceMarker.value,
|
||||
latitude: entranceLocation.lat,
|
||||
longitude: entranceLocation.lng
|
||||
}
|
||||
console.log('已更新入口位置:', entranceLocation)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ interface Position {
|
||||
y: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
withDefaults(defineProps<{
|
||||
position: Position
|
||||
label?: string
|
||||
type?: 'exhibit' | 'hall' | 'facility' | 'location'
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
withDefaults(defineProps<{
|
||||
modelValue?: string
|
||||
placeholder?: string
|
||||
}>(), {
|
||||
|
||||
@@ -6,6 +6,8 @@ const allowedGuideContentModes = new Set<GuideContentDataSourceMode>(['static',
|
||||
const defaultSdkScriptUrl = '/static/sgs-map-sdk/index.global.js'
|
||||
const defaultSgsEngineUrl = '/h5-sdk'
|
||||
const defaultSdkTimeoutMs = 5000
|
||||
const defaultApiBaseUrl = '/app-api'
|
||||
const defaultSgsMapId = '1'
|
||||
const defaultGuideStaticDataBaseUrl = '/static/guide-data'
|
||||
const defaultAudioApiBaseUrl = '/yudao-server'
|
||||
|
||||
@@ -52,12 +54,17 @@ export const dataSourceConfig = {
|
||||
import.meta.env.VITE_GUIDE_STATIC_DATA_BASE_URL,
|
||||
defaultGuideStaticDataBaseUrl
|
||||
),
|
||||
apiBaseUrl: import.meta.env.VITE_API_BASE_URL?.trim() || '',
|
||||
audioApiBaseUrl: normalizeUrl(
|
||||
import.meta.env.VITE_AUDIO_API_BASE_URL || import.meta.env.VITE_API_BASE_URL,
|
||||
defaultAudioApiBaseUrl
|
||||
),
|
||||
apiBaseUrl: normalizeUrl(import.meta.env.VITE_API_BASE_URL, defaultApiBaseUrl),
|
||||
audioLanguage: import.meta.env.VITE_AUDIO_LANGUAGE === 'en-US' ? 'en-US' : 'zh-CN',
|
||||
sgsApiBaseUrl: normalizeUrl(
|
||||
import.meta.env.VITE_SGS_API_BASE_URL || import.meta.env.VITE_API_BASE_URL,
|
||||
defaultApiBaseUrl
|
||||
),
|
||||
sgsMapId: normalizeUrl(import.meta.env.VITE_SGS_MAP_ID, defaultSgsMapId),
|
||||
sgsSdkScriptUrl: normalizeUrl(import.meta.env.VITE_SGS_SDK_SCRIPT_URL, defaultSdkScriptUrl),
|
||||
sgsMapEngineUrl: normalizeUrl(import.meta.env.VITE_SGS_H5_ENGINE_URL, defaultSgsEngineUrl),
|
||||
sgsSdkTargetOrigin: import.meta.env.VITE_SGS_SDK_ORIGIN?.trim() || '',
|
||||
|
||||
@@ -4,6 +4,68 @@ export interface MuseumFloor {
|
||||
order: number
|
||||
}
|
||||
|
||||
export type GuideDiagnosticsStatus = 'OK' | 'WARN' | 'ERROR'
|
||||
export type GuideDataIssueSeverity = 'info' | 'warn' | 'error'
|
||||
|
||||
export interface GuideFloorDetail extends MuseumFloor {
|
||||
sourceCode?: string
|
||||
sourceName?: string
|
||||
modelReady?: boolean
|
||||
poiCount?: number
|
||||
spaceCount?: number
|
||||
guideStopCount?: number
|
||||
navigablePlaceCount?: number
|
||||
routeNodeCount?: number
|
||||
routeEdgeCount?: number
|
||||
routePlanningReady?: boolean
|
||||
warnings: string[]
|
||||
}
|
||||
|
||||
export interface GuideMapDiagnosticsSummary {
|
||||
floorCount: number
|
||||
modelReadyFloorCount: number
|
||||
poiCount: number
|
||||
spaceCount: number
|
||||
guideStopCount: number
|
||||
navigablePlaceCount: number
|
||||
routeNodeCount: number
|
||||
routeEdgeCount: number
|
||||
}
|
||||
|
||||
export interface GuideMapDiagnostics {
|
||||
mapId: string
|
||||
mapName: string
|
||||
sdkVersion?: string
|
||||
status: GuideDiagnosticsStatus
|
||||
floors: GuideFloorDetail[]
|
||||
summary: GuideMapDiagnosticsSummary
|
||||
warnings: string[]
|
||||
}
|
||||
|
||||
export interface GuideDataIntegrityIssue {
|
||||
id: string
|
||||
scope: 'floor' | 'poi'
|
||||
severity: GuideDataIssueSeverity
|
||||
message: string
|
||||
floorId?: string
|
||||
floorLabel?: string
|
||||
poiId?: string
|
||||
fields?: string[]
|
||||
}
|
||||
|
||||
export interface GuideDataIntegrityReport {
|
||||
status: GuideDiagnosticsStatus
|
||||
summary: {
|
||||
floorCount: number
|
||||
poiCount: number
|
||||
issueCount: number
|
||||
errorCount: number
|
||||
warningCount: number
|
||||
}
|
||||
issues: GuideDataIntegrityIssue[]
|
||||
warnings: string[]
|
||||
}
|
||||
|
||||
export interface MuseumCategory {
|
||||
id: string
|
||||
label: string
|
||||
|
||||
3
src/env.d.ts
vendored
3
src/env.d.ts
vendored
@@ -7,10 +7,13 @@ interface ImportMetaEnv {
|
||||
readonly VITE_API_BASE_URL?: string
|
||||
readonly VITE_AUDIO_API_BASE_URL?: string
|
||||
readonly VITE_AUDIO_LANGUAGE?: 'zh-CN' | 'en-US'
|
||||
readonly VITE_SGS_API_BASE_URL?: string
|
||||
readonly VITE_SGS_MAP_ID?: string
|
||||
readonly VITE_SGS_SDK_SCRIPT_URL?: string
|
||||
readonly VITE_SGS_H5_ENGINE_URL?: string
|
||||
readonly VITE_SGS_SDK_ORIGIN?: string
|
||||
readonly VITE_SGS_SDK_TIMEOUT_MS?: string
|
||||
readonly VITE_TENCENT_MAP_KEY?: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,6 +4,18 @@ import type {
|
||||
GuideModelSource,
|
||||
GuideRenderPoi
|
||||
} from '@/domain/guideModel'
|
||||
import {
|
||||
dataSourceConfig
|
||||
} from '@/config/dataSource'
|
||||
import {
|
||||
formatSgsFloorLabel,
|
||||
toMuseumPoiFromSgs
|
||||
} from '@/data/adapters/sgsSdkGuideAdapter'
|
||||
import {
|
||||
defaultSgsSdkApiProvider,
|
||||
type SgsSdkApiProvider,
|
||||
type SgsSdkFloorSummaryPayload
|
||||
} from '@/data/providers/sgsSdkApiProvider'
|
||||
import {
|
||||
formatNavFloorLabel
|
||||
} from '@/data/adapters/navAssetsAdapter'
|
||||
@@ -24,6 +36,38 @@ const toRenderPoi = (poi: StaticNavPoiPayload): GuideRenderPoi => ({
|
||||
sourceObjectName: poi.sourceObjectName
|
||||
})
|
||||
|
||||
const toSgsRenderPoi = (
|
||||
poi: ReturnType<typeof toMuseumPoiFromSgs>
|
||||
): GuideRenderPoi => ({
|
||||
id: poi.id,
|
||||
name: poi.name,
|
||||
floorId: poi.floorId,
|
||||
primaryCategory: poi.primaryCategory.id,
|
||||
primaryCategoryZh: poi.primaryCategory.label,
|
||||
iconType: poi.primaryCategory.iconType || poi.primaryCategory.id,
|
||||
positionGltf: poi.positionGltf,
|
||||
sourceObjectName: poi.sourceObjectName
|
||||
})
|
||||
|
||||
const trimTrailingSlash = (value: string) => value.replace(/\/+$/, '')
|
||||
|
||||
const resolveSgsAssetUrl = (url?: string | null) => {
|
||||
const normalizedUrl = url?.trim()
|
||||
if (!normalizedUrl) return ''
|
||||
if (/^https?:\/\//i.test(normalizedUrl)) return normalizedUrl
|
||||
if (!normalizedUrl.startsWith('/')) return normalizedUrl
|
||||
|
||||
const baseUrl = trimTrailingSlash(dataSourceConfig.sgsApiBaseUrl || dataSourceConfig.apiBaseUrl || '')
|
||||
const origin = baseUrl.endsWith('/app-api')
|
||||
? baseUrl.slice(0, -'/app-api'.length)
|
||||
: baseUrl
|
||||
|
||||
return origin ? `${origin}${normalizedUrl}` : normalizedUrl
|
||||
}
|
||||
|
||||
const sortSgsFloors = (floors: SgsSdkFloorSummaryPayload[]) => [...floors]
|
||||
.sort((a, b) => Number(a.sortOrder || 0) - Number(b.sortOrder || 0))
|
||||
|
||||
export interface GuideModelRepository extends GuideModelSource {}
|
||||
|
||||
export class StaticGuideModelRepository implements GuideModelRepository {
|
||||
@@ -63,4 +107,67 @@ export class StaticGuideModelRepository implements GuideModelRepository {
|
||||
}
|
||||
}
|
||||
|
||||
export const guideModelRepository = new StaticGuideModelRepository()
|
||||
export class SgsSdkGuideModelRepository implements GuideModelRepository {
|
||||
private floorsCache: SgsSdkFloorSummaryPayload[] | null = null
|
||||
|
||||
constructor(private readonly provider: SgsSdkApiProvider = defaultSgsSdkApiProvider) {}
|
||||
|
||||
async loadPackage(): Promise<GuideModelRenderPackage> {
|
||||
const manifest = await this.provider.getManifest()
|
||||
const floors = sortSgsFloors(manifest.floors)
|
||||
this.floorsCache = floors
|
||||
|
||||
const bundles = await Promise.all(
|
||||
floors.map((floor) => this.provider.getFloorBundle(String(floor.floorId)))
|
||||
)
|
||||
|
||||
const floorAssets: GuideModelFloorAsset[] = floors
|
||||
.map((floor, index) => {
|
||||
const bundle = bundles[index]
|
||||
const modelUrl = resolveSgsAssetUrl(bundle.model?.modelUrl || bundle.model?.fallbackModelUrl)
|
||||
|
||||
return {
|
||||
floorId: String(floor.floorId),
|
||||
label: formatSgsFloorLabel(floor.floorCode, floor.floorName),
|
||||
order: Number(floor.sortOrder || 0),
|
||||
modelUrl,
|
||||
sharedModelAsset: false
|
||||
}
|
||||
})
|
||||
.filter((asset) => asset.modelUrl)
|
||||
|
||||
const overviewFloorIndex = floors.findIndex((floor) => floor.floorCode === 'EXTERIOR')
|
||||
const overviewModelUrl = floorAssets[overviewFloorIndex]?.modelUrl || floorAssets[0]?.modelUrl || ''
|
||||
|
||||
return {
|
||||
overviewModelUrl,
|
||||
floors: floorAssets
|
||||
}
|
||||
}
|
||||
|
||||
async loadFloorPois(floorId: string) {
|
||||
const manifest = await this.provider.getManifest()
|
||||
this.floorsCache = sortSgsFloors(manifest.floors)
|
||||
const matchedFloor = this.floorsCache.find((floor) => (
|
||||
String(floor.floorId) === floorId
|
||||
|| floor.floorCode === floorId
|
||||
|| formatSgsFloorLabel(floor.floorCode, floor.floorName) === floorId
|
||||
))
|
||||
if (!matchedFloor) return []
|
||||
|
||||
const pois = await this.provider.getFloorPois(String(matchedFloor.floorId))
|
||||
return pois
|
||||
.map((poi) => toSgsRenderPoi(toMuseumPoiFromSgs(poi, manifest.floors)))
|
||||
.filter((poi) => Array.isArray(poi.positionGltf) && poi.positionGltf.length === 3)
|
||||
}
|
||||
}
|
||||
|
||||
export const createGuideModelRepository = (): GuideModelRepository => {
|
||||
if (dataSourceConfig.mode === 'api' || dataSourceConfig.mode === 'sdk') {
|
||||
return new SgsSdkGuideModelRepository()
|
||||
}
|
||||
|
||||
return new StaticGuideModelRepository()
|
||||
}
|
||||
|
||||
export const guideModelRepository = createGuideModelRepository()
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type {
|
||||
GuideDataIntegrityReport,
|
||||
GuideFloorDetail,
|
||||
GuideLocationPreview,
|
||||
GuideMapDiagnostics,
|
||||
GuideRouteReadiness,
|
||||
MuseumFloor,
|
||||
MuseumPoi
|
||||
@@ -16,6 +19,20 @@ import {
|
||||
defaultStaticNavAssetsProvider,
|
||||
type StaticNavAssetsProvider
|
||||
} from '@/data/providers/staticNavAssetsProvider'
|
||||
import {
|
||||
defaultSgsSdkApiProvider,
|
||||
type SgsSdkApiProvider
|
||||
} from '@/data/providers/sgsSdkApiProvider'
|
||||
import {
|
||||
buildSgsFloorAliases,
|
||||
createGuideDataIntegrityReport,
|
||||
toGuideFloorDetailFromSgs,
|
||||
toGuideMapDiagnostics,
|
||||
toLocationPreviewFromPoi,
|
||||
toMuseumFloorFromSgs,
|
||||
toMuseumPoiFromSgs,
|
||||
toRouteReadinessFromSgsDiagnostics
|
||||
} from '@/data/adapters/sgsSdkGuideAdapter'
|
||||
|
||||
const searchableCategories = new Set([
|
||||
'touring_poi',
|
||||
@@ -60,6 +77,9 @@ export interface GuideRepository {
|
||||
searchPois(keyword?: string): Promise<MuseumPoi[]>
|
||||
getLocationPreview(poiId: string): Promise<GuideLocationPreview | null>
|
||||
getRouteReadiness(): Promise<GuideRouteReadiness>
|
||||
getFloorDetail?(floorId: string): Promise<GuideFloorDetail | null>
|
||||
getMapDiagnostics?(): Promise<GuideMapDiagnostics>
|
||||
checkDataIntegrity?(): Promise<GuideDataIntegrityReport>
|
||||
}
|
||||
|
||||
export class StaticGuideRepository implements GuideRepository {
|
||||
@@ -120,6 +140,161 @@ export class StaticGuideRepository implements GuideRepository {
|
||||
return this.provider.loadRouteReadiness()
|
||||
.catch(() => NAV_ROUTE_READINESS)
|
||||
}
|
||||
|
||||
async getFloorDetail(floorId: string) {
|
||||
const floors = await this.getFloors()
|
||||
const normalizedFloorId = this.normalizeFloorId(floorId)
|
||||
const floor = floors.find((item) => item.id === normalizedFloorId)
|
||||
return floor
|
||||
? {
|
||||
...floor,
|
||||
warnings: []
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
async getMapDiagnostics(): Promise<GuideMapDiagnostics> {
|
||||
const [floors, pois] = await Promise.all([
|
||||
this.getFloors(),
|
||||
this.listPois()
|
||||
])
|
||||
|
||||
return {
|
||||
mapId: 'static',
|
||||
mapName: '本地静态导览数据',
|
||||
status: 'WARN',
|
||||
floors: floors.map((floor) => ({
|
||||
...floor,
|
||||
poiCount: pois.filter((poi) => poi.floorId === floor.id).length,
|
||||
warnings: []
|
||||
})),
|
||||
summary: {
|
||||
floorCount: floors.length,
|
||||
modelReadyFloorCount: floors.length,
|
||||
poiCount: pois.length,
|
||||
spaceCount: 0,
|
||||
guideStopCount: 0,
|
||||
navigablePlaceCount: 0,
|
||||
routeNodeCount: 0,
|
||||
routeEdgeCount: 0
|
||||
},
|
||||
warnings: ['static 模式仅提供本地导览资源基础检查']
|
||||
}
|
||||
}
|
||||
|
||||
async checkDataIntegrity() {
|
||||
const [floors, pois, diagnostics] = await Promise.all([
|
||||
this.getFloors(),
|
||||
this.listPois(),
|
||||
this.getMapDiagnostics()
|
||||
])
|
||||
|
||||
return createGuideDataIntegrityReport(diagnostics, floors, pois)
|
||||
}
|
||||
}
|
||||
|
||||
export const guideRepository = new StaticGuideRepository()
|
||||
export class SgsSdkGuideRepository implements GuideRepository {
|
||||
private floorsCache: MuseumFloor[] | null = null
|
||||
private poiCache: MuseumPoi[] | null = null
|
||||
private floorAliases: Map<string, string> | null = null
|
||||
|
||||
constructor(private readonly provider: SgsSdkApiProvider = defaultSgsSdkApiProvider) {}
|
||||
|
||||
getAssetBaseUrl() {
|
||||
return ''
|
||||
}
|
||||
|
||||
async getFloors() {
|
||||
if (this.floorsCache) return this.floorsCache
|
||||
|
||||
const manifest = await this.provider.getManifest()
|
||||
this.floorAliases = buildSgsFloorAliases(manifest.floors)
|
||||
this.floorsCache = [...manifest.floors]
|
||||
.sort((a, b) => Number(a.sortOrder || 0) - Number(b.sortOrder || 0))
|
||||
.map(toMuseumFloorFromSgs)
|
||||
|
||||
return this.floorsCache
|
||||
}
|
||||
|
||||
normalizeFloorId(labelOrId: string) {
|
||||
const normalized = labelOrId.trim()
|
||||
const aliases = this.floorAliases
|
||||
if (!aliases) return normalized
|
||||
|
||||
return aliases.get(normalized) || aliases.get(normalized.toLowerCase()) || normalized
|
||||
}
|
||||
|
||||
async listPois() {
|
||||
if (this.poiCache) return this.poiCache
|
||||
|
||||
const manifest = await this.provider.getManifest()
|
||||
this.floorAliases = buildSgsFloorAliases(manifest.floors)
|
||||
const poiGroups = await Promise.all(
|
||||
manifest.floors.map((floor) => this.provider.getFloorPois(String(floor.floorId)))
|
||||
)
|
||||
|
||||
this.poiCache = poiGroups
|
||||
.flat()
|
||||
.map((poi) => toMuseumPoiFromSgs(poi, manifest.floors))
|
||||
.filter((poi) => poi.id && poi.name)
|
||||
|
||||
return this.poiCache
|
||||
}
|
||||
|
||||
async getPoiById(id: string) {
|
||||
const pois = await this.listPois()
|
||||
return pois.find((poi) => poi.id === id) || null
|
||||
}
|
||||
|
||||
async searchPois(keyword = '') {
|
||||
const pois = await this.listPois()
|
||||
const normalizedKeyword = keyword.trim().toLowerCase()
|
||||
|
||||
if (!normalizedKeyword) return pois
|
||||
|
||||
return pois.filter((poi) => toSearchText(poi).includes(normalizedKeyword))
|
||||
}
|
||||
|
||||
async getLocationPreview(poiId: string) {
|
||||
const poi = await this.getPoiById(poiId)
|
||||
return poi ? toLocationPreviewFromPoi(poi) : null
|
||||
}
|
||||
|
||||
async getRouteReadiness() {
|
||||
return this.provider.getMapDiagnostics()
|
||||
.then(toRouteReadinessFromSgsDiagnostics)
|
||||
.catch(() => NAV_ROUTE_READINESS)
|
||||
}
|
||||
|
||||
async getFloorDetail(floorId: string) {
|
||||
await this.getFloors()
|
||||
const normalizedFloorId = this.normalizeFloorId(floorId)
|
||||
try {
|
||||
const diagnostics = await this.provider.getFloorDiagnostics(normalizedFloorId)
|
||||
return toGuideFloorDetailFromSgs(diagnostics)
|
||||
} catch {
|
||||
const manifest = await this.provider.getManifest()
|
||||
const floor = manifest.floors.find((item) => String(item.floorId) === normalizedFloorId)
|
||||
return floor ? toGuideFloorDetailFromSgs(floor) : null
|
||||
}
|
||||
}
|
||||
|
||||
async getMapDiagnostics() {
|
||||
const [manifest, diagnostics] = await Promise.all([
|
||||
this.provider.getManifest(),
|
||||
this.provider.getMapDiagnostics()
|
||||
])
|
||||
|
||||
return toGuideMapDiagnostics(diagnostics, manifest)
|
||||
}
|
||||
|
||||
async checkDataIntegrity() {
|
||||
const [floors, pois, diagnostics] = await Promise.all([
|
||||
this.getFloors(),
|
||||
this.listPois(),
|
||||
this.getMapDiagnostics()
|
||||
])
|
||||
|
||||
return createGuideDataIntegrityReport(diagnostics, floors, pois)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ import type {
|
||||
SearchIndexItem
|
||||
} from '@/domain/museum'
|
||||
import {
|
||||
guideRepository,
|
||||
type GuideRepository
|
||||
} from '@/repositories/GuideRepository'
|
||||
import {
|
||||
guideRepository
|
||||
} from '@/repositories/createGuideRepository'
|
||||
import {
|
||||
staticMuseumContentProvider,
|
||||
type MuseumContentProvider
|
||||
|
||||
@@ -20,6 +20,8 @@ export interface SgsTargetFocusResult {
|
||||
message?: string
|
||||
}
|
||||
|
||||
export type SgsFloorId = string | number
|
||||
|
||||
export const toNumericFloorId = (floorId: string | number | undefined) => {
|
||||
if (typeof floorId === 'number') return floorId
|
||||
if (!floorId) return 1
|
||||
@@ -29,6 +31,17 @@ export const toNumericFloorId = (floorId: string | number | undefined) => {
|
||||
return Number.isFinite(numeric) && numeric > 0 ? numeric : 1
|
||||
}
|
||||
|
||||
export const toSgsFloorId = (floorId: string | number | undefined): SgsFloorId => {
|
||||
if (typeof floorId === 'number') return floorId
|
||||
if (!floorId) return 1
|
||||
|
||||
const normalized = floorId.toString().trim()
|
||||
if (/^\d{15,}$/.test(normalized)) return normalized
|
||||
if (/^\d+$/.test(normalized)) return Number(normalized)
|
||||
|
||||
return toNumericFloorId(normalized)
|
||||
}
|
||||
|
||||
export const toAppFloorId = (floorId: string | number | undefined) => `L${toNumericFloorId(floorId)}`
|
||||
|
||||
export const toFloorLabel = (floorId: string | number | undefined) => `${toNumericFloorId(floorId)}F`
|
||||
|
||||
@@ -14,7 +14,7 @@ interface SgsMapServiceOptions {
|
||||
scriptUrl: string
|
||||
sdkUrl: string
|
||||
targetOrigin: string
|
||||
floorId: number
|
||||
floorId: string | number
|
||||
timeout: number
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ export class SgsMapService {
|
||||
return this.sdk
|
||||
}
|
||||
|
||||
async changeFloor(floorId: number) {
|
||||
async changeFloor(floorId: string | number) {
|
||||
const sdk = await this.getReadySdk()
|
||||
return sdk.changeFloor(floorId, this.options.timeout)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export type SgsMapNode =
|
||||
| { type: 'coord'; floorId: number; x: number; z: number; y?: number }
|
||||
| { type: 'coord'; floorId: string | number; x: number; z: number; y?: number }
|
||||
| { type: 'poiId'; value: number }
|
||||
| { type: 'nodeName'; value: string }
|
||||
|
||||
@@ -27,14 +27,14 @@ export interface SgsMapSDKOptions {
|
||||
container: string | HTMLElement
|
||||
sdkUrl?: string
|
||||
targetOrigin?: string
|
||||
floorId?: number
|
||||
floorId?: string | number
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
export interface SgsMapEvents {
|
||||
ready: { engineVersion: string; protocolVersion: number }
|
||||
poiClick: { id?: number; poiId?: number; name: string; type: string; floorId?: number; description?: string }
|
||||
floorChanged: { floorId: number }
|
||||
poiClick: { id?: number; poiId?: number; name: string; type: string; floorId?: string | number; description?: string }
|
||||
floorChanged: { floorId: string | number }
|
||||
error: { code: string; message: string }
|
||||
loadError: { type: string; detail: string }
|
||||
}
|
||||
@@ -63,9 +63,9 @@ export interface SgsMapSDKInstance {
|
||||
on<K extends keyof SgsMapEvents>(event: K, listener: (payload: SgsMapEvents[K]) => void): void
|
||||
off<K extends keyof SgsMapEvents>(event: K, listener: (payload: SgsMapEvents[K]) => void): void
|
||||
getIsReady(): boolean
|
||||
getCurrentFloor(): number
|
||||
getCurrentFloor(): string | number
|
||||
focusTo(node: SgsMapNode | string): void
|
||||
changeFloor(floorId: number, timeout?: number): Promise<{ success: boolean; floorId: number }>
|
||||
changeFloor(floorId: string | number, timeout?: number): Promise<{ success: boolean; floorId: string | number }>
|
||||
addMarker(config: SgsMarkerConfig): void
|
||||
removeMarker(markerId: string): void
|
||||
clearMarkers(): void
|
||||
|
||||
@@ -5,12 +5,11 @@ import type {
|
||||
} from '@/domain/museum'
|
||||
import {
|
||||
GuideRouteError,
|
||||
guideRouteRepository,
|
||||
type GuideRouteRepository
|
||||
} from '@/repositories/GuideRouteRepository'
|
||||
import {
|
||||
applyRouteReadinessGate
|
||||
} from '@/domain/guideReadiness'
|
||||
createGuideRouteRepository
|
||||
} from '@/repositories/createGuideRouteRepository'
|
||||
|
||||
const toSearchText = (target: GuideRouteTarget) => [
|
||||
target.name,
|
||||
@@ -36,12 +35,12 @@ export class GuideRouteUseCase {
|
||||
private targetsCache: GuideRouteTarget[] | null = null
|
||||
private readinessCache: GuideRouteReadiness | null = null
|
||||
|
||||
constructor(private readonly repository: GuideRouteRepository = guideRouteRepository) {}
|
||||
constructor(private readonly repository: GuideRouteRepository = createGuideRouteRepository()) {}
|
||||
|
||||
async getRouteReadiness() {
|
||||
if (this.readinessCache) return this.readinessCache
|
||||
|
||||
this.readinessCache = applyRouteReadinessGate(await this.repository.getRouteReadiness())
|
||||
this.readinessCache = await this.repository.getRouteReadiness()
|
||||
return this.readinessCache
|
||||
}
|
||||
|
||||
@@ -89,7 +88,7 @@ export class GuideRouteUseCase {
|
||||
return error.message
|
||||
}
|
||||
|
||||
return error instanceof Error ? error.message : '路线预览生成失败'
|
||||
return error instanceof Error ? error.message : '室内导览位置关系生成失败'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import type {
|
||||
GuideDataIntegrityReport,
|
||||
GuideFloorDetail,
|
||||
GuideRouteReadiness,
|
||||
GuideLocationPreviewPlan,
|
||||
GuideMapDiagnostics,
|
||||
GuidePreviewStep,
|
||||
GuideStartLocation,
|
||||
MuseumPoi
|
||||
} from '@/domain/museum'
|
||||
import {
|
||||
guideRepository,
|
||||
type GuideRepository
|
||||
} from '@/repositories/GuideRepository'
|
||||
import {
|
||||
guideRepository
|
||||
} from '@/repositories/createGuideRepository'
|
||||
import {
|
||||
guideModelRepository,
|
||||
type GuideModelRepository
|
||||
@@ -114,6 +119,24 @@ export class GuideUseCase {
|
||||
return this.guide.getPoiById(id)
|
||||
}
|
||||
|
||||
getFloorDetail(floorId: string): Promise<GuideFloorDetail | null> {
|
||||
return this.guide.getFloorDetail
|
||||
? this.guide.getFloorDetail(floorId)
|
||||
: Promise.resolve(null)
|
||||
}
|
||||
|
||||
getMapDiagnostics(): Promise<GuideMapDiagnostics | null> {
|
||||
return this.guide.getMapDiagnostics
|
||||
? this.guide.getMapDiagnostics()
|
||||
: Promise.resolve(null)
|
||||
}
|
||||
|
||||
checkDataIntegrity(): Promise<GuideDataIntegrityReport | null> {
|
||||
return this.guide.checkDataIntegrity
|
||||
? this.guide.checkDataIntegrity()
|
||||
: Promise.resolve(null)
|
||||
}
|
||||
|
||||
async createLocationPreviewPlan(
|
||||
facilityId: string,
|
||||
target: string,
|
||||
|
||||
Reference in New Issue
Block a user