177 lines
4.6 KiB
TypeScript
177 lines
4.6 KiB
TypeScript
import {
|
||
DeleteOutlined,
|
||
FileTextOutlined,
|
||
PlayCircleOutlined,
|
||
} from '@ant-design/icons';
|
||
import { message, Upload } from 'antd';
|
||
import type { RcFile, UploadFile, UploadProps } from 'antd/lib/upload';
|
||
import React, { useState } from 'react';
|
||
import { createSample, getSample } from '@/services/ai/sample';
|
||
import styles from './index.module.less';
|
||
|
||
const { Dragger } = Upload;
|
||
interface AudioUploaderProps {
|
||
value?: UploadFile[] | UploadFile | null;
|
||
onChange?: (file: UploadFile[] | UploadFile | null) => void;
|
||
maxSize?: number;
|
||
accept?: string;
|
||
disabled?: boolean;
|
||
maxCount?: number;
|
||
placeholder?: string;
|
||
}
|
||
|
||
const AudioUploader: React.FC<AudioUploaderProps> = ({
|
||
value,
|
||
onChange,
|
||
maxSize = 10,
|
||
accept = '.mp3,.wav,.flac,.aac,.ogg',
|
||
disabled = false,
|
||
maxCount = 10,
|
||
placeholder = '点击或拖拽音频文件到此区域上传',
|
||
}) => {
|
||
const [fileList, setFileList] = useState<UploadFile[]>(() => {
|
||
if (Array.isArray(value)) {
|
||
return value;
|
||
} else if (value) {
|
||
return [value];
|
||
}
|
||
return [];
|
||
});
|
||
const [uploading, setUploading] = useState<boolean>(false);
|
||
|
||
const beforeUpload = (file: RcFile): boolean => {
|
||
const isAudio = file.type.startsWith('audio/');
|
||
if (!isAudio) {
|
||
message.error('只能上传音频文件!');
|
||
return false;
|
||
}
|
||
|
||
// 检查文件大小
|
||
const isLtMaxSize = file.size / 1024 / 1024 < maxSize;
|
||
if (!isLtMaxSize) {
|
||
message.error(`音频文件大小不能超过 ${maxSize}MB!`);
|
||
return false;
|
||
}
|
||
|
||
// 检查文件数量限制
|
||
if (fileList.length >= maxCount) {
|
||
message.error(`最多只能上传 ${maxCount} 个文件!`);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
};
|
||
|
||
// 实际的后端接口上传
|
||
const uploadToServer = async (file: File) => {
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
// formData.append("type", "audio"); // 可以添加额外参数
|
||
|
||
const response = await createSample(formData);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
return response.json();
|
||
};
|
||
|
||
const customRequest: UploadProps['customRequest'] = async (options) => {
|
||
const { file, onSuccess, onError, onProgress } = options;
|
||
|
||
try {
|
||
setUploading(true);
|
||
|
||
// 模拟进度更新
|
||
onProgress?.({ percent: 10 });
|
||
|
||
// 调用后端接口
|
||
const result = await uploadToServer(file as File);
|
||
|
||
onProgress?.({ percent: 100 });
|
||
|
||
if (result.success && result.data) {
|
||
// 构造返回数据
|
||
const responseData = {
|
||
url: result.data.url,
|
||
name: result.data.filename || (file as File).name,
|
||
uid: (file as any).uid,
|
||
status: 'done' as const,
|
||
response: result.data,
|
||
};
|
||
|
||
onSuccess?.(responseData);
|
||
message.success('音频上传成功!');
|
||
} else {
|
||
throw new Error(result.message || '上传失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('Upload error:', error);
|
||
onError?.(error as Error);
|
||
message.error(`上传失败: ${(error as Error).message}`);
|
||
} finally {
|
||
setUploading(false);
|
||
}
|
||
};
|
||
|
||
const handleChange: UploadProps['onChange'] = ({
|
||
fileList: newFileList,
|
||
file,
|
||
}) => {
|
||
setFileList(newFileList);
|
||
onChange?.(newFileList);
|
||
};
|
||
|
||
const handleRemove = (file: UploadFile): boolean => {
|
||
const newFileList = fileList.filter((item) => item.uid !== file.uid);
|
||
setFileList(newFileList);
|
||
onChange?.(newFileList);
|
||
return true;
|
||
};
|
||
|
||
const handlePreview = (file: UploadFile): void => {
|
||
const audioUrl = file.url || (file.response as any)?.url;
|
||
if (audioUrl) {
|
||
const audio = new Audio(audioUrl);
|
||
audio.play().catch(() => {
|
||
message.error('音频播放失败');
|
||
});
|
||
}
|
||
};
|
||
|
||
const uploadProps: UploadProps = {
|
||
name: 'file',
|
||
multiple: true,
|
||
fileList,
|
||
accept,
|
||
disabled: disabled || uploading,
|
||
beforeUpload,
|
||
customRequest,
|
||
// onChange: handleChange,
|
||
onRemove: handleRemove,
|
||
onPreview: handlePreview,
|
||
showUploadList: {
|
||
showPreviewIcon: true,
|
||
showRemoveIcon: true,
|
||
previewIcon: <PlayCircleOutlined />,
|
||
removeIcon: <DeleteOutlined />,
|
||
},
|
||
};
|
||
|
||
return (
|
||
<div className={styles['uploader-card']}>
|
||
<Dragger {...uploadProps}>
|
||
<p className="">
|
||
<FileTextOutlined style={{ fontSize: 36 }} />
|
||
</p>
|
||
<p className="ant-upload-text">{placeholder}</p>
|
||
<p className="ant-upload-hint">
|
||
支持格式:{accept},单次最多同时上传 {maxCount} 个文件
|
||
</p>
|
||
</Dragger>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default AudioUploader;
|