y
This commit is contained in:
1 parent
349db76d3d
commit
18a0a81c20
2 files changed
+292
-120
No files matched your search
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { dialog, toast } from 'sard-uniapp'
|
||||
import { dialog } from 'sard-uniapp'
|
||||
import { useUserStore } from '@/store'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
@@ -1,103 +1,84 @@
|
||||
import { toast } from 'sard-uniapp'
|
||||
import { getaliyunkeyApi } from '@/request/api'
|
||||
import { useUserStore } from '@/store'
|
||||
import { isH5 } from './platform'
|
||||
import { isApp, isH5, isMpWeixin } from './platform'
|
||||
|
||||
const aliyunBucket = import.meta.env.VITE_ALIYUN_BUCKET
|
||||
|
||||
/**
|
||||
* 压缩图片(多端通用)
|
||||
* @param url 图片路径
|
||||
* @param name 图片名称
|
||||
* @returns Promise<{ path: string, size: number }>
|
||||
*/
|
||||
export async function imageCompress(url: string): Promise<{ path: string, size: number }> {
|
||||
// #ifndef H5
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.compressImage({
|
||||
src: url,
|
||||
quality: 80,
|
||||
success: async (res) => {
|
||||
if (isH5) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
img.crossOrigin = 'Anonymous'
|
||||
img.src = url
|
||||
|
||||
img.onload = () => {
|
||||
try {
|
||||
// 获取文件信息
|
||||
const fileInfo = await uni.getFileInfo({ filePath: res.tempFilePath })
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d')!
|
||||
const maxSize = 600
|
||||
let { width, height } = img
|
||||
|
||||
// 按比例压缩
|
||||
if (width > height && width > maxSize) {
|
||||
height = (maxSize * height) / width
|
||||
width = maxSize
|
||||
}
|
||||
else if (height > width && height > maxSize) {
|
||||
width = (maxSize * width) / height
|
||||
height = maxSize
|
||||
}
|
||||
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
ctx.drawImage(img, 0, 0, width, height)
|
||||
|
||||
const base64 = canvas.toDataURL('image/jpeg', 0.8)
|
||||
|
||||
// 计算 Base64 大小
|
||||
const sizeInBytes = Math.round((base64.length - (base64.indexOf(',') + 1)) * 0.75)
|
||||
|
||||
resolve({
|
||||
path: res.tempFilePath,
|
||||
size: fileInfo.size,
|
||||
path: base64,
|
||||
size: sizeInBytes,
|
||||
})
|
||||
}
|
||||
catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
},
|
||||
fail: reject,
|
||||
}
|
||||
|
||||
img.onerror = reject
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
img.crossOrigin = 'Anonymous'
|
||||
img.src = url
|
||||
|
||||
img.onload = () => {
|
||||
try {
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d')!
|
||||
const maxSize = 600
|
||||
let { width, height } = img
|
||||
|
||||
// 按比例压缩
|
||||
if (width > height && width > maxSize) {
|
||||
height = (maxSize * height) / width
|
||||
width = maxSize
|
||||
}
|
||||
else if (height > width && height > maxSize) {
|
||||
width = (maxSize * width) / height
|
||||
height = maxSize
|
||||
}
|
||||
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
ctx.drawImage(img, 0, 0, width, height)
|
||||
|
||||
const base64 = canvas.toDataURL('image/jpeg', 0.8)
|
||||
|
||||
// 计算 Base64 大小
|
||||
const sizeInBytes = Math.round((base64.length - (base64.indexOf(',') + 1)) * 0.75)
|
||||
|
||||
resolve({
|
||||
path: base64,
|
||||
size: sizeInBytes,
|
||||
})
|
||||
}
|
||||
catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
img.onerror = reject
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断文件名是否为图片类型
|
||||
* @param fileName 文件名(如 "photo.png"、"avatar.JPG")
|
||||
*/
|
||||
export function isImage(fileName: string): boolean {
|
||||
if (!fileName || typeof fileName !== 'string')
|
||||
return false
|
||||
|
||||
// 常见图片扩展名(全小写)
|
||||
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'heic']
|
||||
|
||||
// 取文件名的后缀
|
||||
const ext = fileName.split('.').pop()?.toLowerCase()
|
||||
|
||||
// 检查是否在图片后缀列表中
|
||||
return !!ext && imageExtensions.includes(ext)
|
||||
}
|
||||
else {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.compressImage({
|
||||
src: url,
|
||||
quality: 80,
|
||||
success: async (res) => {
|
||||
try {
|
||||
// 获取文件信息
|
||||
const fileInfo = await uni.getFileInfo({ filePath: res.tempFilePath })
|
||||
resolve({
|
||||
path: res.tempFilePath,
|
||||
size: fileInfo.size,
|
||||
})
|
||||
}
|
||||
catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
},
|
||||
fail: reject,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 文件大小格式化
|
||||
@@ -178,47 +159,238 @@ export async function uploadFileToAliyun(
|
||||
})
|
||||
})
|
||||
}
|
||||
/** =================== 工具函数 =================== */
|
||||
|
||||
/** 获取文件名:从 URL 提取最后一段 */
|
||||
function getFileName(fileUrl: string) {
|
||||
return decodeURIComponent(fileUrl.split('/').pop() || '未命名文件')
|
||||
}
|
||||
|
||||
/** 获取文件后缀 */
|
||||
export function getFileExtension(fileUrl: string) {
|
||||
const clean = fileUrl.split('?')[0].split('#')[0].trim()
|
||||
const lastDot = clean.lastIndexOf('.')
|
||||
return lastDot === -1 ? '' : clean.substring(lastDot + 1).toLowerCase()
|
||||
}
|
||||
|
||||
/** 文件类型判断 */
|
||||
export function isImage(fileUrl: string) {
|
||||
return ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(getFileExtension(fileUrl))
|
||||
}
|
||||
|
||||
export function isVideo(fileUrl: string) {
|
||||
return ['mp4', 'avi', 'mov', 'mkv', 'flv', 'wmv', '3gp'].includes(getFileExtension(fileUrl))
|
||||
}
|
||||
|
||||
/** =================== 下载入口 =================== */
|
||||
export function downloadFile(fileUrl: string, fileName?: string) {
|
||||
const type: 'image' | 'video' | 'file' = isImage(fileUrl)
|
||||
? 'image'
|
||||
: isVideo(fileUrl)
|
||||
? 'video'
|
||||
: 'file'
|
||||
|
||||
const finalName = fileName ?? getFileName(fileUrl)
|
||||
|
||||
if (isH5) {
|
||||
// H5 平台下载逻辑
|
||||
const name = fileName || fileUrl.split('/').pop() || 'file'
|
||||
const a = document.createElement('a')
|
||||
a.href = fileUrl
|
||||
a.download = name
|
||||
a.target = '_blank'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
console.log('H5 文件下载已触发:', name)
|
||||
downloadH5(fileUrl, finalName)
|
||||
}
|
||||
else {
|
||||
// APP / 小程序下载逻辑
|
||||
uni.downloadFile({
|
||||
url: fileUrl,
|
||||
success: (response) => {
|
||||
if (response.statusCode === 200 && response.tempFilePath) {
|
||||
uni.saveFile({
|
||||
tempFilePath: response.tempFilePath,
|
||||
success: (resData) => {
|
||||
console.log('保存路径', resData.savedFilePath)
|
||||
uni.showToast({ title: '下载成功' })
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error('保存失败', error)
|
||||
},
|
||||
})
|
||||
}
|
||||
else {
|
||||
console.error('下载失败或 tempFilePath 不存在', response)
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('downloadFile 失败', err)
|
||||
},
|
||||
progress: (res) => {
|
||||
console.log(`下载进度: ${res.progress}%`)
|
||||
},
|
||||
})
|
||||
else if (isMpWeixin) {
|
||||
downloadMpWeixin(fileUrl, type, finalName)
|
||||
}
|
||||
else if (isApp) {
|
||||
downloadApp(fileUrl, type, finalName)
|
||||
}
|
||||
}
|
||||
|
||||
/** =================== H5 下载 =================== */
|
||||
async function downloadH5(fileUrl: string, fileName: string) {
|
||||
try {
|
||||
const response = await fetch(fileUrl, { mode: 'cors' })
|
||||
if (!response.ok)
|
||||
throw new Error(`下载失败: ${response.status}`)
|
||||
|
||||
const blob = await response.blob()
|
||||
const blobUrl = window.URL.createObjectURL(blob)
|
||||
|
||||
const a = document.createElement('a')
|
||||
a.href = blobUrl
|
||||
a.download = fileName
|
||||
a.style.display = 'none'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
|
||||
window.URL.revokeObjectURL(blobUrl)
|
||||
a.remove()
|
||||
toast.success(`文件下载成功:${fileName}`)
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
toast.fail('下载失败')
|
||||
}
|
||||
}
|
||||
|
||||
/** =================== 微信小程序 下载 =================== */
|
||||
function downloadMpWeixin(fileUrl: string, type: 'image' | 'video' | 'file', fileName: string) {
|
||||
if (type === 'image' || type === 'video') {
|
||||
downloadMpWeixinMedia(fileUrl, type)
|
||||
}
|
||||
else {
|
||||
downloadMpWeixinFile(fileUrl, fileName)
|
||||
}
|
||||
}
|
||||
|
||||
function downloadMpWeixinMedia(fileUrl: string, type: 'image' | 'video') {
|
||||
uni.showLoading({ mask: true, title: '下载中...' })
|
||||
|
||||
const task = uni.downloadFile({
|
||||
url: fileUrl,
|
||||
success: (res) => {
|
||||
if (res.statusCode !== 200) {
|
||||
uni.hideLoading()
|
||||
toast.fail('下载失败')
|
||||
return
|
||||
}
|
||||
|
||||
const saveOptions = {
|
||||
filePath: res.tempFilePath,
|
||||
success: () => toast.success('下载成功'),
|
||||
fail: () => toast.fail('保存失败'),
|
||||
complete: () => uni.hideLoading(),
|
||||
}
|
||||
|
||||
type === 'image'
|
||||
? uni.saveImageToPhotosAlbum(saveOptions)
|
||||
: uni.saveVideoToPhotosAlbum(saveOptions)
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading()
|
||||
toast.fail('下载失败')
|
||||
},
|
||||
})
|
||||
|
||||
task.onProgressUpdate((res) => {
|
||||
toast.loading(`正在下载 ${res.progress}%`)
|
||||
})
|
||||
}
|
||||
|
||||
function downloadMpWeixinFile(fileUrl: string, fileName: string) {
|
||||
toast.loading('下载中...')
|
||||
const task = uni.downloadFile({
|
||||
url: fileUrl,
|
||||
success: (res) => {
|
||||
uni.shareFileMessage({
|
||||
filePath: res.tempFilePath,
|
||||
fileName,
|
||||
success: () => {
|
||||
toast.hide()
|
||||
toast.success('下载成功')
|
||||
},
|
||||
fail: () => {
|
||||
toast.hide()
|
||||
toast.fail('下载失败')
|
||||
},
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
toast.hide()
|
||||
toast.fail('下载失败')
|
||||
},
|
||||
})
|
||||
|
||||
task.onProgressUpdate(res => toast.loading(`正在下载 ${res.progress}%`))
|
||||
}
|
||||
|
||||
/** =================== App 下载 =================== */
|
||||
function downloadApp(fileUrl: string, type: 'image' | 'video' | 'file', fileName: string) {
|
||||
if (type === 'image' || type === 'video') {
|
||||
downloadAppMedia(fileUrl, type)
|
||||
}
|
||||
else {
|
||||
downloadAppFile(fileUrl, fileName)
|
||||
}
|
||||
}
|
||||
|
||||
// 普通文件下载并打开
|
||||
function downloadAppFile(fileUrl: string, fileName: string) {
|
||||
const downloadTask = plus.downloader.createDownload(
|
||||
fileUrl,
|
||||
{ filename: `_downloads/${fileName}` },
|
||||
(d, status) => {
|
||||
if (status === 200) {
|
||||
toast('下载完成')
|
||||
plus.runtime.openFile(d.filename)
|
||||
}
|
||||
else {
|
||||
toast('下载失败')
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
try {
|
||||
downloadTask.start()
|
||||
const showLoading = plus.nativeUI.showWaiting('正在下载')
|
||||
|
||||
downloadTask.addEventListener('statechanged', (task) => {
|
||||
switch (task.state) {
|
||||
case 1: {
|
||||
showLoading.setTitle('正在下载')
|
||||
break
|
||||
}
|
||||
case 2: {
|
||||
showLoading.setTitle('已连接到服务器')
|
||||
break
|
||||
}
|
||||
case 3: {
|
||||
const progress = Math.floor((task.downloadedSize / task.totalSize) * 100)
|
||||
showLoading.setTitle(`正在下载 ${progress}%`)
|
||||
break
|
||||
}
|
||||
case 4: {
|
||||
plus.nativeUI.closeWaiting()
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err)
|
||||
plus.nativeUI.closeWaiting()
|
||||
toast('下载失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 图片/视频下载并保存到相册
|
||||
function downloadAppMedia(fileUrl: string, type: 'image' | 'video') {
|
||||
const showLoading = plus.nativeUI.showWaiting('正在下载')
|
||||
|
||||
const task = plus.downloader.createDownload(
|
||||
fileUrl,
|
||||
{ filename: `_downloads/${new Date().valueOf()}` },
|
||||
(d, status) => {
|
||||
if (status !== 200) {
|
||||
toast.fail('下载失败')
|
||||
plus.nativeUI.closeWaiting()
|
||||
return
|
||||
}
|
||||
|
||||
const saveFunc = type === 'image' ? plus.gallery.save : plus.gallery.save
|
||||
// plus.gallery.save 会根据文件类型自动保存
|
||||
saveFunc(d.filename, () => {
|
||||
toast.success('下载成功')
|
||||
}, () => {
|
||||
toast.fail('保存失败')
|
||||
})
|
||||
|
||||
plus.nativeUI.closeWaiting()
|
||||
},
|
||||
)
|
||||
|
||||
task.start()
|
||||
task.addEventListener('statechanged', (t) => {
|
||||
if (t.state === 3) {
|
||||
const progress = Math.floor((t.downloadedSize / t.totalSize) * 100)
|
||||
showLoading.setTitle(`正在下载 ${progress}%`)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user