1005 lines
22 KiB
Vue
1005 lines
22 KiB
Vue
<template>
|
||
<view
|
||
v-if="visible"
|
||
class="outdoor-nav-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 v-if="isCollapsed" class="panel-collapsed" @tap="expandPanel">
|
||
<view class="panel-collapsed-copy">
|
||
<text class="panel-title">馆外导览</text>
|
||
<text class="panel-summary">{{ collapsedSummary }}</text>
|
||
</view>
|
||
<view class="panel-expand">
|
||
<text class="panel-expand-text">展开</text>
|
||
</view>
|
||
</view>
|
||
|
||
<template v-else>
|
||
<view class="panel-header">
|
||
<view class="panel-title-group">
|
||
<text class="panel-title">馆外导览</text>
|
||
<text v-if="routeSummary" class="panel-summary">{{ routeSummary }}</text>
|
||
</view>
|
||
<view class="panel-actions">
|
||
<view class="panel-light-action" @tap="handleBack">
|
||
<text class="panel-light-action-text">返回</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 起点选择区域 -->
|
||
<view class="start-point-section">
|
||
<view class="section-label">
|
||
<text class="section-label-text">起点</text>
|
||
</view>
|
||
<view class="start-point-buttons">
|
||
<view
|
||
class="start-btn gps-btn"
|
||
:class="{ active: effectiveStartMode === 'gps' }"
|
||
@tap="handleGpsStart"
|
||
>
|
||
<view class="start-btn-icon icon-gps">
|
||
<view class="icon-gps-dot"></view>
|
||
</view>
|
||
<text class="start-btn-text">GPS定位</text>
|
||
</view>
|
||
<view
|
||
class="start-btn manual-btn"
|
||
:class="{ active: effectiveStartMode === 'manual' }"
|
||
@tap="handleManualStart"
|
||
>
|
||
<view class="start-btn-icon icon-manual">
|
||
<view class="icon-manual-line"></view>
|
||
</view>
|
||
<text class="start-btn-text">手动选择</text>
|
||
</view>
|
||
</view>
|
||
<!-- 手动选择模式:显示搜索框 -->
|
||
<view v-if="effectiveStartMode === 'manual'" class="start-search-container">
|
||
<view class="start-search-input-wrapper">
|
||
<input
|
||
class="start-search-input"
|
||
type="text"
|
||
placeholder="输入起点地址或地名"
|
||
v-model="startSearchQuery"
|
||
@input="handleStartSearchInput"
|
||
/>
|
||
<view v-if="isSearchingStart" class="start-search-loading">
|
||
<text class="loading-text">搜索中...</text>
|
||
</view>
|
||
</view>
|
||
<!-- 搜索结果列表 -->
|
||
<view v-if="startSearchResults.length > 0" class="start-search-results">
|
||
<view
|
||
v-for="(result, index) in startSearchResults"
|
||
:key="index"
|
||
class="search-result-item"
|
||
@tap="handleSelectSearchResult(result)"
|
||
>
|
||
<text class="result-title">{{ result.title }}</text>
|
||
<text class="result-address">{{ result.address }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-if="effectiveStartPoint" class="start-point-display">
|
||
<view class="point-dot start"></view>
|
||
<text class="point-name">{{ formatStartPointName() }}</text>
|
||
<view class="point-change" @tap="handleManualStart">
|
||
<text class="point-change-text">修改</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 出行方式选择 -->
|
||
<view class="travel-mode-section">
|
||
<text class="section-label-text">出行方式</text>
|
||
<view class="travel-mode-list">
|
||
<view
|
||
v-for="mode in travelModes"
|
||
:key="mode.type"
|
||
class="travel-mode-item"
|
||
:class="[{ active: selectedTravelMode === mode.type }, `mode-${mode.type}`]"
|
||
@tap="handleTravelModeChange(mode.type)"
|
||
>
|
||
<view class="travel-mode-icon"></view>
|
||
<text class="travel-mode-label">{{ mode.label }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 路线状态 -->
|
||
<view v-if="loading" class="panel-status">
|
||
<text class="panel-status-text">规划路线中...</text>
|
||
</view>
|
||
<view v-else-if="routeError" class="panel-status">
|
||
<text class="panel-status-text error">{{ routeError }}</text>
|
||
</view>
|
||
|
||
<!-- 操作按钮 -->
|
||
<view class="action-buttons">
|
||
<view
|
||
v-if="hasRoute"
|
||
class="nav-btn secondary"
|
||
@tap="handleClearRoute"
|
||
>
|
||
<text class="nav-btn-text">清除路线</text>
|
||
</view>
|
||
<view
|
||
class="nav-btn primary"
|
||
:class="{ disabled: !canNavigate }"
|
||
@tap="handleStartNavigation"
|
||
>
|
||
<text class="nav-btn-text">{{ navigateButtonText }}</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, ref, watch } from 'vue'
|
||
import { TRAVEL_MODE_CONFIG, type TravelMode } from '@/config/museum'
|
||
|
||
type StartMode = 'gps' | 'manual'
|
||
|
||
const props = withDefaults(defineProps<{
|
||
visible?: boolean
|
||
loading?: boolean
|
||
routeError?: string
|
||
routeDistance?: number
|
||
routeDuration?: number
|
||
selectingManualStart?: boolean
|
||
startPoint?: { latitude: number; longitude: number; name?: string } | null
|
||
startMode?: 'gps' | 'manual'
|
||
}>(), {
|
||
visible: false,
|
||
loading: false,
|
||
routeError: '',
|
||
routeDistance: 0,
|
||
routeDuration: 0,
|
||
selectingManualStart: false,
|
||
startPoint: null,
|
||
startMode: 'gps'
|
||
})
|
||
|
||
const emit = defineEmits<{
|
||
close: []
|
||
gpsStart: []
|
||
manualStartRequest: []
|
||
manualStartConfirm: [location: { latitude: number; longitude: number }]
|
||
manualStartCancel: []
|
||
travelModeChange: [mode: TravelMode]
|
||
startNavigation: []
|
||
clearRoute: []
|
||
back: []
|
||
}>()
|
||
|
||
const isCollapsed = ref(false)
|
||
const localStartMode = ref<StartMode>('gps')
|
||
const manualStartPoint = ref<{ latitude: number; longitude: number; name?: string } | null>(null)
|
||
const selectedTravelMode = ref<TravelMode>('walking')
|
||
const panelTouchStartY = ref(0)
|
||
const panelTouchCurrentY = ref(0)
|
||
|
||
// 起点搜索相关状态
|
||
const startSearchQuery = ref('')
|
||
const startSearchResults = ref<Array<{ title: string; address: string; latitude: number; longitude: number }>>([])
|
||
const isSearchingStart = ref(false)
|
||
|
||
const travelModes = Object.values(TRAVEL_MODE_CONFIG)
|
||
|
||
// 使用外部传入的 startPoint,兼容本地状态
|
||
const effectiveStartPoint = computed(() => {
|
||
return props.startPoint ?? manualStartPoint.value
|
||
})
|
||
|
||
// 外部传入的 startMode 优先级更高
|
||
const effectiveStartMode = computed(() => {
|
||
if (props.startPoint) return 'manual'
|
||
return props.startMode ?? localStartMode.value
|
||
})
|
||
|
||
const hasRoute = computed(() => {
|
||
return effectiveStartPoint.value !== null && !props.loading && !props.routeError
|
||
})
|
||
|
||
const canNavigate = computed(() => {
|
||
if (props.loading) return false
|
||
if (props.routeError) return false
|
||
if (!effectiveStartPoint.value && effectiveStartMode.value !== 'gps') return false
|
||
return true
|
||
})
|
||
|
||
const navigateButtonText = computed(() => {
|
||
if (props.loading) return '规划中...'
|
||
if (hasRoute.value) return '打开地图'
|
||
return '规划路线'
|
||
})
|
||
|
||
const routeSummary = computed(() => {
|
||
if (props.routeDistance > 0 && props.routeDuration > 0) {
|
||
const distanceText = props.routeDistance >= 1000
|
||
? `${(props.routeDistance / 1000).toFixed(1)}公里`
|
||
: `${Math.round(props.routeDistance)}米`
|
||
const durationText = props.routeDuration >= 60
|
||
? `${Math.round(props.routeDuration / 60)}分钟`
|
||
: `${Math.round(props.routeDuration)}秒`
|
||
return `${distanceText} · 约${durationText}`
|
||
}
|
||
return ''
|
||
})
|
||
|
||
const collapsedSummary = computed(() => {
|
||
if (effectiveStartPoint.value) {
|
||
return formatStartPointName()
|
||
}
|
||
if (effectiveStartMode.value === 'gps') {
|
||
return '使用 GPS 定位'
|
||
}
|
||
return '馆外导览到馆'
|
||
})
|
||
|
||
const formatStartPointName = () => {
|
||
if (!effectiveStartPoint.value) return ''
|
||
if (effectiveStartPoint.value.name) {
|
||
return effectiveStartPoint.value.name
|
||
}
|
||
return `(${effectiveStartPoint.value.latitude.toFixed(5)}, ${effectiveStartPoint.value.longitude.toFixed(5)})`
|
||
}
|
||
|
||
watch(
|
||
() => props.visible,
|
||
(visible) => {
|
||
if (visible) {
|
||
isCollapsed.value = false
|
||
} else {
|
||
// 重置状态
|
||
localStartMode.value = 'gps'
|
||
manualStartPoint.value = null
|
||
selectedTravelMode.value = 'walking'
|
||
}
|
||
}
|
||
)
|
||
|
||
const handleGpsStart = () => {
|
||
localStartMode.value = 'gps'
|
||
manualStartPoint.value = null
|
||
emit('gpsStart')
|
||
}
|
||
|
||
// 搜索起点地址
|
||
const searchStartAddress = async (query: string) => {
|
||
if (!query.trim()) {
|
||
startSearchResults.value = []
|
||
return
|
||
}
|
||
|
||
isSearchingStart.value = true
|
||
try {
|
||
const { searchPoi } = await import('@/services/tencent/TencentMapService')
|
||
const result = await searchPoi(query, '深圳市', 1, 5)
|
||
if (result.status === 0 && result.data) {
|
||
startSearchResults.value = result.data.map(poi => ({
|
||
title: poi.title,
|
||
address: poi.address,
|
||
latitude: poi.location.lat,
|
||
longitude: poi.location.lng
|
||
}))
|
||
} else {
|
||
startSearchResults.value = []
|
||
}
|
||
} catch (error) {
|
||
console.error('搜索起点失败:', error)
|
||
startSearchResults.value = []
|
||
} finally {
|
||
isSearchingStart.value = false
|
||
}
|
||
}
|
||
|
||
// 处理搜索输入
|
||
const handleStartSearchInput = () => {
|
||
const query = startSearchQuery.value
|
||
if (query.length >= 2) {
|
||
void searchStartAddress(query)
|
||
} else {
|
||
startSearchResults.value = []
|
||
}
|
||
}
|
||
|
||
// 选择搜索结果作为起点
|
||
const handleSelectSearchResult = (result: { title: string; address: string; latitude: number; longitude: number }) => {
|
||
manualStartPoint.value = {
|
||
latitude: result.latitude,
|
||
longitude: result.longitude,
|
||
name: result.title
|
||
}
|
||
startSearchQuery.value = ''
|
||
startSearchResults.value = []
|
||
emit('manualStartConfirm', { latitude: result.latitude, longitude: result.longitude })
|
||
}
|
||
|
||
// 切换到手动选择模式
|
||
const handleManualStart = () => {
|
||
localStartMode.value = 'manual'
|
||
startSearchQuery.value = ''
|
||
startSearchResults.value = []
|
||
emit('manualStartRequest')
|
||
}
|
||
|
||
const handleTravelModeChange = (mode: TravelMode) => {
|
||
selectedTravelMode.value = mode
|
||
emit('travelModeChange', mode)
|
||
}
|
||
|
||
const handleStartNavigation = () => {
|
||
if (!canNavigate.value) return
|
||
emit('startNavigation')
|
||
}
|
||
|
||
const handleClearRoute = () => {
|
||
manualStartPoint.value = null
|
||
localStartMode.value = 'gps'
|
||
emit('clearRoute')
|
||
}
|
||
|
||
const handleBack = () => {
|
||
emit('back')
|
||
}
|
||
|
||
const expandPanel = () => {
|
||
isCollapsed.value = false
|
||
}
|
||
|
||
const toggleCollapsed = () => {
|
||
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) => {
|
||
const clientY = getGestureClientY(event)
|
||
panelTouchStartY.value = clientY
|
||
panelTouchCurrentY.value = clientY
|
||
}
|
||
|
||
const handlePanelTouchMove = (event: TouchEvent) => {
|
||
panelTouchCurrentY.value = getGestureClientY(event)
|
||
}
|
||
|
||
const handlePanelTouchEnd = (event: TouchEvent) => {
|
||
panelTouchCurrentY.value = getGestureClientY(event)
|
||
if (panelTouchCurrentY.value - panelTouchStartY.value > 48) {
|
||
emit('close')
|
||
}
|
||
}
|
||
|
||
const handlePanelMouseStart = (event: MouseEvent) => {
|
||
const clientY = getGestureClientY(event)
|
||
panelTouchStartY.value = clientY
|
||
panelTouchCurrentY.value = clientY
|
||
}
|
||
|
||
const handlePanelMouseMove = (event: MouseEvent) => {
|
||
panelTouchCurrentY.value = getGestureClientY(event)
|
||
}
|
||
|
||
const handlePanelMouseEnd = (event: MouseEvent) => {
|
||
panelTouchCurrentY.value = getGestureClientY(event)
|
||
if (panelTouchCurrentY.value - panelTouchStartY.value > 48) {
|
||
emit('close')
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.outdoor-nav-panel {
|
||
position: absolute;
|
||
left: 15px;
|
||
right: 15px;
|
||
bottom: calc(env(safe-area-inset-bottom) + 106px);
|
||
padding: 12px 15px 15px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
box-sizing: border-box;
|
||
background: var(--museum-bg-surface);
|
||
border: 1px solid #d9d9d9;
|
||
border-radius: var(--radius-card);
|
||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
|
||
z-index: 1003;
|
||
}
|
||
|
||
.outdoor-nav-panel.collapsed {
|
||
padding: 10px 12px 12px;
|
||
}
|
||
|
||
.panel-handle {
|
||
width: 38px;
|
||
height: 4px;
|
||
margin: 0 auto 12px;
|
||
background: #d9d9d9;
|
||
border-radius: 999px;
|
||
}
|
||
|
||
.panel-collapsed {
|
||
min-height: 44px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
}
|
||
|
||
.panel-collapsed-copy {
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 3px;
|
||
}
|
||
|
||
.panel-expand {
|
||
flex: 0 0 auto;
|
||
height: 30px;
|
||
padding: 0 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: #000000;
|
||
border-radius: var(--radius-button);
|
||
}
|
||
|
||
.panel-expand-text {
|
||
font-size: 12px;
|
||
line-height: 16px;
|
||
font-weight: 500;
|
||
color: var(--museum-accent);
|
||
}
|
||
|
||
.panel-header {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
}
|
||
|
||
.panel-title-group {
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.panel-title {
|
||
font-size: 18px;
|
||
line-height: 25px;
|
||
font-weight: 500;
|
||
color: var(--museum-text-primary);
|
||
}
|
||
|
||
.panel-summary {
|
||
font-size: 12px;
|
||
line-height: 18px;
|
||
color: var(--museum-text-tertiary);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.panel-actions {
|
||
flex: 0 0 auto;
|
||
display: flex;
|
||
gap: 6px;
|
||
}
|
||
|
||
.panel-light-action {
|
||
flex: 0 0 auto;
|
||
height: 28px;
|
||
padding: 0 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-sizing: border-box;
|
||
background: var(--museum-bg-surface);
|
||
border: 1px solid #d9d9d9;
|
||
border-radius: var(--radius-button);
|
||
}
|
||
|
||
.panel-light-action-text {
|
||
font-size: 12px;
|
||
line-height: 16px;
|
||
font-weight: 500;
|
||
color: var(--museum-text-secondary);
|
||
}
|
||
|
||
.start-point-section {
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.section-label {
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.section-label-text {
|
||
font-size: 13px;
|
||
line-height: 18px;
|
||
font-weight: 600;
|
||
color: var(--museum-text-secondary);
|
||
}
|
||
|
||
.start-point-buttons {
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.start-btn {
|
||
flex: 1;
|
||
height: 44px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
box-sizing: border-box;
|
||
background: var(--museum-bg-surface);
|
||
border: 1px solid #d9d9d9;
|
||
border-radius: var(--radius-button);
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.start-btn.active {
|
||
background: #000000;
|
||
border-color: #000000;
|
||
}
|
||
|
||
.start-btn-icon {
|
||
position: relative;
|
||
width: 18px;
|
||
height: 18px;
|
||
flex-shrink: 0;
|
||
color: var(--museum-text-primary);
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.start-btn.active .start-btn-icon {
|
||
color: var(--museum-accent);
|
||
}
|
||
|
||
.icon-gps {
|
||
border: 1.8px solid currentColor;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.icon-gps::before,
|
||
.icon-gps::after {
|
||
content: '';
|
||
position: absolute;
|
||
background: currentColor;
|
||
}
|
||
|
||
.icon-gps::before {
|
||
left: 8px;
|
||
top: -4px;
|
||
width: 1.8px;
|
||
height: 24px;
|
||
}
|
||
|
||
.icon-gps::after {
|
||
left: -4px;
|
||
top: 8px;
|
||
width: 24px;
|
||
height: 1.8px;
|
||
}
|
||
|
||
.icon-gps-dot {
|
||
position: absolute;
|
||
left: 5px;
|
||
top: 5px;
|
||
width: 6px;
|
||
height: 6px;
|
||
background: currentColor;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.icon-manual {
|
||
border-left: 2px solid currentColor;
|
||
border-bottom: 2px solid currentColor;
|
||
transform: rotate(-45deg);
|
||
}
|
||
|
||
.icon-manual::before {
|
||
content: '';
|
||
position: absolute;
|
||
right: 0;
|
||
bottom: -2px;
|
||
width: 7px;
|
||
height: 7px;
|
||
border-right: 2px solid currentColor;
|
||
border-bottom: 2px solid currentColor;
|
||
}
|
||
|
||
.icon-manual-line {
|
||
position: absolute;
|
||
left: 3px;
|
||
bottom: 5px;
|
||
width: 15px;
|
||
height: 2px;
|
||
background: currentColor;
|
||
}
|
||
|
||
.start-btn-text {
|
||
font-size: 14px;
|
||
line-height: 20px;
|
||
font-weight: 500;
|
||
color: var(--museum-text-primary);
|
||
}
|
||
|
||
.start-btn.active .start-btn-text {
|
||
color: var(--museum-accent);
|
||
}
|
||
|
||
.start-hint {
|
||
margin-top: 8px;
|
||
padding: 10px 12px;
|
||
background: var(--museum-bg-light);
|
||
border: 1px solid var(--museum-border-light);
|
||
border-radius: var(--radius-button);
|
||
}
|
||
|
||
.start-hint-text {
|
||
font-size: 12px;
|
||
line-height: 17px;
|
||
color: var(--museum-text-disabled);
|
||
}
|
||
|
||
.start-search-container {
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.start-search-input-wrapper {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.start-search-input {
|
||
width: 100%;
|
||
height: 40px;
|
||
padding: 0 12px;
|
||
font-size: 14px;
|
||
color: var(--museum-text-primary);
|
||
background: var(--museum-bg-cream);
|
||
border: 1px solid #adb0b4;
|
||
border-radius: var(--radius-button);
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.start-search-loading {
|
||
position: absolute;
|
||
right: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.loading-text {
|
||
font-size: 12px;
|
||
color: var(--museum-text-disabled);
|
||
}
|
||
|
||
.start-search-results {
|
||
margin-top: 8px;
|
||
background: var(--museum-bg-surface);
|
||
border: 1px solid #d9d9d9;
|
||
border-radius: var(--radius-button);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.search-result-item {
|
||
padding: 12px;
|
||
border-bottom: 1px solid #d9d9d9;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.search-result-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.result-title {
|
||
display: block;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: var(--museum-text-primary);
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.result-address {
|
||
display: block;
|
||
font-size: 12px;
|
||
color: var(--museum-text-disabled);
|
||
}
|
||
|
||
.start-point-display {
|
||
position: relative;
|
||
margin-top: 8px;
|
||
padding: 10px 10px 10px 30px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
background: var(--museum-bg-light);
|
||
border: 1px solid #d9d9d9;
|
||
border-radius: var(--radius-button);
|
||
}
|
||
|
||
.point-dot {
|
||
position: absolute;
|
||
left: 12px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.point-dot.start {
|
||
background: #2c854f;
|
||
}
|
||
|
||
.point-name {
|
||
flex: 1;
|
||
font-size: 14px;
|
||
line-height: 20px;
|
||
font-weight: 600;
|
||
color: var(--museum-text-primary);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.point-change {
|
||
flex: 0 0 auto;
|
||
height: 26px;
|
||
padding: 0 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--museum-bg-surface);
|
||
border: 1px solid #d9d9d9;
|
||
border-radius: var(--radius-button);
|
||
}
|
||
|
||
.point-change-text {
|
||
font-size: 12px;
|
||
line-height: 16px;
|
||
font-weight: 500;
|
||
color: var(--museum-text-secondary);
|
||
}
|
||
|
||
.travel-mode-section {
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.travel-mode-list {
|
||
margin-top: 8px;
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 10px;
|
||
}
|
||
|
||
.travel-mode-item {
|
||
height: 52px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 4px;
|
||
box-sizing: border-box;
|
||
background: var(--museum-bg-surface);
|
||
border: 1px solid #d9d9d9;
|
||
border-radius: var(--radius-button);
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.travel-mode-item.active {
|
||
background: #000000;
|
||
border-color: #000000;
|
||
}
|
||
|
||
.travel-mode-icon {
|
||
position: relative;
|
||
width: 22px;
|
||
height: 18px;
|
||
color: var(--museum-text-primary);
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.travel-mode-item.active .travel-mode-icon {
|
||
color: var(--museum-accent);
|
||
}
|
||
|
||
.mode-walking .travel-mode-icon::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 8px;
|
||
top: 0;
|
||
width: 6px;
|
||
height: 6px;
|
||
background: currentColor;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.mode-walking .travel-mode-icon::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 6px;
|
||
top: 7px;
|
||
width: 10px;
|
||
height: 10px;
|
||
border-left: 2px solid currentColor;
|
||
border-bottom: 2px solid currentColor;
|
||
transform: skewX(-18deg);
|
||
}
|
||
|
||
.mode-driving .travel-mode-icon {
|
||
border: 2px solid currentColor;
|
||
border-radius: 4px 4px 3px 3px;
|
||
}
|
||
|
||
.mode-driving .travel-mode-icon::before,
|
||
.mode-driving .travel-mode-icon::after {
|
||
content: '';
|
||
position: absolute;
|
||
bottom: -5px;
|
||
width: 5px;
|
||
height: 5px;
|
||
background: currentColor;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.mode-driving .travel-mode-icon::before {
|
||
left: 2px;
|
||
}
|
||
|
||
.mode-driving .travel-mode-icon::after {
|
||
right: 2px;
|
||
}
|
||
|
||
.mode-transit .travel-mode-icon {
|
||
border: 2px solid currentColor;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.mode-transit .travel-mode-icon::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 4px;
|
||
right: 4px;
|
||
top: 4px;
|
||
height: 5px;
|
||
border-top: 2px solid currentColor;
|
||
border-bottom: 2px solid currentColor;
|
||
}
|
||
|
||
.travel-mode-label {
|
||
font-size: 12px;
|
||
line-height: 16px;
|
||
font-weight: 500;
|
||
color: var(--museum-text-primary);
|
||
}
|
||
|
||
.travel-mode-item.active .travel-mode-label {
|
||
color: var(--museum-accent);
|
||
}
|
||
|
||
.panel-status {
|
||
margin-top: 10px;
|
||
min-height: 20px;
|
||
}
|
||
|
||
.panel-status-text {
|
||
font-size: 12px;
|
||
line-height: 18px;
|
||
color: var(--museum-text-tertiary);
|
||
}
|
||
|
||
.panel-status-text.error {
|
||
color: #e73333;
|
||
}
|
||
|
||
.action-buttons {
|
||
margin-top: 14px;
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.nav-btn {
|
||
flex: 1;
|
||
height: 42px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: var(--radius-button);
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.nav-btn.primary {
|
||
flex: 2;
|
||
background: #000000;
|
||
}
|
||
|
||
.nav-btn.secondary {
|
||
flex: 1;
|
||
background: var(--museum-bg-surface);
|
||
border: 1px solid #000000;
|
||
}
|
||
|
||
.nav-btn.disabled {
|
||
background: #000000;
|
||
border-color: #000000;
|
||
opacity: 0.45;
|
||
}
|
||
|
||
.nav-btn-text {
|
||
font-size: 14px;
|
||
line-height: 19px;
|
||
font-weight: 500;
|
||
color: var(--museum-accent);
|
||
}
|
||
|
||
.nav-btn.secondary .nav-btn-text {
|
||
color: var(--museum-text-primary);
|
||
}
|
||
|
||
.nav-btn.disabled .nav-btn-text {
|
||
color: var(--museum-accent);
|
||
}
|
||
|
||
.manual-select-hint {
|
||
margin-top: 12px;
|
||
padding: 12px 14px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
background: #fff9e6;
|
||
border: 1px solid #ffe58a;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.manual-select-hint-text {
|
||
font-size: 13px;
|
||
line-height: 18px;
|
||
color: #7a6108;
|
||
}
|
||
|
||
.manual-select-cancel {
|
||
flex: 0 0 auto;
|
||
height: 26px;
|
||
padding: 0 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: #ffffff;
|
||
border: 1px solid #d7dad3;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.manual-select-cancel-text {
|
||
font-size: 12px;
|
||
line-height: 16px;
|
||
font-weight: 500;
|
||
color: #545861;
|
||
}
|
||
</style>
|