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);
|
||
});
|
||
}
|