优化导览页面面板与楼层交互
This commit is contained in:
@@ -1158,7 +1158,7 @@ const getMultiFloorVerticalGap = (items: MultiFloorModelItem[]) => {
|
|||||||
const maxFloorHeight = Math.max(...items.map((item) => item.size.y), 1)
|
const maxFloorHeight = Math.max(...items.map((item) => item.size.y), 1)
|
||||||
const maxFootprint = Math.max(...items.map((item) => Math.max(item.size.x, item.size.z)), 1)
|
const maxFootprint = Math.max(...items.map((item) => Math.max(item.size.x, item.size.z)), 1)
|
||||||
|
|
||||||
return Math.max(maxFloorHeight * 3.2, maxFootprint * 0.2, 42)
|
return Math.max(maxFloorHeight * 5.2, maxFootprint * 0.32, 68)
|
||||||
}
|
}
|
||||||
|
|
||||||
const applyMultiFloorLayout = (items: MultiFloorModelItem[]) => {
|
const applyMultiFloorLayout = (items: MultiFloorModelItem[]) => {
|
||||||
@@ -1332,6 +1332,42 @@ const setCameraPreset = (preset: CameraPreset) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const zoomCamera = (direction: 'in' | 'out') => {
|
||||||
|
if (!camera || !controls) return
|
||||||
|
|
||||||
|
const offset = camera.position.clone().sub(controls.target)
|
||||||
|
const currentDistance = offset.length()
|
||||||
|
const zoomFactor = direction === 'in' ? 0.72 : 1.28
|
||||||
|
const minDistance = controls.minDistance || 2
|
||||||
|
const maxDistance = controls.maxDistance || 1200
|
||||||
|
const nextDistance = Math.min(maxDistance, Math.max(minDistance, currentDistance * zoomFactor))
|
||||||
|
|
||||||
|
if (!Number.isFinite(nextDistance) || nextDistance <= 0) return
|
||||||
|
|
||||||
|
offset.setLength(nextDistance)
|
||||||
|
camera.position.copy(controls.target).add(offset)
|
||||||
|
controls.update()
|
||||||
|
|
||||||
|
if (direction === 'in' && activeView.value === 'overview' && !isLoading.value && !isAutoSwitchLocked) {
|
||||||
|
isAutoSwitchLocked = true
|
||||||
|
lastAutoSwitchTime = Date.now()
|
||||||
|
|
||||||
|
void runAutoSwitchLoad(
|
||||||
|
{
|
||||||
|
from: 'overview',
|
||||||
|
to: 'floor',
|
||||||
|
trigger: 'zoom-in',
|
||||||
|
distance: nextDistance
|
||||||
|
},
|
||||||
|
() => loadFloor(currentFloor.value || props.initialFloorId || 'L1'),
|
||||||
|
'单楼层模型加载失败'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handleControlChange()
|
||||||
|
}
|
||||||
|
|
||||||
const loadOverview = async () => {
|
const loadOverview = async () => {
|
||||||
const packageData = renderPackage.value
|
const packageData = renderPackage.value
|
||||||
if (!packageData || !scene) return
|
if (!packageData || !scene) return
|
||||||
@@ -1447,36 +1483,6 @@ const loadMultiFloor = async () => {
|
|||||||
activeView.value = 'multi'
|
activeView.value = 'multi'
|
||||||
clearSceneData()
|
clearSceneData()
|
||||||
setProgress(18, '正在加载多层展示模型...')
|
setProgress(18, '正在加载多层展示模型...')
|
||||||
const packageData = renderPackage.value
|
|
||||||
const allFloorsShareOverview = Boolean(packageData)
|
|
||||||
&& floorIndex.value.every((floor) => floor.sharedModelAsset && floor.modelUrl === packageData!.overviewModelUrl)
|
|
||||||
|
|
||||||
if (allFloorsShareOverview && packageData) {
|
|
||||||
if (!attachCachedSharedModel(packageData.overviewModelUrl, 'GuideMultiFloorModel', loadToken)) {
|
|
||||||
const gltf = await loadModel(packageData.overviewModelUrl, '正在加载多层展示模型', loadToken)
|
|
||||||
assertCurrentModelLoad(loadToken, gltf.scene)
|
|
||||||
|
|
||||||
const targetScene = scene
|
|
||||||
if (!targetScene) {
|
|
||||||
disposeObject(gltf.scene)
|
|
||||||
throw createStaleModelLoadError()
|
|
||||||
}
|
|
||||||
|
|
||||||
activeModel = gltf.scene
|
|
||||||
activeModel.name = 'GuideMultiFloorModel'
|
|
||||||
prepareModel(activeModel)
|
|
||||||
applyModelVisibilityForView(activeModel, 'multi')
|
|
||||||
cachedOverviewModel = activeModel
|
|
||||||
cachedSharedModelUrl = packageData.overviewModelUrl
|
|
||||||
targetScene.add(activeModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!activeModel) throw createStaleModelLoadError()
|
|
||||||
fitCameraToObject(activeModel)
|
|
||||||
refreshPoiVisibilityByDistance()
|
|
||||||
renderRoutePreview()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const group = new THREE.Group()
|
const group = new THREE.Group()
|
||||||
group.name = 'GuideMultiFloorModel'
|
group.name = 'GuideMultiFloorModel'
|
||||||
@@ -2310,6 +2316,7 @@ defineExpose({
|
|||||||
showMultiFloor,
|
showMultiFloor,
|
||||||
resetCamera,
|
resetCamera,
|
||||||
setCameraPreset,
|
setCameraPreset,
|
||||||
|
zoomCamera,
|
||||||
focusTargetPoi: (request: TargetPoiFocusRequest) => {
|
focusTargetPoi: (request: TargetPoiFocusRequest) => {
|
||||||
queueTargetFocus(request)
|
queueTargetFocus(request)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,6 +29,8 @@
|
|||||||
:target-focus-distance-factor="targetFocusDistanceFactor"
|
:target-focus-distance-factor="targetFocusDistanceFactor"
|
||||||
:route-preview="routePreview"
|
:route-preview="routePreview"
|
||||||
:show-route="showRoute"
|
:show-route="showRoute"
|
||||||
|
:auto-switch-threshold-low="autoSwitchThresholdLow"
|
||||||
|
:auto-switch-threshold-high="autoSwitchThresholdHigh"
|
||||||
@floor-change="handleThreeFloorChange"
|
@floor-change="handleThreeFloorChange"
|
||||||
@poi-click="handlePoiClick"
|
@poi-click="handlePoiClick"
|
||||||
@selection-clear="handleSelectionClear"
|
@selection-clear="handleSelectionClear"
|
||||||
@@ -138,7 +140,12 @@
|
|||||||
:class="`side-${floorSide}`"
|
:class="`side-${floorSide}`"
|
||||||
:style="floorSwitcherStyle"
|
:style="floorSwitcherStyle"
|
||||||
>
|
>
|
||||||
<view v-if="showFloorHeader" class="floor-header">
|
<view
|
||||||
|
v-if="showFloorHeader"
|
||||||
|
class="floor-header"
|
||||||
|
:class="{ active: layerMode === 'multi' }"
|
||||||
|
@tap.stop="handleFloorHeaderTap"
|
||||||
|
>
|
||||||
<text class="floor-header-icon">▱</text>
|
<text class="floor-header-icon">▱</text>
|
||||||
<text class="floor-header-label">楼层</text>
|
<text class="floor-header-label">楼层</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -146,7 +153,7 @@
|
|||||||
v-for="floor in floorLabels"
|
v-for="floor in floorLabels"
|
||||||
:key="floor"
|
:key="floor"
|
||||||
class="floor-item"
|
class="floor-item"
|
||||||
:class="{ active: activeFloor === floor }"
|
:class="{ active: activeFloor === floor && layerMode !== 'multi' }"
|
||||||
@tap="handleFloorChange(floor)"
|
@tap="handleFloorChange(floor)"
|
||||||
>
|
>
|
||||||
<text class="floor-label">{{ floor }}</text>
|
<text class="floor-label">{{ floor }}</text>
|
||||||
@@ -242,6 +249,7 @@ const props = withDefaults(defineProps<{
|
|||||||
layerMode?: LayerDisplayMode
|
layerMode?: LayerDisplayMode
|
||||||
searchTop?: string
|
searchTop?: string
|
||||||
floorTop?: string
|
floorTop?: string
|
||||||
|
floorMaxHeight?: string
|
||||||
layerModeToggleTop?: string
|
layerModeToggleTop?: string
|
||||||
toolsTop?: string
|
toolsTop?: string
|
||||||
tools?: string[]
|
tools?: string[]
|
||||||
@@ -275,6 +283,8 @@ const props = withDefaults(defineProps<{
|
|||||||
targetFocusDistanceFactor?: number
|
targetFocusDistanceFactor?: number
|
||||||
routePreview?: GuideRouteResult | null
|
routePreview?: GuideRouteResult | null
|
||||||
showRoute?: boolean
|
showRoute?: boolean
|
||||||
|
autoSwitchThresholdLow?: number
|
||||||
|
autoSwitchThresholdHigh?: number
|
||||||
}>(), {
|
}>(), {
|
||||||
searchText: '请输入地点进行搜索',
|
searchText: '请输入地点进行搜索',
|
||||||
activeMode: '3d',
|
activeMode: '3d',
|
||||||
@@ -284,6 +294,7 @@ const props = withDefaults(defineProps<{
|
|||||||
layerMode: 'single',
|
layerMode: 'single',
|
||||||
searchTop: '16px',
|
searchTop: '16px',
|
||||||
floorTop: '164px',
|
floorTop: '164px',
|
||||||
|
floorMaxHeight: '',
|
||||||
layerModeToggleTop: '154px',
|
layerModeToggleTop: '154px',
|
||||||
toolsTop: '406px',
|
toolsTop: '406px',
|
||||||
tools: () => [] as string[],
|
tools: () => [] as string[],
|
||||||
@@ -316,7 +327,9 @@ const props = withDefaults(defineProps<{
|
|||||||
touchGestureMode: 'orbit',
|
touchGestureMode: 'orbit',
|
||||||
targetFocusDistanceFactor: 0.22,
|
targetFocusDistanceFactor: 0.22,
|
||||||
routePreview: null,
|
routePreview: null,
|
||||||
showRoute: false
|
showRoute: false,
|
||||||
|
autoSwitchThresholdLow: 1.0,
|
||||||
|
autoSwitchThresholdHigh: 1.3
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -339,6 +352,7 @@ const indoorRendererRef = ref<{
|
|||||||
showMultiFloor?: () => Promise<void> | void
|
showMultiFloor?: () => Promise<void> | void
|
||||||
resetCamera?: () => void
|
resetCamera?: () => void
|
||||||
setCameraPreset?: (preset: 'top' | 'oblique') => void
|
setCameraPreset?: (preset: 'top' | 'oblique') => void
|
||||||
|
zoomCamera?: (direction: 'in' | 'out') => void
|
||||||
clearSelection?: (shouldEmit?: boolean) => void
|
clearSelection?: (shouldEmit?: boolean) => void
|
||||||
disableAutoSwitchTemporarily?: (durationMs: number) => void
|
disableAutoSwitchTemporarily?: (durationMs: number) => void
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
@@ -359,7 +373,8 @@ const searchFieldStyle = computed(() => ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
const floorSwitcherStyle = computed(() => ({
|
const floorSwitcherStyle = computed(() => ({
|
||||||
top: props.floorTop
|
top: props.floorTop,
|
||||||
|
...(props.floorMaxHeight ? { maxHeight: props.floorMaxHeight } : {})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const layerModeToggleStyle = computed(() => ({
|
const layerModeToggleStyle = computed(() => ({
|
||||||
@@ -432,6 +447,10 @@ const handleLayerModeToggle = () => {
|
|||||||
handleLayerModeChange(nextLayerMode.value)
|
handleLayerModeChange(nextLayerMode.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleFloorHeaderTap = () => {
|
||||||
|
handleLayerModeChange('multi')
|
||||||
|
}
|
||||||
|
|
||||||
const handleToolClick = (tool: string) => {
|
const handleToolClick = (tool: string) => {
|
||||||
if (props.mapType === 'indoor') {
|
if (props.mapType === 'indoor') {
|
||||||
indoorRendererRef.value?.disableAutoSwitchTemporarily?.(10000)
|
indoorRendererRef.value?.disableAutoSwitchTemporarily?.(10000)
|
||||||
@@ -456,6 +475,7 @@ const handleResetView = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleZoomClick = (direction: 'in' | 'out') => {
|
const handleZoomClick = (direction: 'in' | 'out') => {
|
||||||
|
indoorRendererRef.value?.zoomCamera?.(direction)
|
||||||
emit('toolClick', direction === 'in' ? '放大' : '缩小')
|
emit('toolClick', direction === 'in' ? '放大' : '缩小')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -797,7 +817,9 @@ const handleAutoSwitch = (event: { from: 'overview' | 'floor'; to: 'overview' |
|
|||||||
max-height: calc(100vh - 260px);
|
max-height: calc(100vh - 260px);
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow: visible;
|
overflow-y: auto;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
scrollbar-width: none;
|
||||||
background: rgba(255, 255, 255, 0.84);
|
background: rgba(255, 255, 255, 0.84);
|
||||||
border: 1px solid #dde5df;
|
border: 1px solid #dde5df;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -805,6 +827,10 @@ const handleAutoSwitch = (event: { from: 'overview' | 'floor'; to: 'overview' |
|
|||||||
z-index: 35;
|
z-index: 35;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.floor-switcher::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.floor-switcher.side-right {
|
.floor-switcher.side-right {
|
||||||
right: 16px;
|
right: 16px;
|
||||||
}
|
}
|
||||||
@@ -814,6 +840,7 @@ const handleAutoSwitch = (event: { from: 'overview' | 'floor'; to: 'overview' |
|
|||||||
}
|
}
|
||||||
|
|
||||||
.floor-header {
|
.floor-header {
|
||||||
|
position: relative;
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
padding: 7px 4px 6px;
|
padding: 7px 4px 6px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -826,6 +853,10 @@ const handleAutoSwitch = (event: { from: 'overview' | 'floor'; to: 'overview' |
|
|||||||
border-radius: 8px 8px 0 0;
|
border-radius: 8px 8px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.floor-header.active {
|
||||||
|
background: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
.floor-header::after {
|
.floor-header::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -842,12 +873,20 @@ const handleAutoSwitch = (event: { from: 'overview' | 'floor'; to: 'overview' |
|
|||||||
color: #151713;
|
color: #151713;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.floor-header.active .floor-header-icon {
|
||||||
|
color: var(--museum-accent);
|
||||||
|
}
|
||||||
|
|
||||||
.floor-header-label {
|
.floor-header-label {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
color: #545861;
|
color: #545861;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.floor-header.active .floor-header-label {
|
||||||
|
color: var(--museum-accent);
|
||||||
|
}
|
||||||
|
|
||||||
.layer-mode-toggle {
|
.layer-mode-toggle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 16px;
|
right: 16px;
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<view v-if="visible" class="route-planner-panel" :class="{ collapsed: isCollapsed }">
|
<view
|
||||||
|
v-if="visible"
|
||||||
|
class="route-planner-panel"
|
||||||
|
:class="{ collapsed: isCollapsed }"
|
||||||
|
@touchstart.stop="handlePanelTouchStart"
|
||||||
|
@touchmove.stop="handlePanelTouchMove"
|
||||||
|
@touchend.stop="handlePanelTouchEnd"
|
||||||
|
@mousedown.stop="handlePanelMouseStart"
|
||||||
|
@mousemove.stop="handlePanelMouseMove"
|
||||||
|
@mouseup.stop="handlePanelMouseEnd"
|
||||||
|
>
|
||||||
<view class="panel-handle" @tap.stop="toggleCollapsed"></view>
|
<view class="panel-handle" @tap.stop="toggleCollapsed"></view>
|
||||||
|
|
||||||
<view v-if="isCollapsed" class="panel-collapsed" @tap="expandPanel">
|
<view v-if="isCollapsed" class="panel-collapsed" @tap="expandPanel">
|
||||||
@@ -19,6 +29,9 @@
|
|||||||
<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">
|
||||||
|
<view class="panel-light-action" @tap="handleBack">
|
||||||
|
<text class="panel-light-action-text">返回</text>
|
||||||
|
</view>
|
||||||
<view class="panel-light-action" @tap="collapsePanel">
|
<view class="panel-light-action" @tap="collapsePanel">
|
||||||
<text class="panel-light-action-text">收起</text>
|
<text class="panel-light-action-text">收起</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -94,6 +107,7 @@
|
|||||||
:loading="pickerLoading"
|
:loading="pickerLoading"
|
||||||
:error="pickerError"
|
:error="pickerError"
|
||||||
:empty-text="pickerEmptyText"
|
:empty-text="pickerEmptyText"
|
||||||
|
close-text="返回"
|
||||||
@close="closePicker"
|
@close="closePicker"
|
||||||
@select="handlePointSelect"
|
@select="handlePointSelect"
|
||||||
@search="handlePickerSearch"
|
@search="handlePickerSearch"
|
||||||
@@ -148,10 +162,13 @@ const emit = defineEmits<{
|
|||||||
clear: []
|
clear: []
|
||||||
viewRoute: [payload: { startPoint: RoutePointOption; endPoint: RoutePointOption }]
|
viewRoute: [payload: { startPoint: RoutePointOption; endPoint: RoutePointOption }]
|
||||||
simulateGuide: []
|
simulateGuide: []
|
||||||
|
back: []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const pickerMode = ref<PickerMode>('')
|
const pickerMode = ref<PickerMode>('')
|
||||||
const isCollapsed = ref(false)
|
const isCollapsed = ref(false)
|
||||||
|
const panelTouchStartY = ref(0)
|
||||||
|
const panelTouchCurrentY = ref(0)
|
||||||
|
|
||||||
const pickerTitle = computed(() => (
|
const pickerTitle = computed(() => (
|
||||||
pickerMode.value === 'start' ? '选择起点' : '选择终点'
|
pickerMode.value === 'start' ? '选择起点' : '选择终点'
|
||||||
@@ -274,6 +291,15 @@ const handleClear = () => {
|
|||||||
emit('clear')
|
emit('clear')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
if (pickerMode.value) {
|
||||||
|
closePicker()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('back')
|
||||||
|
}
|
||||||
|
|
||||||
const collapsePanel = () => {
|
const collapsePanel = () => {
|
||||||
isCollapsed.value = true
|
isCollapsed.value = true
|
||||||
}
|
}
|
||||||
@@ -286,6 +312,56 @@ const toggleCollapsed = () => {
|
|||||||
isCollapsed.value = !isCollapsed.value
|
isCollapsed.value = !isCollapsed.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getGestureClientY = (event: TouchEvent | MouseEvent) => {
|
||||||
|
if ('changedTouches' in event) {
|
||||||
|
return event.changedTouches?.[0]?.clientY
|
||||||
|
?? event.touches?.[0]?.clientY
|
||||||
|
?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return event.clientY
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePanelTouchStart = (event: TouchEvent) => {
|
||||||
|
if (pickerMode.value) return
|
||||||
|
const clientY = getGestureClientY(event)
|
||||||
|
panelTouchStartY.value = clientY
|
||||||
|
panelTouchCurrentY.value = clientY
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePanelTouchMove = (event: TouchEvent) => {
|
||||||
|
if (pickerMode.value) return
|
||||||
|
panelTouchCurrentY.value = getGestureClientY(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePanelTouchEnd = (event: TouchEvent) => {
|
||||||
|
if (pickerMode.value) return
|
||||||
|
panelTouchCurrentY.value = getGestureClientY(event)
|
||||||
|
if (panelTouchCurrentY.value - panelTouchStartY.value > 48) {
|
||||||
|
collapsePanel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePanelMouseStart = (event: MouseEvent) => {
|
||||||
|
if (pickerMode.value) return
|
||||||
|
const clientY = getGestureClientY(event)
|
||||||
|
panelTouchStartY.value = clientY
|
||||||
|
panelTouchCurrentY.value = clientY
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePanelMouseMove = (event: MouseEvent) => {
|
||||||
|
if (pickerMode.value) return
|
||||||
|
panelTouchCurrentY.value = getGestureClientY(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePanelMouseEnd = (event: MouseEvent) => {
|
||||||
|
if (pickerMode.value) return
|
||||||
|
panelTouchCurrentY.value = getGestureClientY(event)
|
||||||
|
if (panelTouchCurrentY.value - panelTouchStartY.value > 48) {
|
||||||
|
collapsePanel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleViewRoute = () => {
|
const handleViewRoute = () => {
|
||||||
if (!canViewRoute.value || !props.startPoint || !props.endPoint) return
|
if (!canViewRoute.value || !props.startPoint || !props.endPoint) return
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<view class="picker-header">
|
<view class="picker-header">
|
||||||
<text class="picker-title">{{ title }}</text>
|
<text class="picker-title">{{ title }}</text>
|
||||||
<view class="picker-close" @tap="handleClose">
|
<view class="picker-close" @tap="handleClose">
|
||||||
<text class="picker-close-text">关闭</text>
|
<text class="picker-close-text">{{ closeText }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -72,6 +72,7 @@ const props = withDefaults(defineProps<{
|
|||||||
loading?: boolean
|
loading?: boolean
|
||||||
error?: string
|
error?: string
|
||||||
emptyText?: string
|
emptyText?: string
|
||||||
|
closeText?: string
|
||||||
}>(), {
|
}>(), {
|
||||||
visible: false,
|
visible: false,
|
||||||
title: '选择目标',
|
title: '选择目标',
|
||||||
@@ -81,7 +82,8 @@ const props = withDefaults(defineProps<{
|
|||||||
initialKeyword: '',
|
initialKeyword: '',
|
||||||
loading: false,
|
loading: false,
|
||||||
error: '',
|
error: '',
|
||||||
emptyText: '暂无匹配地点'
|
emptyText: '暂无匹配地点',
|
||||||
|
closeText: '关闭'
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -24,19 +24,20 @@
|
|||||||
:indoor-model-source="indoorModelSource"
|
:indoor-model-source="indoorModelSource"
|
||||||
:floors="guideFloors"
|
:floors="guideFloors"
|
||||||
:normalize-floor-id="normalizeGuideFloorId"
|
:normalize-floor-id="normalizeGuideFloorId"
|
||||||
indoor-initial-view="floor"
|
indoor-initial-view="overview"
|
||||||
:indoor-view="indoorView"
|
:indoor-view="indoorView"
|
||||||
:layer-mode="indoorLayerMode"
|
:layer-mode="indoorLayerMode"
|
||||||
:active-floor="activeGuideFloor"
|
:active-floor="activeGuideFloor"
|
||||||
layer-mode-toggle-top="154px"
|
layer-mode-toggle-top="154px"
|
||||||
floor-top="420px"
|
floor-top="clamp(128px, 18vh, 180px)"
|
||||||
floor-side="left"
|
floor-max-height="min(342px, calc(100vh - clamp(128px, 18vh, 180px) - 300px))"
|
||||||
|
floor-side="right"
|
||||||
show-floor-header
|
show-floor-header
|
||||||
:show-layer-mode-toggle="false"
|
:show-layer-mode-toggle="false"
|
||||||
:show-floor="is3DMode"
|
:show-floor="is3DMode"
|
||||||
tools-top="130px"
|
tools-top="calc(50% - 126px)"
|
||||||
show-zoom-controls
|
show-zoom-controls
|
||||||
zoom-controls-top="560px"
|
zoom-controls-top="calc(100vh - 284px)"
|
||||||
show-location-control
|
show-location-control
|
||||||
location-control-bottom="54px"
|
location-control-bottom="54px"
|
||||||
show-more-control
|
show-more-control
|
||||||
@@ -44,6 +45,8 @@
|
|||||||
:tools="guide3DTools"
|
:tools="guide3DTools"
|
||||||
:route-preview="activeRoutePreview"
|
:route-preview="activeRoutePreview"
|
||||||
:show-route="Boolean(activeRoutePreview)"
|
:show-route="Boolean(activeRoutePreview)"
|
||||||
|
:auto-switch-threshold-low="0.58"
|
||||||
|
:auto-switch-threshold-high="1.18"
|
||||||
@search-tap="handleGuideSearchTap"
|
@search-tap="handleGuideSearchTap"
|
||||||
@mode-change="handleModeChange"
|
@mode-change="handleModeChange"
|
||||||
@floor-change="handleFloorChange"
|
@floor-change="handleFloorChange"
|
||||||
@@ -107,8 +110,17 @@
|
|||||||
</template>
|
</template>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="showGuideMoreMenu && !showRoutePlanner" class="guide-more-popover">
|
<view
|
||||||
<view class="more-popover-handle"></view>
|
v-if="showGuideMoreMenu && !showRoutePlanner"
|
||||||
|
class="guide-more-popover"
|
||||||
|
@touchstart.stop="handleGuideMoreTouchStart"
|
||||||
|
@touchmove.stop="handleGuideMoreTouchMove"
|
||||||
|
@touchend.stop="handleGuideMoreTouchEnd"
|
||||||
|
@mousedown.stop="handleGuideMoreMouseStart"
|
||||||
|
@mousemove.stop="handleGuideMoreMouseMove"
|
||||||
|
@mouseup.stop="handleGuideMoreMouseEnd"
|
||||||
|
>
|
||||||
|
<view class="more-popover-handle" @tap.stop="closeGuideMoreMenu"></view>
|
||||||
<view class="more-popover-grid">
|
<view class="more-popover-grid">
|
||||||
<view class="more-action" @tap="handleMoreRouteGuide">
|
<view class="more-action" @tap="handleMoreRouteGuide">
|
||||||
<text class="more-action-title">线路预览</text>
|
<text class="more-action-title">线路预览</text>
|
||||||
@@ -122,14 +134,6 @@
|
|||||||
<text class="more-action-title">推荐入口</text>
|
<text class="more-action-title">推荐入口</text>
|
||||||
<text class="more-action-desc">入口参考</text>
|
<text class="more-action-desc">入口参考</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="more-action" @tap="handleEnter3DMode">
|
|
||||||
<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>
|
|
||||||
<text class="more-action-desc">切换楼层</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -153,6 +157,7 @@
|
|||||||
@clear="handleRouteClear"
|
@clear="handleRouteClear"
|
||||||
@view-route="handleRouteView"
|
@view-route="handleRouteView"
|
||||||
@simulate-guide="handleRouteSimulate"
|
@simulate-guide="handleRouteSimulate"
|
||||||
|
@back="handleRouteBack"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<view v-if="isSimulatingRoute && activeRoutePreview" class="route-sim-top-pill">
|
<view v-if="isSimulatingRoute && activeRoutePreview" class="route-sim-top-pill">
|
||||||
@@ -262,7 +267,7 @@ const initialTopTab = (): GuideTopTab => topTabFromHash() || 'guide'
|
|||||||
// 3D 模式状态
|
// 3D 模式状态
|
||||||
const is3DMode = ref(true)
|
const is3DMode = ref(true)
|
||||||
const guideOutdoorState = ref<'home' | 'entrance'>('home')
|
const guideOutdoorState = ref<'home' | 'entrance'>('home')
|
||||||
const indoorView = ref<GuideIndoorView>('floor')
|
const indoorView = ref<GuideIndoorView>('overview')
|
||||||
const activeGuideFloor = ref('1F')
|
const activeGuideFloor = ref('1F')
|
||||||
const selectedGuidePoi = ref<GuideRenderPoi | null>(null)
|
const selectedGuidePoi = ref<GuideRenderPoi | null>(null)
|
||||||
|
|
||||||
@@ -281,6 +286,8 @@ const activeRoutePreview = ref<GuideRouteResult | null>(null)
|
|||||||
const showGuideMoreMenu = ref(false)
|
const showGuideMoreMenu = ref(false)
|
||||||
const isSimulatingRoute = ref(false)
|
const isSimulatingRoute = ref(false)
|
||||||
const isPoiCardCollapsed = ref(false)
|
const isPoiCardCollapsed = ref(false)
|
||||||
|
const guideMoreTouchStartY = ref(0)
|
||||||
|
const guideMoreTouchCurrentY = ref(0)
|
||||||
|
|
||||||
const indoorNavAssetBaseUrl = guideUseCase.getAssetBaseUrl()
|
const indoorNavAssetBaseUrl = guideUseCase.getAssetBaseUrl()
|
||||||
const indoorModelSource = guideUseCase.getModelSource()
|
const indoorModelSource = guideUseCase.getModelSource()
|
||||||
@@ -530,8 +537,9 @@ const buildExplainHallItems = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleGuideSearchTap = () => {
|
const handleGuideSearchTap = () => {
|
||||||
uni.navigateTo({
|
uni.showToast({
|
||||||
url: '/pages/search/index'
|
title: '功能待开放',
|
||||||
|
icon: 'none'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -707,6 +715,19 @@ const handleRouteClear = () => {
|
|||||||
clearRoutePreviewState()
|
clearRoutePreviewState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleRouteBack = () => {
|
||||||
|
if (activeRoutePreview.value) {
|
||||||
|
activeRoutePreview.value = null
|
||||||
|
routePlanError.value = ''
|
||||||
|
isSimulatingRoute.value = false
|
||||||
|
showRoutePlanner.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showRoutePlanner.value = false
|
||||||
|
isSimulatingRoute.value = false
|
||||||
|
}
|
||||||
|
|
||||||
const handleRouteStartPointChange = (point: RoutePointOption | null) => {
|
const handleRouteStartPointChange = (point: RoutePointOption | null) => {
|
||||||
routeStartPoint.value = point
|
routeStartPoint.value = point
|
||||||
clearRoutePreviewState()
|
clearRoutePreviewState()
|
||||||
@@ -789,7 +810,7 @@ const handleRouteSimulate = () => {
|
|||||||
// 进入 3D 室内模式
|
// 进入 3D 室内模式
|
||||||
const handleEnter3DMode = () => {
|
const handleEnter3DMode = () => {
|
||||||
console.log('进入 3D 室内模式')
|
console.log('进入 3D 室内模式')
|
||||||
indoorView.value = 'floor'
|
indoorView.value = 'overview'
|
||||||
is3DMode.value = true
|
is3DMode.value = true
|
||||||
guideOutdoorState.value = 'home'
|
guideOutdoorState.value = 'home'
|
||||||
showGuideMoreMenu.value = false
|
showGuideMoreMenu.value = false
|
||||||
@@ -811,7 +832,7 @@ const handleModeChange = (mode: '2d' | '3d') => {
|
|||||||
isSimulatingRoute.value = false
|
isSimulatingRoute.value = false
|
||||||
clearRoutePreviewState()
|
clearRoutePreviewState()
|
||||||
} else {
|
} else {
|
||||||
indoorView.value = 'floor'
|
indoorView.value = 'overview'
|
||||||
if (!routePointOptions.value.length) {
|
if (!routePointOptions.value.length) {
|
||||||
void loadRouteTargets()
|
void loadRouteTargets()
|
||||||
}
|
}
|
||||||
@@ -841,6 +862,54 @@ const handleGuideMoreClick = () => {
|
|||||||
selectedGuidePoi.value = null
|
selectedGuidePoi.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const closeGuideMoreMenu = () => {
|
||||||
|
showGuideMoreMenu.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const getGestureClientY = (event: TouchEvent | MouseEvent) => {
|
||||||
|
if ('changedTouches' in event) {
|
||||||
|
return event.changedTouches?.[0]?.clientY
|
||||||
|
?? event.touches?.[0]?.clientY
|
||||||
|
?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return event.clientY
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGuideMoreTouchStart = (event: TouchEvent) => {
|
||||||
|
const clientY = getGestureClientY(event)
|
||||||
|
guideMoreTouchStartY.value = clientY
|
||||||
|
guideMoreTouchCurrentY.value = clientY
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGuideMoreTouchMove = (event: TouchEvent) => {
|
||||||
|
guideMoreTouchCurrentY.value = getGestureClientY(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGuideMoreTouchEnd = (event: TouchEvent) => {
|
||||||
|
guideMoreTouchCurrentY.value = getGestureClientY(event)
|
||||||
|
if (guideMoreTouchCurrentY.value - guideMoreTouchStartY.value > 48) {
|
||||||
|
closeGuideMoreMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGuideMoreMouseStart = (event: MouseEvent) => {
|
||||||
|
const clientY = getGestureClientY(event)
|
||||||
|
guideMoreTouchStartY.value = clientY
|
||||||
|
guideMoreTouchCurrentY.value = clientY
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGuideMoreMouseMove = (event: MouseEvent) => {
|
||||||
|
guideMoreTouchCurrentY.value = getGestureClientY(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGuideMoreMouseEnd = (event: MouseEvent) => {
|
||||||
|
guideMoreTouchCurrentY.value = getGestureClientY(event)
|
||||||
|
if (guideMoreTouchCurrentY.value - guideMoreTouchStartY.value > 48) {
|
||||||
|
closeGuideMoreMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleMoreRouteGuide = () => {
|
const handleMoreRouteGuide = () => {
|
||||||
openRoutePlanner()
|
openRoutePlanner()
|
||||||
}
|
}
|
||||||
@@ -864,13 +933,6 @@ const handleMoreRecommendedEntrance = () => {
|
|||||||
openOutdoorReference('entrance')
|
openOutdoorReference('entrance')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMoreToggleLayer = () => {
|
|
||||||
is3DMode.value = true
|
|
||||||
guideOutdoorState.value = 'home'
|
|
||||||
indoorView.value = indoorLayerMode.value === 'multi' ? 'floor' : 'multi'
|
|
||||||
showGuideMoreMenu.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const guideShellMode = computed(() => (is3DMode.value ? '3d' : '2d'))
|
const guideShellMode = computed(() => (is3DMode.value ? '3d' : '2d'))
|
||||||
const guideMapType = computed(() => (is3DMode.value ? 'indoor' : 'outdoor'))
|
const guideMapType = computed(() => (is3DMode.value ? 'indoor' : 'outdoor'))
|
||||||
const guideOutdoorVariant = computed(() => (
|
const guideOutdoorVariant = computed(() => (
|
||||||
|
|||||||
Reference in New Issue
Block a user