181 lines
4.8 KiB
TypeScript
181 lines
4.8 KiB
TypeScript
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,
|
||
};
|
||
}
|