123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- 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<string> {
- 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<string[]> {
- 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<File> {
- 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<string> {
- 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<void> {
- 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';
- }
- }
|