80 lines
2.6 KiB
Vue
80 lines
2.6 KiB
Vue
<script setup lang="ts">
|
|
import { onBeforeUnmount, ref, watch } from 'vue'
|
|
import { ImageOff } from '@lucide/vue'
|
|
import { downloadWeightThumbnail } from '../../weight/api/weightOcr'
|
|
|
|
const props = withDefaults(defineProps<{
|
|
customerId: string
|
|
inboundId: string
|
|
fileId?: string | null
|
|
alt: string
|
|
emptyText?: string
|
|
}>(), {
|
|
fileId: null,
|
|
emptyText: '未绑定测量图片',
|
|
})
|
|
|
|
defineEmits<{ open: [] }>()
|
|
|
|
const imageUrl = ref('')
|
|
const loading = ref(false)
|
|
const failed = ref(false)
|
|
let requestId = 0
|
|
|
|
function releaseImage() {
|
|
if (imageUrl.value) URL.revokeObjectURL(imageUrl.value)
|
|
imageUrl.value = ''
|
|
}
|
|
|
|
async function load() {
|
|
const currentRequest = ++requestId
|
|
releaseImage()
|
|
failed.value = false
|
|
if (!props.fileId) {
|
|
loading.value = false
|
|
return
|
|
}
|
|
loading.value = true
|
|
try {
|
|
const blob = await downloadWeightThumbnail(props.customerId, props.inboundId, props.fileId)
|
|
if (currentRequest !== requestId) return
|
|
imageUrl.value = URL.createObjectURL(blob)
|
|
} catch {
|
|
if (currentRequest === requestId) failed.value = true
|
|
} finally {
|
|
if (currentRequest === requestId) loading.value = false
|
|
}
|
|
}
|
|
|
|
watch(() => [props.customerId, props.inboundId, props.fileId], load, { immediate: true })
|
|
onBeforeUnmount(() => {
|
|
requestId += 1
|
|
releaseImage()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<button
|
|
class="measurement-evidence-thumb"
|
|
type="button"
|
|
:disabled="!fileId"
|
|
:aria-label="fileId ? `查看${alt}` : emptyText"
|
|
@click="$emit('open')"
|
|
>
|
|
<img v-if="imageUrl" :src="imageUrl" :alt="alt" loading="lazy" />
|
|
<span v-else class="measurement-evidence-thumb__state" :class="{ 'is-loading': loading }">
|
|
<ImageOff v-if="failed || !fileId" :size="22" aria-hidden="true" />
|
|
<span>{{ !fileId ? emptyText : failed ? '图片加载失败' : '图片加载中' }}</span>
|
|
</span>
|
|
</button>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.measurement-evidence-thumb { display: block; width: 100%; min-width: 96px; aspect-ratio: 4 / 3; overflow: hidden; padding: 0; border: 1px solid var(--cc-border); border-radius: 6px; background: #f2f4f7; cursor: zoom-in; }
|
|
.measurement-evidence-thumb:disabled { cursor: default; }
|
|
.measurement-evidence-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
.measurement-evidence-thumb__state { display: grid; width: 100%; height: 100%; place-items: center; align-content: center; gap: 7px; padding: 8px; color: #667085; font-size: 12px; }
|
|
.measurement-evidence-thumb__state.is-loading { color: #98a2b3; }
|
|
.measurement-evidence-thumb:focus-visible { outline: 2px solid var(--cc-primary); outline-offset: 2px; }
|
|
</style>
|