解决方式:由后端转发,前端将图片url接口传给后端, 后端返回blob数据流展示,前端对图片url相同的进行缓存,不会重复发请求。
封装方法utils/transitionImage.ts
TypeScript
import { previewImage } from '@/api/common'
// 跟踪正在加载的URL
export const loadingUrls = new Set<string>()
// 跟踪pending状态的请求(用于取消请求)
const pendingRequests = new Map<string, AbortController>()
// 记录每个URL的最新请求序列号(确保响应顺序正确)
const requestSequence = new Map<string, number>()
export const getImageUrl = async (
url: string,
pictureUrlsRef: { value: Record<string, string> },
forceUpdate = false
): Promise<string | undefined> => {
if (!url) return
// 生成当前请求的序列号
const currentSeq = (requestSequence.get(url) || 0) + 1
requestSequence.set(url, currentSeq)
// 强制刷新时:取消旧请求(如果存在)
if (forceUpdate) {
// 清除旧缓存
if (pictureUrlsRef.value[url]) {
URL.revokeObjectURL(pictureUrlsRef.value[url])
delete pictureUrlsRef.value[url]
}
// 取消pending的旧请求
const existingController = pendingRequests.get(url)
if (existingController) {
existingController.abort() // 中断旧请求
pendingRequests.delete(url)
loadingUrls.delete(url) // 移除加载中标记
}
} else {
// 非强制刷新:如果已有缓存,直接返回(核心逻辑)
if (pictureUrlsRef.value[url]) {
return pictureUrlsRef.value[url]
}
// 非强制刷新:如果正在加载,直接返回(避免重复请求)
if (loadingUrls.has(url)) {
return
}
}
// 发起新请求(仅当无缓存、非加载中、或强制刷新时)
const controller = new AbortController()
pendingRequests.set(url, controller)
loadingUrls.add(url)
try {
const res = await previewImage(encodeURIComponent(url), {
signal: controller.signal
})
// 校验序列号,确保只处理最新请求的响应
if (currentSeq !== requestSequence.get(url)) {
console.log(
`忽略过期响应: ${url} (当前序列号: ${currentSeq}, 最新序列号: ${requestSequence.get(url)})`
)
return
}
if (res?.data instanceof Blob) {
const blobUrl = URL.createObjectURL(res.data)
pictureUrlsRef.value[url] = blobUrl // 缓存结果
return blobUrl
}
} catch (error) {
// 忽略主动取消的错误
if ((error as Error).name !== 'AbortError') {
console.error('图片加载失败:', error)
}
} finally {
// 清理状态(仅当前请求是最新的才执行)
if (currentSeq === requestSequence.get(url)) {
loadingUrls.delete(url)
pendingRequests.delete(url)
}
}
}
export const clearImageCache = (pictureUrlsRef: { value: Record<string, string> }) => {
Object.values(pictureUrlsRef.value).forEach((url) => {
if (url.startsWith('blob:')) {
URL.revokeObjectURL(url)
}
})
pictureUrlsRef.value = {}
requestSequence.clear() // 重置序列号
}
vue页面内使用
TypeScript
import { getImageUrl, loadingUrls, clearImageCache } from '@/utils/transitionImage'
const pictureUrls = ref<Record<string, string>>({})
const schema2 = reactive<DescriptionsSchema[]>([
{
field: 'vehiclePicUri',
label: '通行抓拍图',
width: 100,
slots: {
default: (data: any) => {
const imageUrl = data.vehiclePicUri
const imgUrl = pictureUrls.value[imageUrl]
const isLoading = Array.from(loadingUrls).includes(imageUrl)
if (imageUrl && !imgUrl && !isLoading) {
getImageUrl(imageUrl, pictureUrls)
}
return imgUrl ? (
<ElImage
style="width: 100px; height: 100px"
src={imgUrl}
zoom-rate={1.2}
max-scale={7}
min-scale={0.2}
preview-src-list={[imgUrl]}
initial-index={0}
fit="cover"
/>
) : isLoading ? (
<div class="flex items-center justify-center h-full">
<i class="el-icon-loading"></i>
</div>
) : (
<div>(未知)</div>
)
}
}
}
])
// 监听弹窗打开,触发图片加载
watch(
() => dialogVisible2.value,
(newVal) => {
if (newVal && data1) {
const imageUrl = data1.vehiclePicUri
if (imageUrl) {
getImageUrl(imageUrl, pictureUrls)
}
}
}
)
// 组件卸载时清理缓存,防止内存泄漏
onUnmounted(() => {
clearImageCache(pictureUrls)
})