test(图片预加载、缓存): 功能测试
This commit is contained in:
parent
a59c75c57b
commit
d53b1daddb
@ -10,6 +10,7 @@
|
||||
alt=""
|
||||
width="100%"
|
||||
style="display: block"
|
||||
:crossorigin="isOSSImage ? 'anonymous' : undefined"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
@ -78,6 +79,11 @@ const shouldShowImage = computed(() => {
|
||||
return isVisible.value
|
||||
})
|
||||
|
||||
// 判断是否为OSS图片
|
||||
const isOSSImage = computed(() => {
|
||||
return props.imgUrl.startsWith('https://tkm-likei.oss-ap-southeast-1.aliyuncs.com/h5')
|
||||
})
|
||||
|
||||
// 创建交叉观察器
|
||||
const createObserver = () => {
|
||||
if (!props.lazy || props.immediate) return
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
// src/config/imagePaths.js
|
||||
export const OSS_BASE_URL = 'https://tkm-likei.oss-ap-southeast-1.aliyuncs.com/h5/' // 阿里云oss
|
||||
// export const OMAN_NATIONAL_DAY_PATH = 'Activities/Oman/NationalDay/'// 文件路径
|
||||
export const OSS_BASE_URL =
|
||||
process.env.NODE_ENV === 'development'
|
||||
? '/oss/h5/' // 开发环境使用代理
|
||||
: 'https://tkm-likei.oss-ap-southeast-1.aliyuncs.com/h5/' // 生产环境直接访问
|
||||
|
||||
// 组合使用示例
|
||||
export const getPngUrl = (imagePath, filename) => {
|
||||
|
||||
@ -4,21 +4,37 @@ 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'
|
||||
// 对于OSS图片使用特殊处理
|
||||
if (binding.value.startsWith('https://tkm-likei.oss-ap-southeast-1.aliyuncs.com/h5')) {
|
||||
const img = new Image()
|
||||
img.crossOrigin = 'anonymous'
|
||||
img.onload = () => {
|
||||
el.src = binding.value
|
||||
el.style.opacity = '1'
|
||||
}
|
||||
img.onerror = (error) => {
|
||||
console.warn(`图片加载失败: ${binding.value}`, error)
|
||||
el.src = binding.value
|
||||
el.style.opacity = '1'
|
||||
}
|
||||
img.src = binding.value
|
||||
} else {
|
||||
// 对于外部链接,直接使用普通加载方式
|
||||
if (!imageCacheManager.isBuildAsset(binding.value)) {
|
||||
el.src = binding.value
|
||||
el.style.opacity = '1'
|
||||
return
|
||||
}
|
||||
|
||||
await imageCacheManager.loadImage(binding.value)
|
||||
el.src = binding.value
|
||||
el.style.opacity = '1'
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`图片加载失败: ${binding.value}`, error)
|
||||
el.src = binding.value
|
||||
@ -29,17 +45,34 @@ export const smartImage = {
|
||||
|
||||
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'
|
||||
// 对于OSS图片使用特殊处理
|
||||
if (binding.value.startsWith('https://tkm-likei.oss-ap-southeast-1.aliyuncs.com/h5')) {
|
||||
const img = new Image()
|
||||
img.crossOrigin = 'anonymous'
|
||||
img.onload = () => {
|
||||
el.src = binding.value
|
||||
el.style.opacity = '1'
|
||||
}
|
||||
img.onerror = (error) => {
|
||||
console.warn(`图片更新失败: ${binding.value}`, error)
|
||||
el.src = binding.value
|
||||
el.style.opacity = '1'
|
||||
}
|
||||
img.src = binding.value
|
||||
} else {
|
||||
// 对于外部链接,直接使用普通加载方式
|
||||
if (!imageCacheManager.isBuildAsset(binding.value)) {
|
||||
el.src = binding.value
|
||||
return
|
||||
}
|
||||
|
||||
await imageCacheManager.loadImage(binding.value)
|
||||
el.src = binding.value
|
||||
el.style.opacity = '1'
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`图片更新失败: ${binding.value}`, error)
|
||||
el.src = binding.value
|
||||
|
||||
@ -4,8 +4,8 @@ class ImageCacheManager {
|
||||
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.maxCacheItems = 100 // 限制最多缓存50张图片(可根据需要调整)
|
||||
this.maxCacheSize = 100 * 1024 * 1024 // 限制总大小50MB(可根据需要调整)
|
||||
this.init()
|
||||
}
|
||||
|
||||
@ -119,7 +119,10 @@ class ImageCacheManager {
|
||||
// 先检查并清理超出限制的缓存
|
||||
// await this.enforceCacheLimits(cache)
|
||||
|
||||
const response = await fetch(url)
|
||||
const response = await fetch(url, {
|
||||
mode: 'cors',
|
||||
credentials: 'omit',
|
||||
})
|
||||
if (response.ok) {
|
||||
await cache.put(url, response.clone())
|
||||
console.log(`图片已缓存: ${url}`)
|
||||
@ -180,15 +183,42 @@ class ImageCacheManager {
|
||||
|
||||
async loadImageFromNetwork(src, resolve, reject) {
|
||||
try {
|
||||
const response = await fetch(src)
|
||||
// 对于OSS资源,先尝试直接创建Image对象(绕过fetch的CORS问题)
|
||||
if (src.startsWith('https://tkm-likei.oss-ap-southeast-1.aliyuncs.com/h5')) {
|
||||
const img = new Image()
|
||||
img.crossOrigin = 'anonymous'
|
||||
img.onload = () => {
|
||||
resolve(img)
|
||||
// 对于OSS资源,仍然尝试缓存(如果支持)
|
||||
if (this.isBuildAsset(src)) {
|
||||
this.cacheImageFromImage(src, img)
|
||||
}
|
||||
}
|
||||
img.onerror = (error) => {
|
||||
console.warn('图片加载失败:', src, error)
|
||||
reject(error)
|
||||
}
|
||||
img.src = src
|
||||
return
|
||||
}
|
||||
|
||||
// 对于其他资源,使用fetch
|
||||
const response = await fetch(src, {
|
||||
mode: 'cors',
|
||||
credentials: 'omit',
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const img = new Image()
|
||||
img.onload = async () => {
|
||||
resolve(img)
|
||||
// 缓存带有 ETag 的响应
|
||||
if (this.isBuildAsset(src)) {
|
||||
const cache = await caches.open(this.cacheName)
|
||||
await cache.put(src, response.clone())
|
||||
try {
|
||||
await this.cacheImage(src) // 使用原始的缓存方法
|
||||
} catch (cacheError) {
|
||||
console.warn('缓存图片时出错:', cacheError)
|
||||
}
|
||||
}
|
||||
}
|
||||
img.onerror = reject
|
||||
@ -197,9 +227,43 @@ class ImageCacheManager {
|
||||
reject(new Error(`HTTP ${response.status}`))
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('网络加载图片失败:', src, error)
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 为OSS图片创建缓存的特殊处理
|
||||
async cacheImageFromImage(src, img) {
|
||||
try {
|
||||
if (!('caches' in window) || !this.isBuildAsset(src)) return
|
||||
|
||||
const cache = await caches.open(this.cacheName)
|
||||
const cachedResponse = await cache.match(src)
|
||||
|
||||
if (!cachedResponse) {
|
||||
// 创建一个模拟的响应来缓存
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = img.width
|
||||
canvas.height = img.height
|
||||
const ctx = canvas.getContext('2d')
|
||||
ctx.drawImage(img, 0, 0)
|
||||
|
||||
canvas.toBlob(async (blob) => {
|
||||
if (blob) {
|
||||
const response = new Response(blob, {
|
||||
headers: {
|
||||
'Content-Type': 'image/png',
|
||||
},
|
||||
})
|
||||
await cache.put(src, response.clone())
|
||||
console.log(`OSS图片已缓存: ${src}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('缓存OSS图片失败:', src, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<!-- src/views/Activities/heroesDay/index.vue -->
|
||||
<!-- src/views/Activities/Game/index.vue -->
|
||||
<template>
|
||||
<div class="fullPage">
|
||||
<div class="bg">
|
||||
<div class="bg" :style="{ '--bg-url': `url(${imageUrl('bg')})` }">
|
||||
<!-- 状态栏占位区域(仅在APP中显示) -->
|
||||
<div v-if="isInAppEnvironment" style="height: 30px"></div>
|
||||
|
||||
@ -20,25 +20,25 @@
|
||||
<!-- 倒计时 -->
|
||||
<div style="display: flex; justify-content: center; margin-top: 15vw">
|
||||
<div style="width: 20%">
|
||||
<itemCenter :imgUrl="images.timeBg">
|
||||
<itemCenter :imgUrl="imageUrl('timeBg')">
|
||||
<div class="timeText" style="">{{ Days }}D</div>
|
||||
</itemCenter>
|
||||
</div>
|
||||
<div class="timeText timeGap"></div>
|
||||
<div style="width: 20%">
|
||||
<itemCenter :imgUrl="images.timeBg">
|
||||
<itemCenter :imgUrl="imageUrl('timeBg')">
|
||||
<div class="timeText" style="">{{ Hours }}</div>
|
||||
</itemCenter>
|
||||
</div>
|
||||
<div class="timeText timeGap">:</div>
|
||||
<div style="width: 20%">
|
||||
<itemCenter :imgUrl="images.timeBg">
|
||||
<itemCenter :imgUrl="imageUrl('timeBg')">
|
||||
<div class="timeText" style="">{{ Minutes }}</div>
|
||||
</itemCenter>
|
||||
</div>
|
||||
<div class="timeText timeGap">:</div>
|
||||
<div style="width: 20%">
|
||||
<itemCenter :imgUrl="images.timeBg">
|
||||
<itemCenter :imgUrl="imageUrl('timeBg')">
|
||||
<div class="timeText" style="">{{ Second }}</div>
|
||||
</itemCenter>
|
||||
</div>
|
||||
@ -52,14 +52,14 @@
|
||||
>
|
||||
<img
|
||||
v-smart-img
|
||||
:src="rankingShow ? images.rankingBtActive : images.rankingBt"
|
||||
:src="rankingShow ? imageUrl('rankingBtActive') : imageUrl('rankingBt')"
|
||||
alt=""
|
||||
style="display: block; width: 40%"
|
||||
@click="rankingShow = true"
|
||||
/>
|
||||
<img
|
||||
v-smart-img
|
||||
:src="!rankingShow ? images.rewardBtActive : images.rewardsBt"
|
||||
:src="!rankingShow ? imageUrl('rewardBtActive') : imageUrl('rewardsBt')"
|
||||
alt=""
|
||||
style="display: block; width: 40%"
|
||||
@click="rankingShow = false"
|
||||
@ -71,14 +71,14 @@
|
||||
>
|
||||
<img
|
||||
v-smart-img
|
||||
:src="rankingShow ? images.rankingBtActiveAr : images.rankingBtAr"
|
||||
:src="rankingShow ? imageUrl('rankingBtActiveAr') : imageUrl('rankingBtAr')"
|
||||
alt=""
|
||||
style="display: block; width: 40%"
|
||||
@click="rankingShow = true"
|
||||
/>
|
||||
<img
|
||||
v-smart-img
|
||||
:src="!rankingShow ? images.rewardBtActiveAr : images.rewardsBtAr"
|
||||
:src="!rankingShow ? imageUrl('rewardBtActiveAr') : imageUrl('rewardsBtAr')"
|
||||
alt=""
|
||||
style="display: block; width: 40%"
|
||||
@click="rankingShow = false"
|
||||
@ -90,20 +90,17 @@
|
||||
<div>
|
||||
<!-- Ranking -->
|
||||
<div v-show="rankingShow" style="position: relative; width: 100%">
|
||||
<!-- 背景图 -->
|
||||
<!-- <div class="ranking-bg"></div> -->
|
||||
|
||||
<div style="position: relative; z-index: 2">
|
||||
<!-- 前三名 -->
|
||||
<itemCenter
|
||||
:imgUrl="images.RankingMain"
|
||||
:imgUrl="imageUrl('RankingMain')"
|
||||
:contentStyle="`padding: 25% 5% 0;flex-direction: column;`"
|
||||
>
|
||||
<div>
|
||||
<!-- 第一 -->
|
||||
<div style="display: flex; justify-content: center">
|
||||
<TopUser
|
||||
:BorderImgUrl="images.topOne"
|
||||
:BorderImgUrl="imageUrl('topOne')"
|
||||
:avatarUrl="RankingHasTop3[0].avatar"
|
||||
:name="RankingHasTop3[0].nickname"
|
||||
:distributionValue="RankingHasTop3[0].quantity"
|
||||
@ -116,7 +113,7 @@
|
||||
<TopUser
|
||||
:isTopOne="false"
|
||||
ranking="2"
|
||||
:BorderImgUrl="images.topTwo"
|
||||
:BorderImgUrl="imageUrl('topTwo')"
|
||||
:avatarUrl="RankingHasTop3[1].avatar"
|
||||
:name="RankingHasTop3[1].nickname"
|
||||
:distributionValue="RankingHasTop3[1].quantity"
|
||||
@ -126,7 +123,7 @@
|
||||
<TopUser
|
||||
:isTopOne="false"
|
||||
ranking="3"
|
||||
:BorderImgUrl="images.topThree"
|
||||
:BorderImgUrl="imageUrl('topThree')"
|
||||
:avatarUrl="RankingHasTop3[2].avatar"
|
||||
:name="RankingHasTop3[2].nickname"
|
||||
:distributionValue="RankingHasTop3[2].quantity"
|
||||
@ -143,9 +140,12 @@
|
||||
style="margin-bottom: 6vw; position: relative; z-index: 2"
|
||||
>
|
||||
<itemCenter
|
||||
v-for="listItem in showRanking"
|
||||
:imgUrl="images.RankingItem"
|
||||
v-for="(listItem, index) in showRanking"
|
||||
:key="listItem.userId"
|
||||
:imgUrl="imageUrl('RankingItem')"
|
||||
:contentStyle="`padding: 2% 13% 0`"
|
||||
:lazy="true"
|
||||
:immediate="index < 2"
|
||||
@click="viewUserInfo(listItem.userId)"
|
||||
>
|
||||
<!-- 基本信息 -->
|
||||
@ -228,7 +228,7 @@
|
||||
<!-- 底框 -->
|
||||
<img
|
||||
v-smart-img
|
||||
:src="images.RankingBottomBorder"
|
||||
:src="imageUrl('RankingBottomBorder')"
|
||||
alt=""
|
||||
width="100%"
|
||||
style="display: block; margin-top: -40vw; position: relative; z-index: 1"
|
||||
@ -255,7 +255,7 @@
|
||||
:key="rewardIndex"
|
||||
style="width: 100%"
|
||||
>
|
||||
<itemCenter :imgUrl="images.giftItemBg">
|
||||
<itemCenter :imgUrl="imageUrl('giftItemBg')">
|
||||
<img
|
||||
v-smart-img
|
||||
:src="reward.cover || ''"
|
||||
@ -298,7 +298,7 @@
|
||||
<!-- 我的排名 -->
|
||||
<itemCenter
|
||||
style="position: fixed; bottom: 0; z-index: 2"
|
||||
:imgUrl="images.myRankingBg"
|
||||
:imgUrl="imageUrl('myRankingBg')"
|
||||
:contentStyle="`padding: 2% 8% 0`"
|
||||
>
|
||||
<!-- 基本信息 -->
|
||||
@ -380,14 +380,14 @@
|
||||
<img
|
||||
v-if="currentLangType == 'en'"
|
||||
v-smart-img
|
||||
:src="images.helpInfo"
|
||||
:src="imageUrl('helpInfo')"
|
||||
alt=""
|
||||
style="display: block; width: 100%"
|
||||
/>
|
||||
<img
|
||||
v-if="currentLangType == 'ar'"
|
||||
v-smart-img
|
||||
:src="images.helpInfoAr"
|
||||
:src="imageUrl('helpInfoAr')"
|
||||
alt=""
|
||||
style="display: block; width: 100%"
|
||||
/>
|
||||
@ -402,6 +402,8 @@ import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { isInApp, viewUserInfo } from '@/utils/appBridge.js'
|
||||
import { connectToApp } from '@/utils/appConnector.js'
|
||||
import { useLangStore } from '@/stores/lang'
|
||||
import { getPngUrl } from '@/config/imagePaths.js'
|
||||
import { preloadImages } from '@/utils/imagePreloader.js'
|
||||
|
||||
import { getThisWeekRewardsAndGifts, getRankingListAndMyRanking } from '@/api/activity.js'
|
||||
|
||||
@ -409,17 +411,8 @@ import itemCenter from '@/components/itemCenter.vue'
|
||||
import TopUser from './components/topUser.vue'
|
||||
import maskLayer from '@/components/MaskLayer.vue'
|
||||
|
||||
// vite动态批量导入图片
|
||||
const imageModules = import.meta.glob('@/assets/images/Activities/Game/*.{png,jpg,svg}', {
|
||||
eager: true,
|
||||
})
|
||||
// 生成文件名映射对象(自动移除路径和扩展名)
|
||||
const images = Object.fromEntries(
|
||||
Object.entries(imageModules).map(([path, module]) => {
|
||||
const fileName = path.split('/').pop().split('.')[0]
|
||||
return [fileName, module.default]
|
||||
})
|
||||
)
|
||||
// 获取OSS图片URL的函数
|
||||
const imageUrl = (filename) => getPngUrl('Activities/Game/', filename)
|
||||
|
||||
const isInAppEnvironment = ref(false) // 检测是否在APP环境中
|
||||
const maskLayerShow = ref(false) //帮助模块
|
||||
@ -444,7 +437,7 @@ const Ranking = ref([]) // 排行榜
|
||||
const myRanking = ref({}) // 我的排名
|
||||
const rewardsList = ref([]) // 奖励列表
|
||||
|
||||
const topImg = ref([images.top1RewardBg, images.top2RewardBg, images.top3RewardBg]) // top图片
|
||||
const topImg = ref([imageUrl('top1RewardBg'), imageUrl('top2RewardBg'), imageUrl('top3RewardBg')]) // top图片
|
||||
|
||||
// 处理排行榜小于3人时的情况
|
||||
const RankingHasTop3 = computed(() => {
|
||||
@ -463,7 +456,6 @@ const RankingHasTop3 = computed(() => {
|
||||
// 展示用的榜单
|
||||
const showRanking = computed(() => {
|
||||
return Ranking.value.filter((_, index) => index >= 3)
|
||||
// return []
|
||||
})
|
||||
|
||||
// 奖励图片资源出错处理
|
||||
@ -586,11 +578,43 @@ const initData = () => {
|
||||
getRewardsAndGifts() //获取本周的礼物和奖励列表
|
||||
}
|
||||
|
||||
// 预加载关键图片
|
||||
const preloadCriticalImages = () => {
|
||||
const criticalImages = [
|
||||
imageUrl('bg'),
|
||||
imageUrl('timeBg'),
|
||||
imageUrl('rankingBt'),
|
||||
imageUrl('rankingBtActive'),
|
||||
imageUrl('rankingBtAr'),
|
||||
imageUrl('rankingBtActiveAr'),
|
||||
imageUrl('rewardBtActive'),
|
||||
imageUrl('rewardBtActiveAr'),
|
||||
imageUrl('rewardsBt'),
|
||||
imageUrl('rewardsBtAr'),
|
||||
imageUrl('RankingMain'),
|
||||
imageUrl('topOne'),
|
||||
imageUrl('topTwo'),
|
||||
imageUrl('topThree'),
|
||||
imageUrl('RankingItem'),
|
||||
imageUrl('RankingBottomBorder'),
|
||||
imageUrl('myRankingBg'),
|
||||
imageUrl('helpInfo'),
|
||||
imageUrl('helpInfoAr'),
|
||||
imageUrl('top1RewardBg'),
|
||||
imageUrl('top2RewardBg'),
|
||||
imageUrl('top3RewardBg'),
|
||||
imageUrl('giftItemBg'),
|
||||
]
|
||||
|
||||
preloadImages(criticalImages, { useSmartCache: true })
|
||||
}
|
||||
|
||||
// 使用工具函数连接APP
|
||||
const connectToAppHandler = async () => {
|
||||
await connectToApp(() => {
|
||||
// 连接成功回调
|
||||
initData() //初始化数据
|
||||
preloadCriticalImages() //预加载关键图片
|
||||
})
|
||||
}
|
||||
|
||||
@ -622,7 +646,7 @@ onUnmounted(() => {
|
||||
.bg {
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
background-image: url(../../../assets/images/Activities/Game/bg.png);
|
||||
background-image: var(--bg-url);
|
||||
background-size: 100% auto;
|
||||
background-repeat: no-repeat;
|
||||
position: relative;
|
||||
|
||||
@ -45,6 +45,13 @@ export default defineConfig({
|
||||
cors: true, // 启用CORS
|
||||
// 代理配置(如果需要)
|
||||
proxy: {
|
||||
// OSS 代理配置
|
||||
'/oss': {
|
||||
target: 'https://tkm-likei.oss-ap-southeast-1.aliyuncs.com',
|
||||
changeOrigin: true,
|
||||
secure: true,
|
||||
rewrite: (path) => path.replace(/^\/oss/, ''),
|
||||
},
|
||||
// '/api': {
|
||||
// target: 'http://localhost:3000',
|
||||
// changeOrigin: true,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user