feat(图片缓存): 新增对加载过的图片进行缓存的功能,只有在发布新版本时才会清空缓存

This commit is contained in:
hzj 2025-11-18 15:30:12 +08:00
parent 925dd8f3e0
commit d9cf63af3d
3 changed files with 243 additions and 0 deletions

View File

@ -0,0 +1,50 @@
// src/directives/smartImage.js
import { imageCacheManager } from '@/utils/imageCacheManager'
export const smartImage = {
async mounted(el, binding) {
if (binding.value) {
// 对于外部链接,直接使用普通加载方式
if (!imageCacheManager.isBuildAsset(binding.value)) {
el.src = binding.value
el.style.opacity = '1'
return
}
// 设置加载状态样式
el.style.opacity = '0'
el.style.transition = 'opacity 0.3s ease-in-out'
try {
await imageCacheManager.loadImage(binding.value)
el.src = binding.value
el.style.opacity = '1'
} catch (error) {
console.warn(`图片加载失败: ${binding.value}`, error)
el.src = binding.value
el.style.opacity = '1'
}
}
},
async updated(el, binding) {
if (binding.value !== binding.oldValue && binding.value) {
// 对于外部链接,直接使用普通加载方式
if (!imageCacheManager.isBuildAsset(binding.value)) {
el.src = binding.value
return
}
el.style.opacity = '0'
try {
await imageCacheManager.loadImage(binding.value)
el.src = binding.value
el.style.opacity = '1'
} catch (error) {
console.warn(`图片更新失败: ${binding.value}`, error)
el.src = binding.value
el.style.opacity = '1'
}
}
},
}

View File

@ -1,6 +1,7 @@
import './assets/main.css'
import { logEnvInfo } from './utils/env.js'
import { versionChecker } from './utils/versionChecker.js'
import { smartImage } from './directives/smartImage.js'
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import i18n, { initI18n } from './locales/i18n'
@ -20,6 +21,9 @@ console.debug('🔐 Permission system initialized')
const pinia = createPinia()
const app = createApp(App)
// 注册智能图片指令
app.directive('smart-img', smartImage)
app.use(router)
app.use(pinia)

View File

@ -0,0 +1,189 @@
// src/utils/imageCacheManager.js
class ImageCacheManager {
constructor() {
this.cacheVersionKey = 'likei-image-cache-version'
this.currentAppVersion = import.meta.env.VITE_APP_VERSION || '1.0.0'
this.cacheName = `likei-images-v${this.currentAppVersion}`
this.maxCacheItems = 50 // 限制最多缓存50张图片可根据需要调整
this.maxCacheSize = 50 * 1024 * 1024 // 限制总大小50MB可根据需要调整
this.init()
}
async init() {
await this.checkVersionAndUpdate()
}
async checkVersionAndUpdate() {
const cachedVersion = localStorage.getItem(this.cacheVersionKey)
if (!cachedVersion) {
localStorage.setItem(this.cacheVersionKey, this.currentAppVersion)
console.log('初始化图片缓存版本:', this.currentAppVersion)
return
}
if (cachedVersion !== this.currentAppVersion) {
console.log(`图片缓存版本更新: ${cachedVersion} -> ${this.currentAppVersion}`)
await this.clearOldCaches()
localStorage.setItem(this.cacheVersionKey, this.currentAppVersion)
}
}
// 检查并清理超出限制的缓存
async enforceCacheLimits(cache) {
try {
const keys = await cache.keys()
// 检查数量限制
if (keys.length > this.maxCacheItems) {
// 删除最旧的缓存项
const itemsToRemove = keys.length - this.maxCacheItems
for (let i = 0; i < itemsToRemove; i++) {
await cache.delete(keys[i])
console.log(`已删除超出限制的缓存项: ${keys[i].url}`)
}
}
// 可选:检查大小限制(需要估算)
// 这里简化处理,实际项目中可以更精确计算
} catch (error) {
console.warn('检查缓存限制时出错:', error)
}
}
async clearOldCaches() {
if (!('caches' in window)) return
try {
const cacheNames = await caches.keys()
const oldCaches = cacheNames.filter(
(name) => name.startsWith('likei-images-v') && name !== this.cacheName
)
await Promise.all(oldCaches.map((name) => caches.delete(name)))
console.log('已清理旧版本图片缓存:', oldCaches)
} catch (error) {
console.warn('清理旧图片缓存失败:', error)
}
}
// 更严格的判断是否为本地构建资源
isBuildAsset(url) {
try {
// 如果是相对路径,肯定是本地资源
if (url.startsWith('/') || url.startsWith('./') || url.startsWith('../')) {
return true
}
// 如果是绝对URL检查是否为本地域名
const currentOrigin = window.location.origin
if (url.startsWith(currentOrigin)) {
return true
}
// 检查是否为构建后的资源(包含哈希)
if (/\.[a-f0-9]{8,}\./.test(url)) {
return true
}
// 检查是否为本地assets路径
if (url.includes('/src/assets/') || url.includes('/assets/')) {
return true
}
return false
} catch (error) {
console.warn('判断资源类型时出错:', error)
return false
}
}
// 缓存图片
async cacheImage(url) {
if (!('caches' in window) || !url) return
try {
// 只缓存本地构建后的资源,避免缓存外部网络图片
if (this.isBuildAsset(url)) {
const cache = await caches.open(this.cacheName)
const cachedResponse = await cache.match(url)
if (!cachedResponse) {
// 先检查并清理超出限制的缓存
// await this.enforceCacheLimits(cache)
const response = await fetch(url)
if (response.ok) {
await cache.put(url, response.clone())
console.log(`图片已缓存: ${url}`)
}
}
}
} catch (error) {
console.warn(`缓存图片失败 ${url}:`, error)
}
}
// 从缓存获取图片
async getCachedImage(url) {
if (!('caches' in window)) return null
try {
// 只从缓存获取本地资源
if (this.isBuildAsset(url)) {
const cache = await caches.open(this.cacheName)
return await cache.match(url)
}
return null
} catch (error) {
console.warn('获取缓存图片失败:', error)
return null
}
}
// 智能加载图片
async loadImage(src) {
return new Promise(async (resolve, reject) => {
try {
// 首先尝试从缓存获取(仅限本地资源)
const cachedResponse = await this.getCachedImage(src)
if (cachedResponse) {
const blob = await cachedResponse.blob()
const imageUrl = URL.createObjectURL(blob)
const img = new Image()
img.onload = () => {
URL.revokeObjectURL(imageUrl)
resolve(img)
}
img.onerror = () => {
URL.revokeObjectURL(imageUrl)
this.loadImageFromNetwork(src, resolve, reject)
}
img.src = imageUrl
return
}
// 缓存中没有或不是本地资源,从网络加载
this.loadImageFromNetwork(src, resolve, reject)
} catch (error) {
this.loadImageFromNetwork(src, resolve, reject)
}
})
}
async loadImageFromNetwork(src, resolve, reject) {
const img = new Image()
img.onload = async () => {
resolve(img)
// 只有本地资源才缓存
if (this.isBuildAsset(src)) {
await this.cacheImage(src)
}
}
img.onerror = reject
img.src = src
}
}
// 导出单例
export const imageCacheManager = new ImageCacheManager()