286 lines
7.8 KiB
JavaScript
286 lines
7.8 KiB
JavaScript
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<void>}
|
|
*/
|
|
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
|
|
} |