Files
frontend-miniapp/src/components/navigation/ArrivalPanel.vue
lyf 64dfb480dc
Some checks failed
CI / verify (push) Has been cancelled
实现来馆第三方地图跳转
2026-07-09 00:07:47 +08:00

440 lines
9.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view
v-if="visible"
class="arrival-scrim"
@tap="emit('close')"
></view>
<view
v-if="visible"
class="arrival-panel"
@tap.stop
>
<view class="arrival-handle"></view>
<view class="arrival-header">
<view class="arrival-title-group">
<text class="arrival-kicker">来馆</text>
<text class="arrival-title">选择第三方地图检索目标</text>
</view>
<view class="arrival-close" @tap="emit('close')">
<text class="arrival-close-text">×</text>
</view>
</view>
<view class="arrival-notes">
<text class="arrival-note">公交站停车场信息以第三方地图实时结果为准</text>
<text class="arrival-note">暂未接入停车余位</text>
</view>
<view class="arrival-section">
<text class="arrival-section-title">导航到场馆</text>
<view
v-for="target in venueTargets"
:key="target.id"
class="arrival-target-row"
@tap="handleTargetSelect(target)"
>
<view class="arrival-target-dot"></view>
<view class="arrival-target-copy">
<text class="arrival-target-title">{{ target.title }}</text>
<text class="arrival-target-keyword">{{ target.keyword }}</text>
</view>
<text class="arrival-target-action">选择地图</text>
</view>
</view>
<view class="arrival-section">
<text class="arrival-section-title">推荐公共交通</text>
<view
v-for="target in transitTargets"
:key="target.id"
class="arrival-target-row"
@tap="handleTargetSelect(target)"
>
<view class="arrival-target-dot"></view>
<view class="arrival-target-copy">
<text class="arrival-target-title">{{ target.title }}</text>
<text class="arrival-target-keyword">{{ target.keyword }}</text>
</view>
<text class="arrival-target-action">选择地图</text>
</view>
</view>
<view class="arrival-section">
<text class="arrival-section-title">实时查询</text>
<view
v-for="target in realtimeTargets"
:key="target.id"
class="arrival-target-row"
@tap="handleTargetSelect(target)"
>
<view class="arrival-target-dot"></view>
<view class="arrival-target-copy">
<text class="arrival-target-title">{{ target.title }}</text>
<text class="arrival-target-keyword">{{ target.keyword }}</text>
</view>
<text class="arrival-target-action">选择地图</text>
</view>
</view>
</view>
<view
v-if="selectedTarget"
class="provider-scrim"
@tap="closeProviderSheet"
></view>
<view
v-if="selectedTarget"
class="provider-sheet"
@tap.stop
>
<view class="provider-header">
<view class="provider-title-group">
<text class="provider-kicker">选择地图</text>
<text class="provider-title">{{ selectedTarget.title }}</text>
</view>
<view class="provider-close" @tap="closeProviderSheet">
<text class="provider-close-text">×</text>
</view>
</view>
<view
v-for="provider in mapProviders"
:key="provider.provider"
class="provider-row"
@tap="handleProviderSelect(provider)"
>
<view class="provider-mark">
<text class="provider-mark-text">{{ provider.label.slice(0, 1) }}</text>
</view>
<text class="provider-label">{{ provider.label }}</text>
<text class="provider-action">打开</text>
</view>
</view>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import {
ARRIVAL_REALTIME_TARGETS,
ARRIVAL_TRANSIT_TARGETS,
ARRIVAL_VENUE_TARGETS,
type ArrivalSearchTarget
} from '@/data/arrivalSearchTargets'
import {
openThirdPartyMapSearch,
THIRD_PARTY_MAP_PROVIDERS,
type ThirdPartyMapProviderOption
} from '@/services/ThirdPartyMapSearchService'
const props = withDefaults(defineProps<{
visible?: boolean
}>(), {
visible: false
})
const emit = defineEmits<{
close: []
}>()
const venueTargets = computed(() => ARRIVAL_VENUE_TARGETS)
const transitTargets = computed(() => ARRIVAL_TRANSIT_TARGETS)
const realtimeTargets = computed(() => ARRIVAL_REALTIME_TARGETS)
const mapProviders = computed(() => THIRD_PARTY_MAP_PROVIDERS)
const selectedTarget = ref<ArrivalSearchTarget | null>(null)
const handleTargetSelect = (target: ArrivalSearchTarget) => {
selectedTarget.value = target
}
const closeProviderSheet = () => {
selectedTarget.value = null
}
const handleProviderSelect = (provider: ThirdPartyMapProviderOption) => {
const target = selectedTarget.value
if (!target) return
openThirdPartyMapSearch(provider.provider, {
keyword: target.keyword,
region: target.region
})
}
watch(
() => props.visible,
(visible) => {
if (!visible) {
selectedTarget.value = null
}
}
)
</script>
<style scoped lang="scss">
.arrival-scrim {
position: fixed;
inset: 0;
z-index: 2090;
background: rgba(0, 0, 0, 0.18);
}
.arrival-panel {
position: fixed;
left: 50%;
bottom: calc(env(safe-area-inset-bottom) + 16px);
z-index: 2100;
width: min(430px, calc(100vw - 24px));
max-height: min(72vh, 620px);
padding: 10px 14px 16px;
overflow-y: auto;
box-sizing: border-box;
background: #ffffff;
border: 1px solid rgba(31, 35, 41, 0.08);
border-radius: 8px;
box-shadow: 0 10px 34px rgba(31, 35, 41, 0.18);
transform: translateX(-50%);
}
.arrival-handle {
width: 38px;
height: 4px;
margin: 0 auto 12px;
background: #d7d9cf;
border-radius: 2px;
}
.arrival-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
}
.arrival-title-group {
display: flex;
flex-direction: column;
gap: 3px;
}
.arrival-kicker {
font-size: 12px;
line-height: 16px;
font-weight: 600;
color: #696962;
}
.arrival-title {
font-size: 18px;
line-height: 25px;
font-weight: 700;
color: #262421;
}
.arrival-close {
width: 32px;
height: 32px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
background: #f3f3f3;
}
.arrival-close-text {
font-size: 22px;
line-height: 26px;
color: #424754;
}
.arrival-notes {
margin-top: 12px;
padding: 10px 12px;
display: flex;
flex-direction: column;
gap: 4px;
background: #f5f5ed;
border-radius: 8px;
}
.arrival-note {
font-size: 12px;
line-height: 17px;
color: #424754;
}
.arrival-section {
margin-top: 16px;
}
.arrival-section-title {
display: block;
margin-bottom: 8px;
font-size: 14px;
line-height: 20px;
font-weight: 700;
color: #262421;
}
.arrival-target-row {
min-height: 58px;
display: flex;
align-items: center;
gap: 10px;
padding: 10px 0;
border-top: 1px solid #eceee7;
}
.arrival-section-title + .arrival-target-row {
border-top: 0;
}
.arrival-target-dot {
width: 9px;
height: 9px;
flex-shrink: 0;
background: #e0e100;
border: 2px solid #262421;
border-radius: 50%;
}
.arrival-target-copy {
min-width: 0;
flex: 1;
display: flex;
flex-direction: column;
gap: 3px;
}
.arrival-target-title {
font-size: 15px;
line-height: 21px;
font-weight: 600;
color: #262421;
}
.arrival-target-keyword {
font-size: 12px;
line-height: 17px;
color: #696962;
}
.arrival-target-action {
flex-shrink: 0;
font-size: 12px;
line-height: 17px;
font-weight: 600;
color: #262421;
}
.provider-scrim {
position: fixed;
inset: 0;
z-index: 2190;
background: rgba(0, 0, 0, 0.28);
}
.provider-sheet {
position: fixed;
left: 50%;
bottom: calc(env(safe-area-inset-bottom) + 12px);
z-index: 2200;
width: min(430px, calc(100vw - 24px));
padding: 14px;
box-sizing: border-box;
background: #ffffff;
border-radius: 8px;
box-shadow: 0 12px 36px rgba(31, 35, 41, 0.22);
transform: translateX(-50%);
}
.provider-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.provider-title-group {
min-width: 0;
display: flex;
flex-direction: column;
gap: 3px;
}
.provider-kicker {
font-size: 12px;
line-height: 16px;
font-weight: 600;
color: #696962;
}
.provider-title {
font-size: 16px;
line-height: 22px;
font-weight: 700;
color: #262421;
}
.provider-close {
width: 32px;
height: 32px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
background: #f3f3f3;
border-radius: 8px;
}
.provider-close-text {
font-size: 22px;
line-height: 26px;
color: #424754;
}
.provider-row {
min-height: 52px;
display: flex;
align-items: center;
gap: 10px;
padding: 9px 0;
border-top: 1px solid #eceee7;
}
.provider-mark {
width: 30px;
height: 30px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
background: #262421;
border-radius: 8px;
}
.provider-mark-text {
font-size: 13px;
line-height: 18px;
font-weight: 700;
color: #e0e100;
}
.provider-label {
flex: 1;
font-size: 15px;
line-height: 21px;
font-weight: 600;
color: #262421;
}
.provider-action {
flex-shrink: 0;
font-size: 12px;
line-height: 17px;
font-weight: 600;
color: #696962;
}
</style>