feat: 新增 SGS SDK 数据适配器与腾讯地图服务
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
856
src/components/navigation/OutdoorNavigationPanel.vue
Normal file
856
src/components/navigation/OutdoorNavigationPanel.vue
Normal file
@@ -0,0 +1,856 @@
|
||||
<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"
|
||||
>
|
||||
<text class="start-btn-icon">📍</text>
|
||||
<text class="start-btn-text">GPS定位</text>
|
||||
</view>
|
||||
<view
|
||||
class="start-btn manual-btn"
|
||||
:class="{ active: effectiveStartMode === 'manual' }"
|
||||
@tap="handleManualStart"
|
||||
>
|
||||
<text class="start-btn-icon">✏️</text>
|
||||
<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 }"
|
||||
@tap="handleTravelModeChange(mode.type)"
|
||||
>
|
||||
<text class="travel-mode-icon">{{ mode.icon }}</text>
|
||||
<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: 12px;
|
||||
right: 12px;
|
||||
bottom: calc(env(safe-area-inset-bottom) + 30px);
|
||||
padding: 10px 14px 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.97);
|
||||
border: 1px solid #e5e6de;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 24px rgba(36, 49, 42, 0.12);
|
||||
z-index: 1003;
|
||||
}
|
||||
|
||||
.outdoor-nav-panel.collapsed {
|
||||
padding: 8px 10px 10px;
|
||||
}
|
||||
|
||||
.panel-handle {
|
||||
width: 38px;
|
||||
height: 4px;
|
||||
margin: 0 auto 10px;
|
||||
background: #d8dbd2;
|
||||
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 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #151713;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.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: 17px;
|
||||
line-height: 24px;
|
||||
font-weight: 700;
|
||||
color: #151713;
|
||||
}
|
||||
|
||||
.panel-summary {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #696962;
|
||||
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: #f5f7f2;
|
||||
border: 1px solid #e4e5df;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.panel-light-action-text {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
font-weight: 500;
|
||||
color: #545861;
|
||||
}
|
||||
|
||||
.start-point-section {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.section-label-text {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
font-weight: 600;
|
||||
color: #545861;
|
||||
}
|
||||
|
||||
.start-point-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.start-btn {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
box-sizing: border-box;
|
||||
background: #f6f7f4;
|
||||
border: 1px solid #e5e6de;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.start-btn.active {
|
||||
background: #151713;
|
||||
border-color: #151713;
|
||||
}
|
||||
|
||||
.start-btn-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.start-btn-text {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-weight: 500;
|
||||
color: #151713;
|
||||
}
|
||||
|
||||
.start-btn.active .start-btn-text {
|
||||
color: var(--museum-accent);
|
||||
}
|
||||
|
||||
.start-hint {
|
||||
margin-top: 8px;
|
||||
padding: 10px 12px;
|
||||
background: #f9fafb;
|
||||
border: 1px solid #ecede7;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.start-hint-text {
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
color: #8b8b84;
|
||||
}
|
||||
|
||||
.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;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e4e5df;
|
||||
border-radius: 8px;
|
||||
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: #8b8b84;
|
||||
}
|
||||
|
||||
.start-search-results {
|
||||
margin-top: 8px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e4e5df;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.search-result-item {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #f0f1ee;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-result-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #151713;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.result-address {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #8b8b84;
|
||||
}
|
||||
|
||||
.start-point-display {
|
||||
position: relative;
|
||||
margin-top: 8px;
|
||||
padding: 10px 10px 10px 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: #f6f7f4;
|
||||
border: 1px solid #e5e6de;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.point-dot {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.point-dot.start {
|
||||
background: #1fbf6b;
|
||||
}
|
||||
|
||||
.point-name {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-weight: 600;
|
||||
color: #1f2329;
|
||||
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: #ffffff;
|
||||
border: 1px solid #d7dad3;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.point-change-text {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
font-weight: 500;
|
||||
color: #545861;
|
||||
}
|
||||
|
||||
.travel-mode-section {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.travel-mode-list {
|
||||
margin-top: 8px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.travel-mode-item {
|
||||
height: 52px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
box-sizing: border-box;
|
||||
background: #f6f7f4;
|
||||
border: 1px solid #e5e6de;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.travel-mode-item.active {
|
||||
background: #151713;
|
||||
border-color: #151713;
|
||||
}
|
||||
|
||||
.travel-mode-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.travel-mode-label {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
font-weight: 500;
|
||||
color: #151713;
|
||||
}
|
||||
|
||||
.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: #696962;
|
||||
}
|
||||
|
||||
.panel-status-text.error {
|
||||
color: #b44b42;
|
||||
}
|
||||
|
||||
.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: 8px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.nav-btn.primary {
|
||||
flex: 2;
|
||||
background: #151713;
|
||||
}
|
||||
|
||||
.nav-btn.secondary {
|
||||
flex: 1;
|
||||
background: #f6f7f4;
|
||||
border: 1px solid #e5e6de;
|
||||
}
|
||||
|
||||
.nav-btn.disabled {
|
||||
background: #d8dbd2;
|
||||
border-color: #d8dbd2;
|
||||
}
|
||||
|
||||
.nav-btn-text {
|
||||
font-size: 14px;
|
||||
line-height: 19px;
|
||||
font-weight: 500;
|
||||
color: var(--museum-accent);
|
||||
}
|
||||
|
||||
.nav-btn.secondary .nav-btn-text {
|
||||
color: #545861;
|
||||
}
|
||||
|
||||
.nav-btn.disabled .nav-btn-text {
|
||||
color: #8b8b84;
|
||||
}
|
||||
|
||||
.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>
|
||||
Reference in New Issue
Block a user