Files
admin-web/src/modules/overseas-master-data/components/TransportRouteDialog.vue
T
2026-10-20 15:48:27 +08:00

82 lines
5.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.
<script setup lang="ts">
import InlineHelp from '@/shared/components/InlineHelp.vue'
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import { resolveRouteLocation, RouteAddressError, type RouteLocation } from '../routeLocations'
const open = defineModel<boolean>({ default: false })
const props = defineProps<{ origin: RouteLocation; destinations: RouteLocation[] }>()
const mapRevision = ref(0)
const destinationIndex = ref(0)
const selected = computed(() => props.destinations[destinationIndex.value])
const origin = ref(''), destination = ref(''), loading = ref(false), error = ref<ApiErrorDetails | null>(null)
let generation = 0
let cache = new Map<string, Promise<string>>()
function resolve(location: RouteLocation) {
const key = JSON.stringify(location)
if (!cache.has(key)) cache.set(key, resolveRouteLocation(location))
return cache.get(key)!
}
async function load() {
const current = ++generation
origin.value = ''; destination.value = ''; error.value = null; loading.value = false
if (!open.value) return
const valid = (location?: RouteLocation) => location && !['', '—', '-', '仓库名称待补充'].includes(location.label.trim())
if (!valid(props.origin) || !valid(selected.value)) {
error.value = { message: '缺少起点或目的地,请先补充地点或详细地址' }; return
}
loading.value = true
try {
const [start, end] = await Promise.all([resolve(props.origin), resolve(selected.value!)])
if (current === generation && open.value) { origin.value = start; destination.value = end; mapRevision.value++ }
} catch (cause) {
if (current === generation) error.value = cause instanceof RouteAddressError ? { message: cause.message } : getApiErrorDetails(cause, '仓库地址查询失败,请确认具有仓库查看权限后重试')
} finally { if (current === generation) loading.value = false }
}
function retry() { cache = new Map(); void load() }
watch([open, () => props.origin, () => props.destinations], () => {
destinationIndex.value = 0; cache = new Map(); void load()
}, { immediate: true })
watch(destinationIndex, load)
onBeforeUnmount(() => { generation++ })
const ready = computed(() => !loading.value && !error.value && Boolean(origin.value && destination.value))
const mapUrl = computed(() => `https://maps.google.com/maps?${new URLSearchParams({ saddr: origin.value, daddr: destination.value, dirflg: 'd', output: 'embed' })}`)
const externalUrl = computed(() => `https://www.google.com/maps/dir/?${new URLSearchParams({ api: '1', origin: origin.value, destination: destination.value, travelmode: 'driving' })}`)
const tooLong = computed(() => mapUrl.value.length > 2048 || externalUrl.value.length > 2048)
</script>
<template>
<AppDialog v-model="open" class="admin-density-dialog" title="查看路线"
width="min(1100px, 96vw)" align-center :show-confirm="false" cancel-text="关闭">
<section v-if="open" class="route-map" aria-label="起点至目的地路线">
<strong>{{ props.origin.label || '起点未填写' }} → {{ selected?.label || '目的地未填写' }}<InlineHelp title="路线地图说明"><p>按起点和目的地规划驾车路线,不显示实时位置或实际行驶轨迹。</p></InlineHelp></strong>
<el-select v-if="props.destinations.length > 1" v-model="destinationIndex" aria-label="选择目的仓">
<el-option v-for="(item, index) in props.destinations" :key="index" :label="item.label" :value="index" />
</el-select>
<p v-if="loading" role="status">正在查询仓库详细地址…</p>
<LoadErrorState v-else-if="error" title="无法加载路线" :message="error.message" @retry="retry"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
<p v-else-if="tooLong" role="status">地点信息过长,请核对起点和目的地地址后重试。</p>
<template v-else-if="ready"><div class="route-addresses"><div>起点地址:{{ origin }}</div><div>目的地地址:{{ destination }}</div></div>
<iframe :key="mapRevision" :src="mapUrl" :title="`${props.origin.label}至${selected?.label}路线地图`"
referrerpolicy="no-referrer" />
</template>
<div v-if="ready && !tooLong" class="route-help">
<el-button @click="mapRevision++">重新加载地图</el-button>
<el-link :href="externalUrl" target="_blank" rel="noopener noreferrer" type="primary">在新窗口打开地图</el-link>
</div>
</section>
</AppDialog>
</template>
<style scoped>
.route-map { display: grid; gap: var(--admin-space-3); }
.route-note { margin: 0; color: var(--cc-muted); font-size: var(--admin-font-caption); }
.route-map iframe { width: 100%; height: 58vh; min-height: 320px; border: 1px solid var(--cc-border); border-radius: var(--admin-radius); }
.route-help { display: flex; align-items: center; flex-wrap: wrap; gap: var(--admin-space-3); color: var(--cc-muted); }
</style>