简化来馆面板并优化地图点位展示
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
@tap="collapsePanel"
|
||||
></view>
|
||||
<view
|
||||
ref="arrivalPanelRef"
|
||||
v-if="visible"
|
||||
class="arrival-panel"
|
||||
:class="{ collapsed: isCollapsed }"
|
||||
@@ -13,7 +14,7 @@
|
||||
<view v-if="isCollapsed" class="arrival-collapsed" @tap="expandPanel">
|
||||
<view class="arrival-collapsed-copy">
|
||||
<text class="arrival-collapsed-title">来馆</text>
|
||||
<text class="arrival-collapsed-summary">公交 / 地铁 / 停车</text>
|
||||
<text class="arrival-collapsed-summary">{{ selectedTarget?.title || activeTypeLabel }}</text>
|
||||
</view>
|
||||
<view class="arrival-collapsed-action">
|
||||
<text class="arrival-collapsed-action-text">展开</text>
|
||||
@@ -21,83 +22,64 @@
|
||||
</view>
|
||||
|
||||
<template v-else>
|
||||
<view class="arrival-handle" @tap="collapsePanel"></view>
|
||||
<view class="arrival-handle" @tap="collapsePanel"></view>
|
||||
|
||||
<view class="arrival-header">
|
||||
<view class="arrival-title-group">
|
||||
<text class="arrival-kicker">来馆</text>
|
||||
<text class="arrival-title">选择第三方地图检索目标</text>
|
||||
</view>
|
||||
<view class="arrival-collapse" @tap="collapsePanel">
|
||||
<text class="arrival-collapse-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 class="arrival-header">
|
||||
<view class="arrival-title-group">
|
||||
<text class="arrival-kicker">来馆</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 class="arrival-collapse" @tap="collapsePanel">
|
||||
<text class="arrival-collapse-text">收起</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 class="arrival-tabs">
|
||||
<view
|
||||
v-for="option in typeOptions"
|
||||
:key="option.type"
|
||||
class="arrival-tab"
|
||||
:class="{ active: option.type === activeType }"
|
||||
@tap="handleTypeChange(option.type)"
|
||||
>
|
||||
<text class="arrival-tab-text">{{ option.label }}</text>
|
||||
</view>
|
||||
<text class="arrival-target-action">选择地图</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="arrival-list">
|
||||
<view
|
||||
v-for="target in targets"
|
||||
:key="target.id"
|
||||
class="arrival-target-row"
|
||||
:class="{ active: target.id === selectedTargetId }"
|
||||
@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-subtitle">{{ target.subtitle }}</text>
|
||||
<text v-if="target.description" class="arrival-target-desc">{{ target.description }}</text>
|
||||
</view>
|
||||
<text v-if="target.id === selectedTargetId" class="arrival-target-state">已选</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="arrival-primary"
|
||||
:class="{ disabled: !selectedTarget }"
|
||||
@tap="handleNavigateTap"
|
||||
>
|
||||
<text class="arrival-primary-text">第三方导航</text>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="selectedTarget"
|
||||
v-if="providerSheetVisible && selectedTarget"
|
||||
class="provider-scrim"
|
||||
@tap="closeProviderSheet"
|
||||
></view>
|
||||
<view
|
||||
v-if="selectedTarget"
|
||||
v-if="providerSheetVisible && selectedTarget"
|
||||
class="provider-sheet"
|
||||
@tap.stop
|
||||
>
|
||||
@@ -126,12 +108,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
|
||||
import {
|
||||
ARRIVAL_REALTIME_TARGETS,
|
||||
ARRIVAL_TRANSIT_TARGETS,
|
||||
ARRIVAL_VENUE_TARGETS,
|
||||
type ArrivalSearchTarget
|
||||
ARRIVAL_TARGET_TYPES,
|
||||
type ArrivalSearchTarget,
|
||||
type ArrivalTargetType
|
||||
} from '@/data/arrivalSearchTargets'
|
||||
import {
|
||||
openThirdPartyMapSearch,
|
||||
@@ -141,25 +122,44 @@ import {
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
visible?: boolean
|
||||
defaultCollapsed?: boolean
|
||||
activeType: ArrivalTargetType
|
||||
targets?: ArrivalSearchTarget[]
|
||||
selectedTargetId?: string
|
||||
selectedTarget?: ArrivalSearchTarget | null
|
||||
}>(), {
|
||||
visible: false,
|
||||
defaultCollapsed: false
|
||||
targets: () => [] as ArrivalSearchTarget[],
|
||||
selectedTargetId: '',
|
||||
selectedTarget: null
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:activeType': [type: ArrivalTargetType]
|
||||
selectTarget: [target: ArrivalSearchTarget]
|
||||
collapsedChange: [collapsed: boolean]
|
||||
layoutChange: [layout: { height: number; collapsed: boolean }]
|
||||
}>()
|
||||
|
||||
const venueTargets = computed(() => ARRIVAL_VENUE_TARGETS)
|
||||
const transitTargets = computed(() => ARRIVAL_TRANSIT_TARGETS)
|
||||
const realtimeTargets = computed(() => ARRIVAL_REALTIME_TARGETS)
|
||||
const typeOptions = computed(() => ARRIVAL_TARGET_TYPES)
|
||||
const mapProviders = computed(() => THIRD_PARTY_MAP_PROVIDERS)
|
||||
const selectedTarget = ref<ArrivalSearchTarget | null>(null)
|
||||
const arrivalPanelRef = ref<unknown>(null)
|
||||
const isCollapsed = ref(false)
|
||||
const providerSheetVisible = ref(false)
|
||||
let resizeObserver: ResizeObserver | null = null
|
||||
|
||||
const activeTypeLabel = computed(() => (
|
||||
typeOptions.value.find((option) => option.type === props.activeType)?.label || '来馆'
|
||||
))
|
||||
|
||||
const handleTypeChange = (type: ArrivalTargetType) => {
|
||||
if (type === props.activeType) return
|
||||
closeProviderSheet()
|
||||
emit('update:activeType', type)
|
||||
}
|
||||
|
||||
const handleTargetSelect = (target: ArrivalSearchTarget) => {
|
||||
selectedTarget.value = target
|
||||
closeProviderSheet()
|
||||
emit('selectTarget', target)
|
||||
}
|
||||
|
||||
const collapsePanel = () => {
|
||||
@@ -173,11 +173,76 @@ const expandPanel = () => {
|
||||
}
|
||||
|
||||
const closeProviderSheet = () => {
|
||||
selectedTarget.value = null
|
||||
providerSheetVisible.value = false
|
||||
}
|
||||
|
||||
const getArrivalPanelElement = () => {
|
||||
const rawRef = arrivalPanelRef.value
|
||||
if (rawRef instanceof HTMLElement) return rawRef
|
||||
|
||||
const maybeComponent = rawRef as { $el?: Element } | null
|
||||
if (maybeComponent?.$el instanceof HTMLElement) return maybeComponent.$el
|
||||
|
||||
if (typeof document === 'undefined') return null
|
||||
return document.querySelector<HTMLElement>('.arrival-panel')
|
||||
}
|
||||
|
||||
const emitLayoutChange = () => {
|
||||
if (!props.visible) {
|
||||
emit('layoutChange', { height: 0, collapsed: false })
|
||||
return
|
||||
}
|
||||
|
||||
void nextTick(() => {
|
||||
const panelElement = getArrivalPanelElement()
|
||||
emit('layoutChange', {
|
||||
height: panelElement?.getBoundingClientRect().height || 0,
|
||||
collapsed: isCollapsed.value
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const disconnectLayoutObserver = () => {
|
||||
resizeObserver?.disconnect()
|
||||
resizeObserver = null
|
||||
}
|
||||
|
||||
const startLayoutObserver = () => {
|
||||
if (typeof window === 'undefined' || typeof ResizeObserver === 'undefined') {
|
||||
emitLayoutChange()
|
||||
return
|
||||
}
|
||||
|
||||
void nextTick(() => {
|
||||
const panelElement = getArrivalPanelElement()
|
||||
if (!panelElement) {
|
||||
emitLayoutChange()
|
||||
return
|
||||
}
|
||||
|
||||
disconnectLayoutObserver()
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
emitLayoutChange()
|
||||
})
|
||||
resizeObserver.observe(panelElement)
|
||||
emitLayoutChange()
|
||||
})
|
||||
}
|
||||
|
||||
const handleNavigateTap = () => {
|
||||
if (!props.selectedTarget) {
|
||||
uni.showToast({
|
||||
title: '请先选择点位',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
providerSheetVisible.value = true
|
||||
}
|
||||
|
||||
const handleProviderSelect = (provider: ThirdPartyMapProviderOption) => {
|
||||
const target = selectedTarget.value
|
||||
const target = props.selectedTarget
|
||||
if (!target) return
|
||||
|
||||
openThirdPartyMapSearch(provider.provider, {
|
||||
@@ -190,16 +255,37 @@ watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
isCollapsed.value = props.defaultCollapsed
|
||||
emit('collapsedChange', props.defaultCollapsed)
|
||||
} else {
|
||||
selectedTarget.value = null
|
||||
isCollapsed.value = false
|
||||
emit('collapsedChange', false)
|
||||
startLayoutObserver()
|
||||
} else {
|
||||
closeProviderSheet()
|
||||
isCollapsed.value = false
|
||||
disconnectLayoutObserver()
|
||||
emit('collapsedChange', false)
|
||||
emit('layoutChange', { height: 0, collapsed: false })
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.selectedTargetId,
|
||||
() => {
|
||||
closeProviderSheet()
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => [props.targets.length, isCollapsed.value] as const,
|
||||
() => {
|
||||
startLayoutObserver()
|
||||
}
|
||||
)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
disconnectLayoutObserver()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
expandPanel
|
||||
})
|
||||
@@ -210,7 +296,7 @@ defineExpose({
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 2090;
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.arrival-panel {
|
||||
@@ -219,8 +305,8 @@ defineExpose({
|
||||
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;
|
||||
max-height: min(58vh, 520px);
|
||||
padding: 10px 14px 14px;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
background: #ffffff;
|
||||
@@ -346,12 +432,115 @@ defineExpose({
|
||||
color: #424754;
|
||||
}
|
||||
|
||||
.arrival-notes {
|
||||
margin-top: 12px;
|
||||
padding: 10px 12px;
|
||||
.arrival-tabs {
|
||||
height: 40px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 4px;
|
||||
margin-top: 14px;
|
||||
padding: 4px;
|
||||
box-sizing: border-box;
|
||||
background: #f3f3f3;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.arrival-tab {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.arrival-tab.active {
|
||||
background: #262421;
|
||||
}
|
||||
|
||||
.arrival-tab-text {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
font-weight: 700;
|
||||
color: #424754;
|
||||
}
|
||||
|
||||
.arrival-tab.active .arrival-tab-text {
|
||||
color: #e0e100;
|
||||
}
|
||||
|
||||
.arrival-list {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.arrival-target-row {
|
||||
min-height: 68px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 8px;
|
||||
border-top: 1px solid #eceee7;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.arrival-target-row:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.arrival-target-row.active {
|
||||
background: #f5f5ed;
|
||||
}
|
||||
|
||||
.arrival-target-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
flex-shrink: 0;
|
||||
background: #d7d9cf;
|
||||
border: 2px solid #696962;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.arrival-target-row.active .arrival-target-dot {
|
||||
background: #e0e100;
|
||||
border-color: #262421;
|
||||
}
|
||||
|
||||
.arrival-target-copy {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.arrival-target-title {
|
||||
font-size: 15px;
|
||||
line-height: 21px;
|
||||
font-weight: 700;
|
||||
color: #262421;
|
||||
}
|
||||
|
||||
.arrival-target-subtitle,
|
||||
.arrival-target-desc {
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
color: #696962;
|
||||
}
|
||||
|
||||
.arrival-target-desc {
|
||||
color: #424754;
|
||||
}
|
||||
|
||||
.arrival-target-state {
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
font-weight: 700;
|
||||
color: #262421;
|
||||
}
|
||||
|
||||
.arrival-notes {
|
||||
margin-top: 10px;
|
||||
padding: 9px 10px;
|
||||
background: #f5f5ed;
|
||||
border-radius: 8px;
|
||||
}
|
||||
@@ -362,68 +551,25 @@ defineExpose({
|
||||
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;
|
||||
.arrival-primary {
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 0;
|
||||
border-top: 1px solid #eceee7;
|
||||
justify-content: center;
|
||||
margin-top: 12px;
|
||||
background: #262421;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.arrival-section-title + .arrival-target-row {
|
||||
border-top: 0;
|
||||
.arrival-primary.disabled {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.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 {
|
||||
.arrival-primary-text {
|
||||
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;
|
||||
font-weight: 700;
|
||||
color: #e0e100;
|
||||
}
|
||||
|
||||
.provider-scrim {
|
||||
|
||||
Reference in New Issue
Block a user