二维码池批量上传与认证图片预览修复
- 后端新增批量创建二维码接口 POST /admin/chats/qrcodes/batch - 前端二维码管理页支持多图拖拽批量上传 - 注册 qrcode 图片压缩场景(maxSide=2560, quality=0.95) - 后端 normalizeScene 注册 qrcode scene - 增强 AuthImage 组件支持 el-image 大图预览(previewSrcList) - 修复 WithdrawalDetailDialog 提现凭证图片 401 问题 - 删除重复的 components/AuthImage.vue,统一使用共享组件 - 提交3剩余: 会话列表群类型图标、发布成功跳群、管理端路由等
This commit is contained in:
@@ -11,6 +11,10 @@ const props = withDefaults(
|
||||
fallbackClass?: string
|
||||
loading?: 'eager' | 'lazy'
|
||||
decoding?: 'async' | 'sync' | 'auto'
|
||||
previewSrcList?: string[]
|
||||
previewTeleported?: boolean
|
||||
fit?: 'fill' | 'contain' | 'cover' | 'none' | 'scale-down'
|
||||
imageStyle?: any
|
||||
}>(),
|
||||
{
|
||||
alt: '',
|
||||
@@ -19,13 +23,16 @@ const props = withDefaults(
|
||||
fallbackClass: '',
|
||||
loading: 'lazy',
|
||||
decoding: 'async',
|
||||
previewTeleported: true,
|
||||
}
|
||||
)
|
||||
|
||||
const imageURL = ref('')
|
||||
const previewURLs = ref<string[]>([])
|
||||
const failed = ref(false)
|
||||
let createdObjectURL = ''
|
||||
const createdURLs: string[] = []
|
||||
|
||||
const usePreview = computed(() => !!props.previewSrcList?.length)
|
||||
const fallbackText = computed(() => (failed.value ? '图片加载失败' : '图片加载中'))
|
||||
|
||||
function extractObjectKey(value: string) {
|
||||
@@ -41,15 +48,25 @@ function shouldFetchWithAuth(value: string) {
|
||||
return value.includes('/api/files/object') || value.includes('/api/admin/files/object')
|
||||
}
|
||||
|
||||
function revokeCurrentURL() {
|
||||
if (!createdObjectURL) return
|
||||
URL.revokeObjectURL(createdObjectURL)
|
||||
createdObjectURL = ''
|
||||
async function resolveURL(url: string): Promise<string> {
|
||||
if (!url || !shouldFetchWithAuth(url)) return url
|
||||
const key = extractObjectKey(url)
|
||||
const blob =
|
||||
props.admin && key ? await fetchAdminFileBlob(key) : await fetchFileBlobByURL(url)
|
||||
const blobURL = URL.createObjectURL(blob)
|
||||
createdURLs.push(blobURL)
|
||||
return blobURL
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
createdURLs.forEach((u) => URL.revokeObjectURL(u))
|
||||
createdURLs.length = 0
|
||||
}
|
||||
|
||||
async function loadImage() {
|
||||
revokeCurrentURL()
|
||||
cleanup()
|
||||
imageURL.value = ''
|
||||
previewURLs.value = []
|
||||
failed.value = false
|
||||
|
||||
if (!props.source) {
|
||||
@@ -57,30 +74,35 @@ async function loadImage() {
|
||||
return
|
||||
}
|
||||
|
||||
if (!shouldFetchWithAuth(props.source)) {
|
||||
imageURL.value = props.source
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const key = extractObjectKey(props.source)
|
||||
const blob =
|
||||
props.admin && key ? await fetchAdminFileBlob(key) : await fetchFileBlobByURL(props.source)
|
||||
createdObjectURL = URL.createObjectURL(blob)
|
||||
imageURL.value = createdObjectURL
|
||||
imageURL.value = await resolveURL(props.source)
|
||||
if (props.previewSrcList?.length) {
|
||||
previewURLs.value = await Promise.all(props.previewSrcList.map(resolveURL))
|
||||
}
|
||||
} catch {
|
||||
failed.value = true
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => [props.source, props.admin] as const, loadImage, { immediate: true })
|
||||
watch(() => [props.source, props.admin, props.previewSrcList] as const, loadImage, {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
})
|
||||
|
||||
onBeforeUnmount(revokeCurrentURL)
|
||||
onBeforeUnmount(cleanup)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-image
|
||||
v-if="imageURL && usePreview"
|
||||
:src="imageURL"
|
||||
:fit="fit"
|
||||
:style="imageStyle"
|
||||
:preview-src-list="previewURLs"
|
||||
:preview-teleported="previewTeleported"
|
||||
/>
|
||||
<img
|
||||
v-if="imageURL"
|
||||
v-else-if="imageURL"
|
||||
:class="imageClass"
|
||||
:src="imageURL"
|
||||
:alt="alt"
|
||||
|
||||
Reference in New Issue
Block a user