import { Injectable } from '@angular/core'; import { NovaUploadService as FmodeUploadService } from 'fmode-ng/storage'; /** * 文件上传服务 * 用于处理图片、视频、文档等文件的上传 * 基于 fmode-ng 的 NovaUploadService */ @Injectable({ providedIn: 'root' }) export class ProjectUploadService { constructor(private uploadServ: FmodeUploadService) {} /** * 上传单个文件 * @param file File对象 * @param options 上传选项 * @returns 上传后的文件URL */ async uploadFile( file: File, options?: { onProgress?: (progress: number) => void; compress?: boolean; maxWidth?: number; maxHeight?: number; } ): Promise { try { let fileToUpload = file; // 如果是图片且需要压缩 if (options?.compress && file.type.startsWith('image/')) { fileToUpload = await this.compressImage(file, { maxWidth: options.maxWidth || 1920, maxHeight: options.maxHeight || 1920, quality: 0.8 }); } // 使用 fmode-ng 的上传服务 const fileResult = await this.uploadServ.upload(fileToUpload, (progress: any) => { if (options?.onProgress && progress?.percent) { options.onProgress(progress.percent); } }); // 返回文件URL return fileResult.url || ''; } catch (error: any) { console.error('文件上传失败:', error); throw new Error('文件上传失败: ' + (error?.message || '未知错误')); } } /** * 批量上传文件 * @param files File数组 * @param options 上传选项 * @returns 上传后的文件URL数组 */ async uploadFiles( files: File[], options?: { onProgress?: (current: number, total: number) => void; compress?: boolean; } ): Promise { const urls: string[] = []; const total = files.length; for (let i = 0; i < files.length; i++) { const url = await this.uploadFile(files[i], { compress: options?.compress }); urls.push(url); options?.onProgress?.(i + 1, total); } return urls; } /** * 压缩图片 * @param file 原始文件 * @param options 压缩选项 * @returns 压缩后的File对象 */ private compressImage( file: File, options: { maxWidth: number; maxHeight: number; quality: number; } ): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => { const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); let { width, height } = img; // 计算压缩后的尺寸 if (width > options.maxWidth || height > options.maxHeight) { const ratio = Math.min( options.maxWidth / width, options.maxHeight / height ); width = width * ratio; height = height * ratio; } 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, options.quality ); }; img.onerror = () => reject(new Error('图片加载失败')); img.src = e.target?.result as string; }; reader.onerror = () => reject(new Error('文件读取失败')); reader.readAsDataURL(file); }); } /** * 生成缩略图 * @param file 原始图片文件 * @param size 缩略图尺寸 * @returns 缩略图URL */ async generateThumbnail(file: File, size: number = 200): Promise { const thumbnailFile = await this.compressImage(file, { maxWidth: size, maxHeight: size, quality: 0.7 }); return await this.uploadFile(thumbnailFile); } /** * 从URL下载文件 * @param url 文件URL * @param filename 保存的文件名 */ async downloadFile(url: string, filename: string): Promise { try { const response = await fetch(url); const blob = await response.blob(); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = filename; link.click(); URL.revokeObjectURL(link.href); } catch (error) { console.error('文件下载失败:', error); throw new Error('文件下载失败'); } } /** * 验证文件类型 * @param file 文件 * @param allowedTypes 允许的MIME类型数组 * @returns 是否合法 */ validateFileType(file: File, allowedTypes: string[]): boolean { return allowedTypes.some(type => { if (type.endsWith('/*')) { const prefix = type.replace('/*', ''); return file.type.startsWith(prefix); } return file.type === type; }); } /** * 验证文件大小 * @param file 文件 * @param maxSizeMB 最大大小(MB) * @returns 是否合法 */ validateFileSize(file: File, maxSizeMB: number): boolean { const maxSizeBytes = maxSizeMB * 1024 * 1024; return file.size <= maxSizeBytes; } /** * 格式化文件大小 * @param bytes 字节数 * @returns 格式化后的字符串 */ formatFileSize(bytes: number): string { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB'; if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(2) + ' MB'; return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB'; } }