224 lines
5.2 KiB
TypeScript
224 lines
5.2 KiB
TypeScript
|
|
import { ElMessage } from "element-plus";
|
|||
|
|
import { uploadImage, uploadDocument, uploadFile } from "@/api/upload";
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 文件大小格式化
|
|||
|
|
* @param bytes 字节数
|
|||
|
|
* @returns 格式化后的字符串
|
|||
|
|
*/
|
|||
|
|
export function formatFileSize(bytes: number): string {
|
|||
|
|
if (bytes === 0) return "0 B";
|
|||
|
|
const k = 1024;
|
|||
|
|
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
|||
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|||
|
|
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取文件扩展名
|
|||
|
|
* @param filename 文件名
|
|||
|
|
* @returns 扩展名(小写)
|
|||
|
|
*/
|
|||
|
|
export function getFileExtension(filename: string): string {
|
|||
|
|
return filename
|
|||
|
|
.slice(((filename.lastIndexOf(".") - 1) >>> 0) + 2)
|
|||
|
|
.toLowerCase();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 验证文件类型
|
|||
|
|
* @param file 文件对象
|
|||
|
|
* @param allowedTypes 允许的类型数组
|
|||
|
|
* @returns 是否通过验证
|
|||
|
|
*/
|
|||
|
|
export function validateFileType(file: File, allowedTypes: string[]): boolean {
|
|||
|
|
return allowedTypes.includes(file.type);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 验证文件大小
|
|||
|
|
* @param file 文件对象
|
|||
|
|
* @param maxSizeMB 最大大小(MB)
|
|||
|
|
* @returns 是否通过验证
|
|||
|
|
*/
|
|||
|
|
export function validateFileSize(file: File, maxSizeMB: number): boolean {
|
|||
|
|
return file.size <= maxSizeMB * 1024 * 1024;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 通用上传处理函数
|
|||
|
|
* @param file 文件对象
|
|||
|
|
* @param options 配置选项
|
|||
|
|
* @returns Promise<string> 返回文件 URL
|
|||
|
|
*/
|
|||
|
|
export interface UploadOptions {
|
|||
|
|
type?: "image" | "document" | "file";
|
|||
|
|
maxSize?: number;
|
|||
|
|
allowedTypes?: string[];
|
|||
|
|
onProgress?: (progress: number) => void;
|
|||
|
|
onSuccess?: (url: string) => void;
|
|||
|
|
onError?: (error: Error) => void;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export async function handleUpload(
|
|||
|
|
file: File,
|
|||
|
|
options: UploadOptions = {}
|
|||
|
|
): Promise<string> {
|
|||
|
|
const {
|
|||
|
|
type = "file",
|
|||
|
|
maxSize,
|
|||
|
|
allowedTypes,
|
|||
|
|
onProgress,
|
|||
|
|
onSuccess,
|
|||
|
|
onError,
|
|||
|
|
} = options;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 进度回调包装
|
|||
|
|
const progressCallback = onProgress
|
|||
|
|
? (progressEvent: any) => {
|
|||
|
|
const progress = Math.round(
|
|||
|
|
(progressEvent.loaded * 100) / progressEvent.total
|
|||
|
|
);
|
|||
|
|
onProgress(progress);
|
|||
|
|
}
|
|||
|
|
: undefined;
|
|||
|
|
|
|||
|
|
let res;
|
|||
|
|
|
|||
|
|
// 根据类型选择上传方法
|
|||
|
|
switch (type) {
|
|||
|
|
case "image":
|
|||
|
|
res = await uploadImage(file, {
|
|||
|
|
maxSize,
|
|||
|
|
allowedTypes,
|
|||
|
|
onProgress: progressCallback,
|
|||
|
|
});
|
|||
|
|
break;
|
|||
|
|
case "document":
|
|||
|
|
res = await uploadDocument(file, {
|
|||
|
|
maxSize,
|
|||
|
|
allowedTypes,
|
|||
|
|
onProgress: progressCallback,
|
|||
|
|
});
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
res = await uploadFile(file, progressCallback);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const url = res.data.url;
|
|||
|
|
|
|||
|
|
if (onSuccess) {
|
|||
|
|
onSuccess(url);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return url;
|
|||
|
|
} catch (error: any) {
|
|||
|
|
if (onError) {
|
|||
|
|
onError(error);
|
|||
|
|
} else {
|
|||
|
|
ElMessage.error(error.message || "上传失败");
|
|||
|
|
}
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 图片压缩
|
|||
|
|
* @param file 图片文件
|
|||
|
|
* @param maxWidth 最大宽度
|
|||
|
|
* @param quality 压缩质量 0-1
|
|||
|
|
* @returns Promise<File> 压缩后的文件
|
|||
|
|
*/
|
|||
|
|
export function compressImage(
|
|||
|
|
file: File,
|
|||
|
|
maxWidth = 1920,
|
|||
|
|
quality = 0.8
|
|||
|
|
): Promise<File> {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
const reader = new FileReader();
|
|||
|
|
reader.readAsDataURL(file);
|
|||
|
|
|
|||
|
|
reader.onload = (e) => {
|
|||
|
|
const img = new Image();
|
|||
|
|
img.src = e.target?.result as string;
|
|||
|
|
|
|||
|
|
img.onload = () => {
|
|||
|
|
const canvas = document.createElement("canvas");
|
|||
|
|
let width = img.width;
|
|||
|
|
let height = img.height;
|
|||
|
|
|
|||
|
|
// 计算缩放比例
|
|||
|
|
if (width > maxWidth) {
|
|||
|
|
height = (height * maxWidth) / width;
|
|||
|
|
width = maxWidth;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
canvas.width = width;
|
|||
|
|
canvas.height = height;
|
|||
|
|
|
|||
|
|
const ctx = canvas.getContext("2d");
|
|||
|
|
ctx?.drawImage(img, 0, 0, width, height);
|
|||
|
|
|
|||
|
|
canvas.toBlob(
|
|||
|
|
(blob) => {
|
|||
|
|
if (blob) {
|
|||
|
|
const compressedFile = new File([blob], file.name, {
|
|||
|
|
type: file.type,
|
|||
|
|
lastModified: Date.now(),
|
|||
|
|
});
|
|||
|
|
resolve(compressedFile);
|
|||
|
|
} else {
|
|||
|
|
reject(new Error("图片压缩失败"));
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
file.type,
|
|||
|
|
quality
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
img.onerror = () => {
|
|||
|
|
reject(new Error("图片加载失败"));
|
|||
|
|
};
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
reader.onerror = () => {
|
|||
|
|
reject(new Error("文件读取失败"));
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Base64 转 File
|
|||
|
|
* @param base64 base64 字符串
|
|||
|
|
* @param filename 文件名
|
|||
|
|
* @returns File 对象
|
|||
|
|
*/
|
|||
|
|
export function base64ToFile(base64: string, filename: string): File {
|
|||
|
|
const arr = base64.split(",");
|
|||
|
|
const mime = arr[0].match(/:(.*?);/)?.[1] || "image/png";
|
|||
|
|
const bstr = atob(arr[1]);
|
|||
|
|
let n = bstr.length;
|
|||
|
|
const u8arr = new Uint8Array(n);
|
|||
|
|
|
|||
|
|
while (n--) {
|
|||
|
|
u8arr[n] = bstr.charCodeAt(n);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return new File([u8arr], filename, { type: mime });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* File 转 Base64
|
|||
|
|
* @param file 文件对象
|
|||
|
|
* @returns Promise<string> base64 字符串
|
|||
|
|
*/
|
|||
|
|
export function fileToBase64(file: File): Promise<string> {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
const reader = new FileReader();
|
|||
|
|
reader.readAsDataURL(file);
|
|||
|
|
reader.onload = () => resolve(reader.result as string);
|
|||
|
|
reader.onerror = (error) => reject(error);
|
|||
|
|
});
|
|||
|
|
}
|