import { showMsg } from "@/utils/showMsg"; /** * 全环境通用文件保存工具 * 支持:PC浏览器、手机浏览器、uniapp webview * 支持文件类型:图片、视频、纯文字 */ // 文件类型枚举 export const FileType = { IMAGE: 'image', VIDEO: 'video', TEXT: 'text' } /** * 检测是否在uniapp webview环境中 */ const isUniAppWebview = () => { return typeof uni !== 'undefined' && typeof uni.downloadFile === 'function' && typeof uni.saveImageToPhotosAlbum === 'function' } /** * 检测是否是iOS设备 */ const isIOS = () => { return /iPhone|iPad|iPod/i.test(navigator.userAgent) } /** * 通用保存文件入口(所有环境通用) * @param {Object} options 保存选项 * @param {string} options.type 文件类型:FileType.IMAGE / FileType.VIDEO / FileType.TEXT * @param {string} options.url 文件URL(图片/视频必填) * @param {string} options.content 文字内容(文字类型必填) * @param {string} options.fileName 保存的文件名(可选,自动生成) * @returns {Promise} */ export const saveFile = async (options) => { const { type, url, content, fileName } = options // 参数校验 if (!type) { throw new Error('请指定文件类型') } if ((type === FileType.IMAGE || type === FileType.VIDEO) && !url) { throw new Error('请提供文件URL') } if (type === FileType.TEXT && !content) { throw new Error('请提供文字内容') } // 生成默认文件名 const timestamp = Date.now() const defaultFileName = `file_${timestamp}` const finalFileName = fileName || defaultFileName // 自动选择保存方式 if (isUniAppWebview()) { await saveInUniApp(type, url, content, finalFileName) } else { await saveInBrowser(type, url, content, finalFileName) } } /** * uniapp webview环境保存 */ const saveInUniApp = async (type, url, content, fileName) => { switch (type) { case FileType.IMAGE: await uniSaveImage(url) break case FileType.VIDEO: await uniSaveVideo(url) break case FileType.TEXT: // webview无法直接保存文件,降级为复制到剪贴板 await uniCopyText(content) break default: throw new Error('不支持的文件类型') } } /** * 浏览器环境保存 */ const saveInBrowser = async (type, url, content, fileName) => { switch (type) { case FileType.IMAGE: await browserSaveImage(url, fileName) break case FileType.VIDEO: await browserSaveVideo(url, fileName) break case FileType.TEXT: browserSaveText(content, fileName) break default: throw new Error('不支持的文件类型') } } // ==================== uniapp保存方法 ==================== const uniSaveImage = (url) => { return new Promise((resolve, reject) => { uni.showLoading({ title: '保存中...' }) uni.downloadFile({ url, success: (res) => { if (res.statusCode !== 200) { reject(new Error('图片下载失败')) return } uni.saveImageToPhotosAlbum({ filePath: res.tempFilePath, success: () => resolve(), fail: (err) => { if (err.errMsg.includes('auth')) { reject(new Error('请在设置中开启相册权限后重试')) } else if (err.errMsg.includes('cancel')) { reject(new Error('已取消保存')) } else { reject(new Error('保存图片失败')) } } }) }, fail: () => reject(new Error('网络请求失败,请检查网络连接')), complete: () => uni.hideLoading() }) }) } const uniSaveVideo = (url) => { return new Promise((resolve, reject) => { uni.showLoading({ title: '保存中...' }) uni.downloadFile({ url, success: (res) => { if (res.statusCode !== 200) { reject(new Error('视频下载失败')) return } uni.saveVideoToPhotosAlbum({ filePath: res.tempFilePath, success: () => resolve(), fail: (err) => { if (err.errMsg.includes('auth')) { reject(new Error('请在设置中开启相册权限后重试')) } else if (err.errMsg.includes('cancel')) { reject(new Error('已取消保存')) } else { reject(new Error('保存视频失败')) } } }) }, fail: () => reject(new Error('网络请求失败,请检查网络连接')), complete: () => uni.hideLoading() }) }) } const uniCopyText = (text) => { return new Promise((resolve, reject) => { uni.setClipboardData({ data: text, success: () => { uni.showToast({ title: '文案已复制到剪贴板', icon: 'success' }) resolve() }, fail: () => reject(new Error('复制失败')) }) }) } // ==================== 浏览器保存方法 ==================== const browserSaveImage = (url, fileName) => { return new Promise((resolve, reject) => { const img = new Image() img.crossOrigin = 'anonymous' img.onload = () => { try { const canvas = document.createElement('canvas') canvas.width = img.naturalWidth canvas.height = img.naturalHeight const ctx = canvas.getContext('2d') if (!ctx) { // Canvas不支持,降级为打开新标签页 openInNewTab(url) resolve() return } ctx.drawImage(img, 0, 0) canvas.toBlob((blob) => { if (!blob) { openInNewTab(url) resolve() return } downloadBlob(blob, `${fileName}.png`) resolve() }, 'image/png') } catch (e) { // 发生错误,降级为打开新标签页 openInNewTab(url) resolve() } } img.onerror = () => { openInNewTab(url) resolve() } img.src = url }) } const browserSaveVideo = async (url, fileName) => { try { // iOS Safari不支持Blob下载视频,直接打开新标签页 if (isIOS()) { openInNewTab(url) return } const response = await fetch(url) if (!response.ok) { throw new Error(`视频加载失败:${response.status}`) } const blob = await response.blob() downloadBlob(blob, `${fileName}.mp4`) } catch (error) { // 发生错误,降级为打开新标签页 openInNewTab(url) } } const browserSaveText = (text, fileName) => { const blob = new Blob([text], { type: 'text/plain;charset=utf-8' }) downloadBlob(blob, `${fileName}.txt`) } // ==================== 通用工具方法 ==================== const downloadBlob = (blob, fileName) => { const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = fileName a.style.display = 'none' document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) showMsg("保存成功"); } const openInNewTab = (url) => { const a = document.createElement('a') a.href = url a.target = '_blank' a.rel = 'noopener noreferrer' a.style.display = 'none' document.body.appendChild(a) a.click() document.body.removeChild(a) } // 导出默认对象,方便使用 export default { saveFile, FileType }