// /** // * 获取文件大小(Promise封装) // * @param {string} filePath // * @returns {Promise} 文件字节数 // */ // const getFileSize = (filePath) => { // return new Promise(resolve => { // uni.getFileInfo({ // filePath, // success: (res) => resolve(res.size), // fail: () => resolve(Infinity) // }); // }); // }; /** * 获取文件大小(兼容新版API) * @param {string} filePath * @returns {Promise} 文件字节数 */ const getFileSize = async (filePath) => { try { const res = await new Promise((resolve, reject) => { wx.getFileSystemManager().getFileInfo({ filePath, success: resolve, fail: reject }); }); return res.size || Infinity; } catch (err) { console.error('获取文件大小失败:', err); return Infinity; } }; /** * 执行图片压缩(Promise封装) * @param {string} src * @param {number} quality * @returns {Promise} 压缩后的临时路径 */ const doCompressImage = (src, quality) => { return new Promise(resolve => { uni.compressImage({ src, quality, success: (res) => resolve(res.tempFilePath), fail: () => resolve(src) // 失败返回原路径 }); }); }; /** * 智能图片压缩(支持多平台) * @param {string} tempFilePath - 图片临时路径 * @param {number} maxSize - 目标最大字节数(默认500KB) * @param {number} [initialQuality=80] - 初始压缩质量(1-100) * @returns {Promise} 压缩后的临时路径 */ export const compressImage = async (tempFilePath, maxSize = 500 * 1024, initialQuality = 80) => { let quality = initialQuality; let compressedPath = tempFilePath; let currentSize = Infinity; // 二分法压缩参数 let low = 10; let high = initialQuality; while (low <= high) { quality = Math.floor((low + high) / 2); // 使用提取的Promise方法 compressedPath = await doCompressImage(tempFilePath, quality); currentSize = await getFileSize(compressedPath); if (currentSize <= maxSize) { low = quality + 1; // 尝试更高质量 } else { high = quality - 1; // 降低质量 } if (high - low < 5) break; // 安全阀 } // 最终强制压缩(如果仍未达标) if (currentSize > maxSize) { compressedPath = await doCompressImage(tempFilePath, 10); } return compressedPath; }; /** * 精准图片压缩(优化版) * @param {string} tempFilePath - 图片临时路径 * @param {number} targetSize - 目标字节数(默认500KB) * @param {number} [initialQuality=85] - 初始压缩质量(1-100) * @returns {Promise<{path: string, quality: number, iterations: number, finalSize: number}>} */ export const preciseCompress = async (tempFilePath, targetSize = 500 * 1024, initialQuality = 85) => { let quality = initialQuality; let compressedPath = tempFilePath; let currentSize = Infinity; let iterations = 0; const maxIterations = 10; // 增加最大迭代次数 const history = []; // 记录每次压缩结果用于分析 // 首先检查原图大小,如果已经满足直接返回 const originalSize = await getFileSize(tempFilePath); if (originalSize <= targetSize) { return { path: tempFilePath, quality: 100, iterations: 0, finalSize: originalSize }; } // 智能预测初始质量范围(新增) const predictQualityRange = (originalSize, targetSize) => { const ratio = targetSize / originalSize; if (ratio > 0.8) return { low: 70, high: 95 }; if (ratio > 0.5) return { low: 45, high: 75 }; if (ratio > 0.3) return { low: 25, high: 50 }; if (ratio > 0.1) return { low: 10, high: 30 }; return { low: 5, high: 15 }; // 极限压缩 }; const { low: predictedLow, high: predictedHigh } = predictQualityRange(originalSize, targetSize); let low = predictedLow; let high = Math.min(predictedHigh, initialQuality); console.log(`预测质量范围: ${low}-${high} (原图${(originalSize/1024).toFixed(2)}KB → 目标${(targetSize/1024).toFixed(2)}KB)`); // 动态调整二分法边界(优化版) const adjustBounds = (currentQuality, currentSize, targetSize) => { const sizeRatio = currentSize / targetSize; const qualityDelta = Math.max(2, Math.floor(currentQuality * 0.1)); // 动态调整幅度 if (sizeRatio > 2.5) { return { lowDelta: -qualityDelta * 2, highDelta: -qualityDelta }; } else if (sizeRatio > 1.5) { return { lowDelta: -qualityDelta, highDelta: -Math.floor(qualityDelta / 2) }; } else if (sizeRatio > 1.1) { return { lowDelta: -3, highDelta: -1 }; } else { return { lowDelta: -1, highDelta: 0 }; } }; let bestResult = null; // 记录最佳结果 while (iterations < maxIterations && low <= high) { iterations++; quality = Math.floor((low + high) / 2); // 跳过已经测试过的quality值 if (history.some(item => item.quality === quality)) { break; } compressedPath = await doCompressImage(tempFilePath, quality); currentSize = await getFileSize(compressedPath); history.push({ quality, size: currentSize }); console.log(`第${iterations}次尝试: quality=${quality} → ${(currentSize / 1024).toFixed(2)}KB (目标${(targetSize/1024).toFixed(2)}KB)`); // 更新最佳结果 if (!bestResult || Math.abs(currentSize - targetSize) < Math.abs(bestResult.size - targetSize)) { bestResult = { quality, size: currentSize, path: compressedPath }; } // 精准匹配检查(容差范围调整为3%) if (Math.abs(currentSize - targetSize) < targetSize * 0.03) { console.log('✅ 达到精准匹配,提前结束'); break; } if (currentSize > targetSize) { const { lowDelta, highDelta } = adjustBounds(quality, currentSize, targetSize); high = Math.max(5, quality + highDelta); // 确保不会低于最小值 } else { low = quality + 1; // 当小于目标时,尝试更高质量 } // 收敛检查 if (high - low <= 1) break; } // 如果最佳结果仍然超标,尝试极限压缩 if (bestResult && bestResult.size > targetSize * 1.1) { console.log('🔥 尝试极限压缩...'); const extremePath = await doCompressImage(tempFilePath, 5); const extremeSize = await getFileSize(extremePath); console.log(`极限压缩结果: quality=5 → ${(extremeSize / 1024).toFixed(2)}KB`); if (extremeSize < bestResult.size) { bestResult = { quality: 5, size: extremeSize, path: extremePath }; } } const finalResult = bestResult || { quality: 10, size: currentSize, path: compressedPath }; console.log(`🎯 最终结果:quality=${finalResult.quality} → ${(finalResult.size / 1024).toFixed(2)}KB` + `(迭代${iterations}次,压缩率${((1 - finalResult.size / originalSize) * 100).toFixed(1)}%)`); return { path: finalResult.path, quality: finalResult.quality, iterations, finalSize: finalResult.size }; }; /** * 图片转Base64(兼容多平台) * @param {string} filePath - 图片临时路径 * @returns {Promise} Base64字符串(带data:image/前缀) */ export const imageToBase64 = (filePath) => { return new Promise((resolve, reject) => { // 先获取图片类型 uni.getImageInfo({ src: filePath, success: (info) => { const type = info.type || 'jpeg'; // 读取文件为Base64 uni.getFileSystemManager().readFile({ filePath, encoding: 'base64', success: (res) => resolve(`data:image/${type};base64,${res.data}`), fail: reject }); }, fail: () => { // 类型获取失败时默认jpeg uni.getFileSystemManager().readFile({ filePath, encoding: 'base64', success: (res) => resolve(`data:image/jpeg;base64,${res.data}`), fail: reject }); } }); }); }; /** * 完整处理流程(智能判断是否压缩): * 选择图片 → 检查大小 → 按需压缩 → 转Base64 * @returns {Promise<{ base64: string, path: string, compressed: boolean }>} */ export const processImage = async (maxSize = 500 * 1024) => { // 1. 选择图片 const chooseRes = await new Promise(resolve => { uni.chooseImage({ count: 1, sizeType: ['original', 'compressed'], success: resolve, fail: () => resolve(null) }); }); if (!chooseRes) throw new Error('选择图片失败'); const tempFilePath = chooseRes.tempFilePaths[0]; let finalPath = tempFilePath; let isCompressed = false; // 2. 检查文件大小 const fileSize = await getFileSize(tempFilePath); console.log(`原图大小: ${(fileSize / 1024).toFixed(2)}KB`); // 3. 按需压缩 if (fileSize > maxSize) { console.log('🔄 开始智能压缩...'); const compressResult = await preciseCompress(tempFilePath, maxSize); finalPath = compressResult.path; isCompressed = true; // 验证压缩结果 console.log(`✅ 压缩完成: ${(fileSize / 1024).toFixed(2)}KB → ${(compressResult.finalSize / 1024).toFixed(2)}KB` + ` (节省${((1 - compressResult.finalSize / fileSize) * 100).toFixed(1)}%)`); } // 4. 转换为Base64 const base64 = await imageToBase64(finalPath); console.log(`📄 Base64长度: ${base64.length} 字符`); return { base64, // Base64字符串 path: finalPath, // 最终文件路径(可能是原图或压缩图) compressed: isCompressed // 是否经过压缩 }; };