fenghuo/apps/web/hooks/useFileDownload.ts

181 lines
4.8 KiB
TypeScript
Raw Normal View History

2025-05-28 20:00:36 +08:00
import { useState } from 'react';
import { useTusUpload } from './useTusUpload';
interface DownloadProgress {
loaded: number;
total: number;
percentage: number;
}
export function useFileDownload() {
const { getFileUrlByFileId, serverUrl } = useTusUpload();
const [downloadProgress, setDownloadProgress] = useState<DownloadProgress | null>(null);
const [isDownloading, setIsDownloading] = useState(false);
const [downloadError, setDownloadError] = useState<string | null>(null);
// 直接下载文件(浏览器处理)
const downloadFile = (fileId: string, filename?: string) => {
const url = getFileUrlByFileId(fileId);
const link = document.createElement('a');
link.href = url;
if (filename) {
link.download = filename;
}
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
// 带进度的文件下载
const downloadFileWithProgress = async (
fileId: string,
filename?: string,
onProgress?: (progress: DownloadProgress) => void,
): Promise<Blob> => {
return new Promise(async (resolve, reject) => {
setIsDownloading(true);
setDownloadError(null);
setDownloadProgress(null);
try {
const url = getFileUrlByFileId(fileId);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const contentLength = response.headers.get('Content-Length');
const total = contentLength ? parseInt(contentLength, 10) : 0;
let loaded = 0;
const reader = response.body?.getReader();
if (!reader) {
throw new Error('Failed to get response reader');
}
const chunks: Uint8Array[] = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (value) {
chunks.push(value);
loaded += value.length;
const progress = {
loaded,
total,
percentage: total > 0 ? Math.round((loaded / total) * 100) : 0,
};
setDownloadProgress(progress);
onProgress?.(progress);
}
}
// 创建 Blob
const blob = new Blob(chunks);
// 如果提供了文件名,自动下载
if (filename) {
const downloadUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(downloadUrl);
}
setIsDownloading(false);
setDownloadProgress(null);
resolve(blob);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Download failed';
setDownloadError(errorMessage);
setIsDownloading(false);
setDownloadProgress(null);
reject(new Error(errorMessage));
}
});
};
// 预览文件(在新窗口打开)
const previewFile = (fileId: string) => {
const url = getFileUrlByFileId(fileId);
window.open(url, '_blank', 'noopener,noreferrer');
};
// 获取文件的 Blob URL用于预览
const getFileBlobUrl = async (fileId: string): Promise<string> => {
try {
const blob = await downloadFileWithProgress(fileId);
return URL.createObjectURL(blob);
} catch (error) {
throw new Error('Failed to create blob URL');
}
};
// 复制文件链接到剪贴板
const copyFileLink = async (fileId: string): Promise<void> => {
try {
const url = getFileUrlByFileId(fileId);
await navigator.clipboard.writeText(url);
} catch (error) {
throw new Error('Failed to copy link');
}
};
// 检查文件是否可以预览(基于 MIME 类型)
const canPreview = (mimeType: string): boolean => {
const previewableTypes = [
'image/', // 所有图片
'application/pdf',
'text/',
'video/',
'audio/',
];
return previewableTypes.some((type) => mimeType.startsWith(type));
};
// 获取文件类型图标
const getFileIcon = (mimeType: string): string => {
if (mimeType.startsWith('image/')) return '🖼️';
if (mimeType.startsWith('video/')) return '🎥';
if (mimeType.startsWith('audio/')) return '🎵';
if (mimeType === 'application/pdf') return '📄';
if (mimeType.startsWith('text/')) return '📝';
if (mimeType.includes('word')) return '📝';
if (mimeType.includes('excel') || mimeType.includes('spreadsheet')) return '📊';
if (mimeType.includes('powerpoint') || mimeType.includes('presentation')) return '📊';
if (mimeType.includes('zip') || mimeType.includes('rar') || mimeType.includes('archive')) return '📦';
return '📁';
};
return {
// 状态
downloadProgress,
isDownloading,
downloadError,
// 方法
downloadFile,
downloadFileWithProgress,
previewFile,
getFileBlobUrl,
copyFileLink,
// 工具函数
canPreview,
getFileIcon,
getFileUrlByFileId,
serverUrl,
};
}