fenghuo/apps/web/hooks/useFileDownload.ts

181 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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