90 lines
1.9 KiB
Vue
90 lines
1.9 KiB
Vue
<script setup lang="ts">
|
|
import { onBeforeUnmount, ref, watch } from 'vue'
|
|
import { fetchAdminFileBlob, fetchFileBlobByURL } from '@/shared/api/files'
|
|
|
|
const props = defineProps<{
|
|
source: string
|
|
admin?: boolean
|
|
}>()
|
|
|
|
const objectURL = ref('')
|
|
const failed = ref(false)
|
|
|
|
function extractObjectKey(value: string) {
|
|
try {
|
|
const parsed = new URL(value, window.location.origin)
|
|
return parsed.searchParams.get('key') || ''
|
|
} catch {
|
|
return ''
|
|
}
|
|
}
|
|
|
|
function revokeCurrentURL() {
|
|
if (!objectURL.value) return
|
|
URL.revokeObjectURL(objectURL.value)
|
|
objectURL.value = ''
|
|
}
|
|
|
|
async function loadImage() {
|
|
revokeCurrentURL()
|
|
failed.value = false
|
|
if (!props.source) {
|
|
failed.value = true
|
|
return
|
|
}
|
|
try {
|
|
const key = extractObjectKey(props.source)
|
|
const blob =
|
|
props.admin && key ? await fetchAdminFileBlob(key) : await fetchFileBlobByURL(props.source)
|
|
objectURL.value = URL.createObjectURL(blob)
|
|
} catch {
|
|
failed.value = true
|
|
}
|
|
}
|
|
|
|
function openImage() {
|
|
if (!objectURL.value) return
|
|
window.open(objectURL.value, '_blank')
|
|
}
|
|
|
|
watch(() => [props.source, props.admin] as const, loadImage, { immediate: true })
|
|
|
|
onBeforeUnmount(revokeCurrentURL)
|
|
</script>
|
|
|
|
<template>
|
|
<button v-if="objectURL" class="chat-image-button" type="button" @click="openImage">
|
|
<img :src="objectURL" alt="聊天图片" loading="lazy" decoding="async" />
|
|
</button>
|
|
<span v-else class="chat-image-fallback">{{ failed ? '图片加载失败' : '图片加载中' }}</span>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.chat-image-button {
|
|
display: block;
|
|
max-width: 220px;
|
|
padding: 0;
|
|
overflow: hidden;
|
|
border: 0;
|
|
border-radius: 8px;
|
|
background: transparent;
|
|
cursor: zoom-in;
|
|
}
|
|
|
|
.chat-image-button img {
|
|
display: block;
|
|
width: 100%;
|
|
max-height: 260px;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.chat-image-fallback {
|
|
display: inline-block;
|
|
padding: 8px 10px;
|
|
border-radius: 8px;
|
|
background: #eef2f7;
|
|
color: #6b7280;
|
|
font-size: 12px;
|
|
}
|
|
</style>
|