132 lines
3.2 KiB
TypeScript
132 lines
3.2 KiB
TypeScript
// src/views/AgentChat/hooks/useFileUpload.ts
|
||
// 文件上传 Hook - 处理文件选择、预览、删除
|
||
|
||
import { useState, useRef } from 'react';
|
||
import type { ChangeEvent, RefObject } from 'react';
|
||
|
||
/**
|
||
* 上传文件数据结构
|
||
*/
|
||
export interface UploadedFile {
|
||
/** 文件名 */
|
||
name: string;
|
||
/** 文件大小(字节) */
|
||
size: number;
|
||
/** 文件 MIME 类型 */
|
||
type: string;
|
||
/** 文件预览 URL(使用 URL.createObjectURL 创建) */
|
||
url: string;
|
||
}
|
||
|
||
/**
|
||
* useFileUpload Hook 返回值
|
||
*/
|
||
export interface UseFileUploadReturn {
|
||
/** 已上传文件列表 */
|
||
uploadedFiles: UploadedFile[];
|
||
/** 文件输入框引用(用于触发文件选择) */
|
||
fileInputRef: RefObject<HTMLInputElement>;
|
||
/** 处理文件选择事件 */
|
||
handleFileSelect: (event: ChangeEvent<HTMLInputElement>) => void;
|
||
/** 删除指定文件 */
|
||
removeFile: (index: number) => void;
|
||
/** 清空所有文件 */
|
||
clearFiles: () => void;
|
||
}
|
||
|
||
/**
|
||
* useFileUpload Hook
|
||
*
|
||
* 处理文件上传相关逻辑(选择、预览、删除)
|
||
*
|
||
* @returns UseFileUploadReturn
|
||
*
|
||
* @example
|
||
* ```tsx
|
||
* const { uploadedFiles, fileInputRef, handleFileSelect, removeFile } = useFileUpload();
|
||
*
|
||
* return (
|
||
* <>
|
||
* <input
|
||
* ref={fileInputRef}
|
||
* type="file"
|
||
* multiple
|
||
* accept="image/*,.pdf,.doc,.docx,.txt"
|
||
* onChange={handleFileSelect}
|
||
* style={{ display: 'none' }}
|
||
* />
|
||
* <Button onClick={() => fileInputRef.current?.click()}>上传文件</Button>
|
||
* {uploadedFiles.map((file, idx) => (
|
||
* <Tag key={idx}>
|
||
* {file.name}
|
||
* <TagCloseButton onClick={() => removeFile(idx)} />
|
||
* </Tag>
|
||
* ))}
|
||
* </>
|
||
* );
|
||
* ```
|
||
*/
|
||
export const useFileUpload = (): UseFileUploadReturn => {
|
||
const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([]);
|
||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||
|
||
/**
|
||
* 处理文件选择事件
|
||
*/
|
||
const handleFileSelect = (event: ChangeEvent<HTMLInputElement>) => {
|
||
const files = Array.from(event.target.files || []);
|
||
|
||
const fileData: UploadedFile[] = files.map((file) => ({
|
||
name: file.name,
|
||
size: file.size,
|
||
type: file.type,
|
||
// 创建本地预览 URL(实际上传时需要转换为 base64 或上传到服务器)
|
||
url: URL.createObjectURL(file),
|
||
}));
|
||
|
||
setUploadedFiles((prev) => [...prev, ...fileData]);
|
||
|
||
// 清空 input value,允许重复选择同一文件
|
||
if (event.target) {
|
||
event.target.value = '';
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 删除指定索引的文件
|
||
*/
|
||
const removeFile = (index: number) => {
|
||
setUploadedFiles((prev) => {
|
||
// 释放 URL.createObjectURL 创建的内存
|
||
const file = prev[index];
|
||
if (file?.url) {
|
||
URL.revokeObjectURL(file.url);
|
||
}
|
||
|
||
return prev.filter((_, i) => i !== index);
|
||
});
|
||
};
|
||
|
||
/**
|
||
* 清空所有文件
|
||
*/
|
||
const clearFiles = () => {
|
||
// 释放所有 URL 内存
|
||
uploadedFiles.forEach((file) => {
|
||
if (file.url) {
|
||
URL.revokeObjectURL(file.url);
|
||
}
|
||
});
|
||
|
||
setUploadedFiles([]);
|
||
};
|
||
|
||
return {
|
||
uploadedFiles,
|
||
fileInputRef,
|
||
handleFileSelect,
|
||
removeFile,
|
||
clearFiles,
|
||
};
|
||
};
|