57 lines
3.7 KiB
Vue
57 lines
3.7 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
|
import { getTransportPhotos, readTransportPhoto, type CrossBorderBusinessKind, type TransportPhotos, type TransportPhotoPhase } from '../api/businessOrders'
|
|
|
|
const props = defineProps<{ kind: CrossBorderBusinessKind; orderNo: string; refreshKey: number }>()
|
|
const photos = ref<TransportPhotos | null>(null), urls = ref<Record<string, string>>({}), loading = ref(false), error = ref('')
|
|
const groups: { phase: TransportPhotoPhase; key: keyof TransportPhotos; label: string }[] = [
|
|
{ phase: 'PICKUP', key: 'pickupPhotos', label: '取货照片' }, { phase: 'ARRIVAL', key: 'arrivalPhotos', label: '派送照片' },
|
|
]
|
|
const previewUrls = computed(() => groups.flatMap(group => (photos.value?.[group.key] || []).filter(file => file.contentType.startsWith('image/')).map(file => urls.value[`${group.phase}:${file.fileId}`]).filter((url): url is string => Boolean(url))))
|
|
let sequence = 0
|
|
function clear() { Object.values(urls.value).forEach(url => URL.revokeObjectURL(url)); urls.value = {} }
|
|
async function load() {
|
|
const current = ++sequence, kind = props.kind, number = props.orderNo
|
|
loading.value = true; error.value = ''; photos.value = null; clear()
|
|
try {
|
|
const value = await getTransportPhotos(kind, number)
|
|
if (current !== sequence) return
|
|
photos.value = value
|
|
await Promise.all(groups.flatMap(group => value[group.key].map(async file => {
|
|
try {
|
|
const blob = await readTransportPhoto(kind, number, group.phase, file.fileId)
|
|
if (current === sequence) urls.value[`${group.phase}:${file.fileId}`] = URL.createObjectURL(blob)
|
|
} catch { if (current === sequence) error.value = '部分照片加载失败,请重试' }
|
|
})))
|
|
} catch (cause) { if (current === sequence) error.value = cause instanceof Error ? cause.message : '运输照片加载失败' }
|
|
finally { if (current === sequence) loading.value = false }
|
|
}
|
|
watch(() => [props.kind, props.orderNo, props.refreshKey], () => void load(), { immediate: true })
|
|
onBeforeUnmount(() => { sequence++; clear() })
|
|
</script>
|
|
<template>
|
|
<section class="transport-photos" aria-label="运输照片" v-loading="loading">
|
|
<div v-if="error" class="photo-error" role="alert">{{ error }} <el-button link type="primary" :disabled="loading" @click="load">重试</el-button></div>
|
|
<section v-for="group in groups" :key="group.phase" class="photo-group">
|
|
<h3>{{ group.label }}</h3>
|
|
<div class="photo-row">
|
|
<template v-for="file in photos?.[group.key] || []" :key="file.fileId">
|
|
<el-image v-if="file.contentType.startsWith('image/')" :src="urls[group.phase + ':' + file.fileId]" :alt="group.label" fit="contain" :preview-src-list="previewUrls" :initial-index="previewUrls.indexOf(urls[group.phase + ':' + file.fileId] || '')" preview-teleported><template #error><span>{{ loading ? '加载中' : '未加载' }}</span></template></el-image>
|
|
<a v-else :href="urls[group.phase + ':' + file.fileId]" target="_blank" rel="noopener">查看凭证</a>
|
|
</template>
|
|
<span v-if="!loading && !photos?.[group.key].length" class="photo-empty">—</span>
|
|
</div>
|
|
</section>
|
|
</section>
|
|
</template>
|
|
<style scoped>
|
|
.transport-photos { margin-top: 20px; }
|
|
.photo-group + .photo-group { margin-top: 16px; }
|
|
.photo-group h3 { margin: 0 0 10px; color: var(--navy); font-size: 15px; }
|
|
.photo-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
|
|
.photo-row :deep(.el-image) { width: 112px; height: 84px; border: 1px solid var(--border); border-radius: 4px; }
|
|
.photo-row :deep(.el-image__error) { font-size: 12px; }
|
|
.photo-empty { color: var(--muted); }
|
|
.photo-error { margin-bottom: 10px; color: var(--el-color-danger); }
|
|
</style>
|