Spaces:
Sleeping
Sleeping
| 'use client'; | |
| import { useState, useEffect } from 'react'; | |
| import { Modal, Upload, message } from 'antd'; | |
| import type { RcFile, UploadFile } from 'antd/es/upload'; | |
| import type { UploadChangeParam } from 'antd/es/upload/interface'; | |
| import { PlusOutlined } from '@ant-design/icons'; | |
| import { uploadLoadAvatar } from '@/service/info'; | |
| import { useLoadInfoStore } from '@/store/useLoadInfoStore'; | |
| interface AvatarUploadProps { | |
| open: boolean; | |
| onClose: () => void; | |
| onAvatarChange: (avatarUrl: string) => void; | |
| currentAvatar?: string; | |
| } | |
| export default function AvatarUpload({ | |
| open, | |
| onClose, | |
| onAvatarChange, | |
| currentAvatar | |
| }: AvatarUploadProps) { | |
| const [previewOpen, setPreviewOpen] = useState(false); | |
| const [previewImage, setPreviewImage] = useState(''); | |
| const [fileList, setFileList] = useState<any[]>([]); | |
| const [messageApi, contextHolder] = message.useMessage(); | |
| const { loadInfo } = useLoadInfoStore(); | |
| const beforeUpload = (file: RcFile) => { | |
| if (!file) return false; | |
| const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'; | |
| if (!isJpgOrPng) { | |
| message.error('You can only upload JPG/PNG file!'); | |
| return false; | |
| } | |
| const isLt2M = file.size / 1024 / 1024 < 2; | |
| if (!isLt2M) { | |
| message.error('Image must smaller than 2MB!'); | |
| return false; | |
| } | |
| return true; | |
| }; | |
| const handlePreview = async (file: UploadFile) => { | |
| if (file.originFileObj) { | |
| const reader = new FileReader(); | |
| reader.onload = () => { | |
| setPreviewImage(reader.result as string); | |
| setPreviewOpen(true); | |
| }; | |
| reader.readAsDataURL(file.originFileObj); | |
| } else if (file.url) { | |
| setPreviewImage(file.url); | |
| setPreviewOpen(true); | |
| } | |
| }; | |
| useEffect(() => { | |
| if (currentAvatar) { | |
| setFileList([ | |
| { | |
| uid: '-1', | |
| name: 'current-avatar.png', | |
| status: 'done', | |
| url: currentAvatar | |
| } | |
| ]); | |
| } else { | |
| setFileList([]); | |
| } | |
| }, [currentAvatar]); | |
| const handleChange = async (info: UploadChangeParam<UploadFile>) => { | |
| const { status } = info.file; | |
| if (status === 'uploading') { | |
| setFileList([{ ...info.file }]); | |
| return; | |
| } | |
| if (status === 'done' && info.file.originFileObj) { | |
| const file = info.file.originFileObj; | |
| // Convert file to base64 | |
| const reader = new FileReader(); | |
| reader.readAsDataURL(file); | |
| reader.onload = async () => { | |
| const base64Url = reader.result as string; | |
| try { | |
| // Directly use base64 string for upload, no longer using FormData | |
| if (loadInfo) { | |
| const res = await uploadLoadAvatar(loadInfo.name, { | |
| avatar_data: base64Url | |
| }); | |
| if (res.data.code === 0) { | |
| // Use base64 image as avatar | |
| setFileList([ | |
| { | |
| uid: '-1', | |
| name: file.name, | |
| status: 'done', | |
| url: base64Url | |
| } | |
| ]); | |
| onAvatarChange(base64Url); | |
| useLoadInfoStore.getState().fetchLoadInfo(); | |
| messageApi.success('Avatar uploaded successfully'); | |
| } else { | |
| setFileList([]); | |
| messageApi.error(res.data.message); | |
| } | |
| } | |
| } catch (err: any) { | |
| setFileList([]); | |
| messageApi.error(err.message || 'Failed to upload avatar'); | |
| } | |
| }; | |
| } | |
| }; | |
| useEffect(() => { | |
| return () => { | |
| fileList.forEach((file) => { | |
| if (file.url?.startsWith('blob:')) { | |
| URL.revokeObjectURL(file.url); | |
| } | |
| }); | |
| }; | |
| }, [fileList]); | |
| return ( | |
| <Modal destroyOnClose footer={null} onCancel={onClose} open={open} title="Upload Avatar"> | |
| <div className="p-4"> | |
| {contextHolder} | |
| <Upload | |
| accept="image/png,image/jpeg" | |
| beforeUpload={beforeUpload} | |
| fileList={fileList} | |
| listType="picture-card" | |
| maxCount={1} | |
| onChange={handleChange} | |
| onPreview={handlePreview} | |
| onRemove={() => { | |
| setFileList([]); | |
| onAvatarChange(''); | |
| }} | |
| showUploadList={{ | |
| showPreviewIcon: true, | |
| showRemoveIcon: true, | |
| showDownloadIcon: false | |
| }} | |
| > | |
| {fileList.length >= 1 ? null : ( | |
| <div> | |
| <PlusOutlined /> | |
| <div style={{ marginTop: 8 }}>Upload</div> | |
| </div> | |
| )} | |
| </Upload> | |
| </div> | |
| <Modal footer={null} onCancel={() => setPreviewOpen(false)} open={previewOpen}> | |
| <img alt="Preview" src={previewImage} style={{ width: '100%' }} /> | |
| </Modal> | |
| </Modal> | |
| ); | |
| } | |