This commit is contained in:
oneao committed 2025-10-09 20:47:49 +08:00
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}%`)
}
})
}