feat: 拆分左侧栏、中间聊天区、右侧栏组件, Hooks 提取
This commit is contained in:
131
src/views/AgentChat/hooks/useFileUpload.ts
Normal file
131
src/views/AgentChat/hooks/useFileUpload.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
// 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,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user