@@ -3,25 +3,14 @@
|
||||
<view class="map-layer" :class="`map-${mapType}`">
|
||||
<template v-if="mapType === 'indoor'">
|
||||
<!-- #ifdef H5 -->
|
||||
<SgsMapRenderer
|
||||
v-if="useSgsMapRenderer"
|
||||
ref="indoorRendererRef"
|
||||
class="indoor-three-map"
|
||||
:initial-floor-id="activeFloorId"
|
||||
:initial-view="indoorInitialView"
|
||||
:floors="props.floors"
|
||||
:target-focus="targetFocusRequest"
|
||||
@target-focus="handleTargetFocus"
|
||||
@floor-change="handleSdkFloorChange"
|
||||
/>
|
||||
<ThreeMap
|
||||
v-else-if="indoorModelSource"
|
||||
v-if="effectiveIndoorModelSource"
|
||||
ref="indoorRendererRef"
|
||||
class="indoor-three-map"
|
||||
:asset-base-url="indoorAssetBaseUrl"
|
||||
:model-source="indoorModelSource"
|
||||
:model-source="effectiveIndoorModelSource"
|
||||
:initial-floor-id="activeFloorId"
|
||||
:initial-view="indoorInitialView"
|
||||
:initial-view="indoorView"
|
||||
:show-controls="false"
|
||||
:show-poi="shouldShowIndoorPois"
|
||||
:target-focus="targetFocusRequest"
|
||||
@@ -50,6 +39,8 @@
|
||||
class="outdoor-map"
|
||||
:active-floor="activeFloor"
|
||||
:floors="props.floors"
|
||||
:polylines="outdoorNavPolylines"
|
||||
@map-tap="handleMapTap"
|
||||
/>
|
||||
</view>
|
||||
<view v-if="dimmed" class="dim-layer"></view>
|
||||
@@ -170,11 +161,6 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="showLocationControl" class="location-control" :style="locationControlStyle" @tap="handleResetView">
|
||||
<view class="location-ring"></view>
|
||||
<view class="location-dot"></view>
|
||||
</view>
|
||||
|
||||
<view v-if="showMoreControl" class="more-control" :style="moreControlStyle" @tap="handleMoreTap">
|
||||
<text class="more-control-text">更多</text>
|
||||
</view>
|
||||
@@ -195,14 +181,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import TencentMap from '@/components/map/TencentMap.vue'
|
||||
// #ifdef H5
|
||||
import ThreeMap from '@/components/map/ThreeMap.vue'
|
||||
import SgsMapRenderer from '@/components/map/SgsMapRenderer.vue'
|
||||
import {
|
||||
isSgsSdkMode
|
||||
} from '@/config/dataSource'
|
||||
// #endif
|
||||
import type {
|
||||
GuideModelSource,
|
||||
@@ -217,6 +199,14 @@ interface GuideFloorOption {
|
||||
label: string
|
||||
}
|
||||
|
||||
interface OutdoorNavPolyline {
|
||||
points: Array<{ latitude: number; longitude: number }>
|
||||
color: string
|
||||
width: number
|
||||
}
|
||||
|
||||
type CameraViewMode = 'reset' | 'top' | 'oblique'
|
||||
|
||||
interface TargetPoiFocusRequest {
|
||||
requestId: number | string
|
||||
poiId: string
|
||||
@@ -253,6 +243,7 @@ const props = withDefaults(defineProps<{
|
||||
layerModeToggleTop?: string
|
||||
toolsTop?: string
|
||||
tools?: string[]
|
||||
cameraView?: CameraViewMode
|
||||
searchVariant?: 'default' | 'home'
|
||||
showVoiceIcon?: boolean
|
||||
showModeRow?: boolean
|
||||
@@ -260,9 +251,9 @@ const props = withDefaults(defineProps<{
|
||||
showZoomControls?: boolean
|
||||
showLocationControl?: boolean
|
||||
showMoreControl?: boolean
|
||||
toolsBottom?: string
|
||||
floorSide?: 'left' | 'right'
|
||||
zoomControlsTop?: string
|
||||
locationControlBottom?: string
|
||||
moreControlBottom?: string
|
||||
showSearch?: boolean
|
||||
showFloor?: boolean
|
||||
@@ -285,6 +276,7 @@ const props = withDefaults(defineProps<{
|
||||
showRoute?: boolean
|
||||
autoSwitchThresholdLow?: number
|
||||
autoSwitchThresholdHigh?: number
|
||||
outdoorNavPolylines?: OutdoorNavPolyline[]
|
||||
}>(), {
|
||||
searchText: '请输入地点进行搜索',
|
||||
activeMode: '3d',
|
||||
@@ -297,7 +289,9 @@ const props = withDefaults(defineProps<{
|
||||
floorMaxHeight: '',
|
||||
layerModeToggleTop: '154px',
|
||||
toolsTop: '406px',
|
||||
toolsBottom: '',
|
||||
tools: () => [] as string[],
|
||||
cameraView: 'reset',
|
||||
searchVariant: 'default',
|
||||
showVoiceIcon: false,
|
||||
showModeRow: true,
|
||||
@@ -307,7 +301,6 @@ const props = withDefaults(defineProps<{
|
||||
showMoreControl: false,
|
||||
floorSide: 'right',
|
||||
zoomControlsTop: '520px',
|
||||
locationControlBottom: '34px',
|
||||
moreControlBottom: '34px',
|
||||
showSearch: true,
|
||||
showFloor: true,
|
||||
@@ -329,7 +322,8 @@ const props = withDefaults(defineProps<{
|
||||
routePreview: null,
|
||||
showRoute: false,
|
||||
autoSwitchThresholdLow: 1.0,
|
||||
autoSwitchThresholdHigh: 1.3
|
||||
autoSwitchThresholdHigh: 1.3,
|
||||
outdoorNavPolylines: () => [] as OutdoorNavPolyline[]
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -344,8 +338,23 @@ const emit = defineEmits<{
|
||||
selectionClear: []
|
||||
targetFocus: [result: TargetPoiFocusResult]
|
||||
autoSwitch: [event: { from: 'overview' | 'floor'; to: 'overview' | 'floor'; trigger: string; distance: number }]
|
||||
mapTap: [location: { latitude: number; longitude: number }]
|
||||
}>()
|
||||
|
||||
// 监听视角切换,循环切换:reset -> top -> oblique -> reset
|
||||
watch(() => props.cameraView, (view) => {
|
||||
if (props.mapType !== 'indoor') return
|
||||
indoorRendererRef.value?.disableAutoSwitchTemporarily?.(10000)
|
||||
|
||||
if (view === 'reset') {
|
||||
indoorRendererRef.value?.resetCamera?.()
|
||||
} else if (view === 'top') {
|
||||
indoorRendererRef.value?.setCameraPreset?.('top')
|
||||
} else if (view === 'oblique') {
|
||||
indoorRendererRef.value?.setCameraPreset?.('oblique')
|
||||
}
|
||||
})
|
||||
|
||||
const indoorRendererRef = ref<{
|
||||
switchFloor?: (floorId: string) => Promise<void> | void
|
||||
showOverview?: () => Promise<void> | void
|
||||
@@ -354,19 +363,30 @@ const indoorRendererRef = ref<{
|
||||
setCameraPreset?: (preset: 'top' | 'oblique') => void
|
||||
zoomCamera?: (direction: 'in' | 'out') => void
|
||||
clearSelection?: (shouldEmit?: boolean) => void
|
||||
clearRoute?: () => Promise<void> | void
|
||||
disableAutoSwitchTemporarily?: (durationMs: number) => void
|
||||
} | null>(null)
|
||||
|
||||
const floorLabels = computed(() => props.floors.map((floor) => floor.label))
|
||||
const indoorModelSource = computed(() => props.indoorModelSource)
|
||||
// #ifdef H5
|
||||
const useSgsMapRenderer = computed(() => isSgsSdkMode())
|
||||
const effectiveIndoorModelSource = computed(() => props.indoorModelSource)
|
||||
// #endif
|
||||
const showIndoorRightControls = computed(() => (
|
||||
props.mapType === 'indoor' && props.indoorView !== 'overview'
|
||||
))
|
||||
|
||||
const activeFloorId = computed(() => props.normalizeFloorId(props.activeFloor))
|
||||
const activeFloorId = computed(() => {
|
||||
const matchedFloor = props.floors.find((floor) => (
|
||||
floor.id === props.activeFloor || floor.label === props.activeFloor
|
||||
))
|
||||
if (matchedFloor) return matchedFloor.id
|
||||
|
||||
const normalizedFloorId = props.normalizeFloorId(props.activeFloor)
|
||||
const normalizedFloor = props.floors.find((floor) => (
|
||||
floor.id === normalizedFloorId || floor.label === normalizedFloorId
|
||||
))
|
||||
return normalizedFloor?.id || normalizedFloorId
|
||||
})
|
||||
|
||||
const searchFieldStyle = computed(() => ({
|
||||
top: props.searchTop
|
||||
@@ -386,17 +406,15 @@ const modeRowStyle = computed(() => ({
|
||||
}))
|
||||
|
||||
const toolStackStyle = computed(() => ({
|
||||
top: props.toolsTop
|
||||
...(props.toolsTop ? { top: props.toolsTop } : {}),
|
||||
...(props.toolsBottom ? { bottom: props.toolsBottom } : {}),
|
||||
left: '18px'
|
||||
}))
|
||||
|
||||
const zoomControlsStyle = computed(() => ({
|
||||
top: props.zoomControlsTop
|
||||
}))
|
||||
|
||||
const locationControlStyle = computed(() => ({
|
||||
bottom: props.locationControlBottom
|
||||
}))
|
||||
|
||||
const moreControlStyle = computed(() => ({
|
||||
bottom: props.moreControlBottom
|
||||
}))
|
||||
@@ -469,11 +487,6 @@ const handleToolClick = (tool: string) => {
|
||||
emit('toolClick', tool)
|
||||
}
|
||||
|
||||
const handleResetView = () => {
|
||||
indoorRendererRef.value?.resetCamera?.()
|
||||
emit('toolClick', '重置')
|
||||
}
|
||||
|
||||
const handleZoomClick = (direction: 'in' | 'out') => {
|
||||
indoorRendererRef.value?.zoomCamera?.(direction)
|
||||
emit('toolClick', direction === 'in' ? '放大' : '缩小')
|
||||
@@ -502,16 +515,23 @@ const handleTargetFocus = (result: TargetPoiFocusResult) => {
|
||||
emit('targetFocus', result)
|
||||
}
|
||||
|
||||
const handleSdkFloorChange = (floorId: string) => {
|
||||
const floor = props.floors.find((item) => item.id === floorId)
|
||||
if (floor) {
|
||||
emit('floorChange', floor.label)
|
||||
}
|
||||
}
|
||||
|
||||
const handleAutoSwitch = (event: { from: 'overview' | 'floor'; to: 'overview' | 'floor'; trigger: string; distance: number }) => {
|
||||
emit('autoSwitch', event)
|
||||
}
|
||||
|
||||
const handleMapTap = (location: { latitude?: number; longitude?: number }) => {
|
||||
const { latitude, longitude } = location
|
||||
if (latitude !== undefined && longitude !== undefined) {
|
||||
emit('mapTap', { latitude, longitude })
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露 clearRoute 方法供父组件显式调用路线清除
|
||||
defineExpose({
|
||||
clearRoute: () => {
|
||||
indoorRendererRef.value?.clearRoute?.()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -958,7 +978,7 @@ const handleAutoSwitch = (event: { from: 'overview' | 'floor'; to: 'overview' |
|
||||
|
||||
.tool-stack {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
left: 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
<view v-if="startPoint && endPoint" class="route-options">
|
||||
<view class="route-options-header">
|
||||
<text class="route-options-title">线路预览</text>
|
||||
<text class="route-options-title">{{ routeOptionsTitle }}</text>
|
||||
<text class="route-options-note">偏好选择</text>
|
||||
</view>
|
||||
<view class="route-option-list">
|
||||
@@ -84,7 +84,7 @@
|
||||
</view>
|
||||
|
||||
<view v-if="loading || error || summary" class="panel-status">
|
||||
<text v-if="loading" class="panel-status-text">线路预览生成中</text>
|
||||
<text v-if="loading" class="panel-status-text">{{ loadingText }}</text>
|
||||
<text v-else-if="error" class="panel-status-text error">{{ error }}</text>
|
||||
<text v-else class="panel-status-text">{{ summary }}</text>
|
||||
</view>
|
||||
@@ -136,6 +136,7 @@ const props = withDefaults(defineProps<{
|
||||
pickerLoading?: boolean
|
||||
pickerError?: string
|
||||
pickerEmptyText?: string
|
||||
routeReady?: boolean
|
||||
}>(), {
|
||||
visible: true,
|
||||
startPoint: null,
|
||||
@@ -147,7 +148,8 @@ const props = withDefaults(defineProps<{
|
||||
summary: '',
|
||||
pickerLoading: false,
|
||||
pickerError: '',
|
||||
pickerEmptyText: '暂无匹配地点'
|
||||
pickerEmptyText: '暂无匹配地点',
|
||||
routeReady: false
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -194,7 +196,15 @@ const canViewRoute = computed(() => Boolean(
|
||||
const canPrimaryAction = computed(() => canViewRoute.value && !props.error)
|
||||
|
||||
const primaryActionText = computed(() => (
|
||||
props.hasRoutePreview ? '模拟导览' : '线路预览'
|
||||
props.hasRoutePreview
|
||||
? '开始导览'
|
||||
: (props.routeReady ? '查看路线' : '查看位置关系')
|
||||
))
|
||||
|
||||
const routeOptionsTitle = computed(() => '室内导览')
|
||||
|
||||
const loadingText = computed(() => (
|
||||
props.routeReady ? '正在规划室内导览路线' : '正在生成室内位置关系'
|
||||
))
|
||||
|
||||
const collapsedSummary = computed(() => {
|
||||
@@ -207,26 +217,32 @@ const collapsedSummary = computed(() => {
|
||||
|
||||
const routeOptions = computed(() => {
|
||||
const unavailable = Boolean(props.error)
|
||||
const summaryText = props.summary || '选择后查看位置关系'
|
||||
const summaryText = props.summary || (props.routeReady ? '选择后规划真实路线' : '选择后查看位置关系')
|
||||
return [
|
||||
{
|
||||
id: 'recommended',
|
||||
title: '推荐路线',
|
||||
meta: unavailable ? '当前为路线示意状态' : props.hasRoutePreview ? summaryText : '优先展示位置关系',
|
||||
meta: unavailable
|
||||
? '当前暂不可用'
|
||||
: props.hasRoutePreview
|
||||
? summaryText
|
||||
: props.routeReady
|
||||
? '调用 SGS SDK 路线规划'
|
||||
: '查看起点终点位置关系',
|
||||
active: !unavailable,
|
||||
disabled: unavailable
|
||||
},
|
||||
{
|
||||
id: 'stairs',
|
||||
title: '少走楼梯',
|
||||
meta: '偏好预览,待路线数据验证',
|
||||
meta: props.routeReady ? '偏好能力待接入' : '偏好预览,待路线数据验证',
|
||||
active: false,
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
id: 'elevator',
|
||||
title: '电梯优先',
|
||||
meta: '偏好预览,待路线数据验证',
|
||||
meta: props.routeReady ? '偏好能力待接入' : '偏好预览,待路线数据验证',
|
||||
active: false,
|
||||
disabled: true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user