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">
|
<script setup lang="ts">
|
||||||
import { dialog, toast } from 'sard-uniapp'
|
import { dialog } from 'sard-uniapp'
|
||||||
import { useUserStore } from '@/store'
|
import { useUserStore } from '@/store'
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|||||||
@@ -1,103 +1,84 @@
|
|||||||
|
import { toast } from 'sard-uniapp'
|
||||||
import { getaliyunkeyApi } from '@/request/api'
|
import { getaliyunkeyApi } from '@/request/api'
|
||||||
import { useUserStore } from '@/store'
|
import { useUserStore } from '@/store'
|
||||||
import { isH5 } from './platform'
|
import { isApp, isH5, isMpWeixin } from './platform'
|
||||||
|
|
||||||
const aliyunBucket = import.meta.env.VITE_ALIYUN_BUCKET
|
const aliyunBucket = import.meta.env.VITE_ALIYUN_BUCKET
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 压缩图片(多端通用)
|
* 压缩图片(多端通用)
|
||||||
* @param url 图片路径
|
* @param url 图片路径
|
||||||
* @param name 图片名称
|
|
||||||
* @returns Promise<{ path: string, size: number }>
|
* @returns Promise<{ path: string, size: number }>
|
||||||
*/
|
*/
|
||||||
export async function imageCompress(url: string): Promise<{ path: string, size: number }> {
|
export async function imageCompress(url: string): Promise<{ path: string, size: number }> {
|
||||||
// #ifndef H5
|
if (isH5) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
uni.compressImage({
|
const img = new Image()
|
||||||
src: url,
|
img.crossOrigin = 'Anonymous'
|
||||||
quality: 80,
|
img.src = url
|
||||||
success: async (res) => {
|
|
||||||
|
img.onload = () => {
|
||||||
try {
|
try {
|
||||||
// 获取文件信息
|
const canvas = document.createElement('canvas')
|
||||||
const fileInfo = await uni.getFileInfo({ filePath: res.tempFilePath })
|
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({
|
resolve({
|
||||||
path: res.tempFilePath,
|
path: base64,
|
||||||
size: fileInfo.size,
|
size: sizeInBytes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
reject(e)
|
reject(e)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
fail: reject,
|
|
||||||
|
img.onerror = reject
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
// #endif
|
else {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
// #ifdef H5
|
uni.compressImage({
|
||||||
return new Promise((resolve, reject) => {
|
src: url,
|
||||||
const img = new Image()
|
quality: 80,
|
||||||
img.crossOrigin = 'Anonymous'
|
success: async (res) => {
|
||||||
img.src = url
|
try {
|
||||||
|
// 获取文件信息
|
||||||
img.onload = () => {
|
const fileInfo = await uni.getFileInfo({ filePath: res.tempFilePath })
|
||||||
try {
|
resolve({
|
||||||
const canvas = document.createElement('canvas')
|
path: res.tempFilePath,
|
||||||
const ctx = canvas.getContext('2d')!
|
size: fileInfo.size,
|
||||||
const maxSize = 600
|
})
|
||||||
let { width, height } = img
|
}
|
||||||
|
catch (e) {
|
||||||
// 按比例压缩
|
reject(e)
|
||||||
if (width > height && width > maxSize) {
|
}
|
||||||
height = (maxSize * height) / width
|
},
|
||||||
width = maxSize
|
fail: reject,
|
||||||
}
|
})
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件大小格式化
|
// 文件大小格式化
|
||||||
@@ -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) {
|
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) {
|
if (isH5) {
|
||||||
// H5 平台下载逻辑
|
downloadH5(fileUrl, finalName)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
else {
|
else if (isMpWeixin) {
|
||||||
// APP / 小程序下载逻辑
|
downloadMpWeixin(fileUrl, type, finalName)
|
||||||
uni.downloadFile({
|
}
|
||||||
url: fileUrl,
|
else if (isApp) {
|
||||||
success: (response) => {
|
downloadApp(fileUrl, type, finalName)
|
||||||
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}%`)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** =================== 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