/** * 资源管理器 - 处理图片预加载、重试和错误处理 * * 功能: * - 图片预加载 * - 自动重试机制 (最多3次) * - 加载状态跟踪 * - 错误处理和降级 * - 占位符支持 */ const DEFAULT_RETRY_COUNT = 3 const DEFAULT_TIMEOUT = 10000 // 10秒超时 // 使用 data URI 作为占位符 - 一个简单的灰色方块 const PLACEHOLDER_IMAGE = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI2U1ZTVlNSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTQiIGZpbGw9IiM5OTkiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7lm77niYfkuI3lj6/nlKg8L3RleHQ+PC9zdmc+' /** * 资源加载状态 */ const LoadStatus = { PENDING: 'pending', LOADING: 'loading', SUCCESS: 'success', FAILED: 'failed', TIMEOUT: 'timeout' } /** * 资源缓存 */ const resourceCache = new Map() /** * 预加载单个图片资源 * @param {string} src - 图片路径 * @param {Object} options - 配置选项 * @param {number} options.maxRetries - 最大重试次数 * @param {number} options.timeout - 超时时间(毫秒) * @param {boolean} options.usePlaceholder - 失败时是否使用占位符 * @returns {Promise} 加载结果 */ export async function preloadImage(src, options = {}) { const { maxRetries = DEFAULT_RETRY_COUNT, timeout = DEFAULT_TIMEOUT, usePlaceholder = true } = options // 检查缓存 if (resourceCache.has(src)) { const cached = resourceCache.get(src) if (cached.status === LoadStatus.SUCCESS) { console.log(`[资源管理器] 使用缓存: ${src}`) return cached } } let lastError = null let retryCount = 0 // 重试循环 while (retryCount <= maxRetries) { try { console.log(`[资源管理器] 加载资源 (尝试 ${retryCount + 1}/${maxRetries + 1}): ${src}`) const result = await loadImageWithTimeout(src, timeout) // 加载成功,缓存结果 const successResult = { src, status: LoadStatus.SUCCESS, width: result.width, height: result.height, path: result.path, retryCount, timestamp: Date.now() } resourceCache.set(src, successResult) console.log(`[资源管理器] 加载成功: ${src}`) return successResult } catch (error) { lastError = error retryCount++ console.warn(`[资源管理器] 加载失败 (尝试 ${retryCount}/${maxRetries + 1}): ${src}`, error) // 如果还有重试机会,等待后重试 if (retryCount <= maxRetries) { const delay = Math.min(1000 * Math.pow(2, retryCount - 1), 5000) // 指数退避,最多5秒 console.log(`[资源管理器] ${delay}ms 后重试...`) await sleep(delay) } } } // 所有重试都失败 console.error(`[资源管理器] 资源加载最终失败: ${src}`, lastError) const failedResult = { src, status: LoadStatus.FAILED, error: lastError, retryCount, timestamp: Date.now(), placeholder: usePlaceholder ? PLACEHOLDER_IMAGE : null } resourceCache.set(src, failedResult) // 如果启用占位符,返回占位符路径 if (usePlaceholder) { return { ...failedResult, src: PLACEHOLDER_IMAGE, usedPlaceholder: true } } return failedResult } /** * 带超时的图片加载 * @param {string} src - 图片路径 * @param {number} timeout - 超时时间 * @returns {Promise} */ function loadImageWithTimeout(src, timeout) { return new Promise((resolve, reject) => { const timer = setTimeout(() => { reject(new Error(`加载超时: ${src}`)) }, timeout) uni.getImageInfo({ src: src, success: (res) => { clearTimeout(timer) resolve(res) }, fail: (error) => { clearTimeout(timer) reject(error) } }) }) } /** * 批量预加载图片资源 * @param {Array} sources - 图片路径数组 * @param {Object} options - 配置选项 * @param {Function} options.onProgress - 进度回调 * @param {boolean} options.parallel - 是否并行加载 * @returns {Promise} 加载结果数组 */ export async function preloadImages(sources, options = {}) { const { onProgress = null, parallel = true, ...imageOptions } = options if (!sources || sources.length === 0) { return [] } console.log(`[资源管理器] 开始批量加载 ${sources.length} 个资源`) const results = [] let loadedCount = 0 if (parallel) { // 并行加载 const promises = sources.map(async (src) => { const result = await preloadImage(src, imageOptions) loadedCount++ if (onProgress) { onProgress({ loaded: loadedCount, total: sources.length, progress: (loadedCount / sources.length) * 100, currentSrc: src, currentResult: result }) } return result }) const settled = await Promise.allSettled(promises) results.push(...settled.map(r => r.status === 'fulfilled' ? r.value : r.reason)) } else { // 串行加载 for (const src of sources) { const result = await preloadImage(src, imageOptions) results.push(result) loadedCount++ if (onProgress) { onProgress({ loaded: loadedCount, total: sources.length, progress: (loadedCount / sources.length) * 100, currentSrc: src, currentResult: result }) } } } const successCount = results.filter(r => r.status === LoadStatus.SUCCESS).length const failedCount = results.filter(r => r.status === LoadStatus.FAILED).length console.log(`[资源管理器] 批量加载完成: 成功 ${successCount}, 失败 ${failedCount}`) return results } /** * 预加载活动配置相关的所有资源 * @param {Object} config - 活动配置对象 * @param {Object} options - 配置选项 * @returns {Promise} 加载结果统计 */ export async function preloadActivityAssets(config, options = {}) { if (!config) { throw new Error('配置对象不能为空') } // 收集所有需要预加载的资源 const sources = [ config.bgImage, config.bannerImage, config.mainAssetIdle, config.mainAssetActive ].filter(Boolean) // 过滤掉 null/undefined console.log(`[资源管理器] 预加载活动资源: ${config.title}`) const results = await preloadImages(sources, options) // 统计结果 const stats = { total: results.length, success: results.filter(r => r.status === LoadStatus.SUCCESS).length, failed: results.filter(r => r.status === LoadStatus.FAILED).length, usedPlaceholder: results.filter(r => r.usedPlaceholder).length, results } return stats } /** * 清除资源缓存 * @param {string} src - 可选,指定要清除的资源路径 */ export function clearResourceCache(src = null) { if (src) { resourceCache.delete(src) console.log(`[资源管理器] 清除缓存: ${src}`) } else { resourceCache.clear() console.log(`[资源管理器] 清除所有缓存`) } } /** * 获取资源缓存状态 * @param {string} src - 资源路径 * @returns {Object|null} */ export function getResourceStatus(src) { return resourceCache.get(src) || null } /** * 检查资源是否已加载 * @param {string} src - 资源路径 * @returns {boolean} */ export function isResourceLoaded(src) { const status = resourceCache.get(src) return status && status.status === LoadStatus.SUCCESS } /** * 辅助函数: 延迟 * @param {number} ms - 毫秒 * @returns {Promise} */ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)) } /** * 导出常量 */ export { LoadStatus, PLACEHOLDER_IMAGE }