add:新增保存功能
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user