73 lines
2.5 KiB
Vue
73 lines
2.5 KiB
Vue
<script setup lang="ts">
|
|
import { shallowRef, watch } from 'vue'
|
|
import { ElImage } from 'element-plus'
|
|
import { accessFileUrl, fileUrls, originalFileUrl, resolveFileUrl, fileAccessRevision, retryFileAccess } from '/@/utils/file-access'
|
|
|
|
defineOptions({ inheritAttrs: false })
|
|
const props = withDefaults(defineProps<{ src?: string; previewSrcList?: string[] }>(), {
|
|
src: '', previewSrcList: () => [],
|
|
})
|
|
const emit = defineEmits<{
|
|
load: [event: Event]
|
|
error: [event: Event]
|
|
show: []
|
|
switch: [index: number]
|
|
close: []
|
|
}>()
|
|
const displaySrc = shallowRef('')
|
|
const previewSources = shallowRef<string[]>([])
|
|
let generation = 0
|
|
let loaded = false
|
|
let failed = false
|
|
let pending = false
|
|
let lastError: Event
|
|
|
|
const identity = (source: string) => resolveFileUrl(originalFileUrl(source))
|
|
const refreshPreview = () => { previewSources.value = fileUrls(props.previewSrcList) }
|
|
|
|
// An already decoded image remains visible after its request signature expires.
|
|
// Only a changed business reference or a failed request needs a new thumbnail request.
|
|
watch(() => identity(props.src), () => {
|
|
generation++
|
|
loaded = failed = pending = false
|
|
displaySrc.value = accessFileUrl(props.src)
|
|
}, { immediate: true })
|
|
watch(() => JSON.stringify(props.previewSrcList.map(identity)), refreshPreview, { immediate: true })
|
|
|
|
function onLoad(event: Event) { loaded = true; failed = false; emit('load', event) }
|
|
async function onError(event: Event) {
|
|
failed = true
|
|
lastError = event
|
|
if (pending) return
|
|
const current = generation
|
|
pending = true
|
|
try {
|
|
const retried = await retryFileAccess(props.src)
|
|
if (current !== generation) return
|
|
if (retried) {
|
|
failed = false
|
|
displaySrc.value = accessFileUrl(props.src)
|
|
} else emit('error', event)
|
|
} finally { if (current === generation) pending = false }
|
|
}
|
|
watch(fileAccessRevision, () => {
|
|
if (loaded || pending) return
|
|
const renewed = accessFileUrl(props.src)
|
|
if (renewed !== displaySrc.value) {
|
|
failed = false
|
|
displaySrc.value = renewed
|
|
} else if (failed) void onError(lastError)
|
|
})
|
|
function onShow() { refreshPreview(); emit('show') }
|
|
function onSwitch(index: number) { refreshPreview(); emit('switch', index) }
|
|
</script>
|
|
|
|
<template>
|
|
<ElImage v-bind="$attrs" :src="displaySrc" :preview-src-list="previewSources"
|
|
@load="onLoad" @error="onError" @show="onShow" @switch="onSwitch" @close="emit('close')">
|
|
<template v-for="(_, name) in $slots" #[name]="scope">
|
|
<slot :name="name" v-bind="scope || {}" />
|
|
</template>
|
|
</ElImage>
|
|
</template>
|