feat: 新增图片资源上传和管理服务
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Modal, Form, Input, Select, Upload, message } from 'antd';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import type { UploadProps } from 'antd';
|
||||
import { PlusOutlined, LoadingOutlined } from '@ant-design/icons';
|
||||
import type { UploadProps, UploadFile } from 'antd';
|
||||
import { brokerService } from '@/services/broker';
|
||||
import { storageService } from '@/services/storage';
|
||||
import type { Broker, CreateBrokerRequest } from '@/types/broker';
|
||||
import { REGION_OPTIONS } from '@/types/broker';
|
||||
import type { RcFile } from 'antd/es/upload';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@@ -17,6 +19,8 @@ interface BrokerFormModalProps {
|
||||
|
||||
const BrokerFormModal = ({ visible, editingBroker, onCancel, onSuccess }: BrokerFormModalProps) => {
|
||||
const [form] = Form.useForm();
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||||
const isEdit = !!editingBroker;
|
||||
|
||||
// 当编辑数据变化时,更新表单
|
||||
@@ -29,45 +33,90 @@ const BrokerFormModal = ({ visible, editingBroker, onCancel, onSuccess }: Broker
|
||||
region: editingBroker.region,
|
||||
brokerImage: editingBroker.brokerImage,
|
||||
});
|
||||
// 如果有图片,设置文件列表
|
||||
if (editingBroker.brokerImage) {
|
||||
setFileList([
|
||||
{
|
||||
uid: '-1',
|
||||
name: 'broker-logo',
|
||||
status: 'done',
|
||||
url: editingBroker.brokerImage,
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
setFileList([]);
|
||||
}
|
||||
} else {
|
||||
form.resetFields();
|
||||
setFileList([]);
|
||||
}
|
||||
}
|
||||
}, [visible, editingBroker, form]);
|
||||
|
||||
// 图片上传配置(仅UI,不上传)
|
||||
// 自定义上传函数
|
||||
const customRequest: UploadProps['customRequest'] = async (options) => {
|
||||
const { file, onSuccess, onError } = options;
|
||||
setUploading(true);
|
||||
|
||||
try {
|
||||
const uploadFile = file as RcFile;
|
||||
const response = await storageService.uploadFile({
|
||||
file: uploadFile,
|
||||
folder: 'broker',
|
||||
});
|
||||
|
||||
// 更新表单字段
|
||||
form.setFieldValue('brokerImage', response.url);
|
||||
|
||||
// 更新文件列表
|
||||
setFileList([
|
||||
{
|
||||
uid: response.path,
|
||||
name: response.filename,
|
||||
status: 'done',
|
||||
url: response.url,
|
||||
},
|
||||
]);
|
||||
|
||||
message.success('图片上传成功');
|
||||
onSuccess?.(response);
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '图片上传失败');
|
||||
onError?.(error);
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 图片上传配置
|
||||
const uploadProps: UploadProps = {
|
||||
name: 'file',
|
||||
listType: 'picture-card',
|
||||
maxCount: 1,
|
||||
beforeUpload: () => {
|
||||
// 阻止自动上传
|
||||
return false;
|
||||
},
|
||||
onChange: (info) => {
|
||||
if (info.file.status === 'done') {
|
||||
message.success(`${info.file.name} 文件上传成功`);
|
||||
} else if (info.file.status === 'error') {
|
||||
message.error(`${info.file.name} 文件上传失败`);
|
||||
fileList,
|
||||
accept: 'image/*',
|
||||
customRequest,
|
||||
beforeUpload: (file) => {
|
||||
const isImage = file.type.startsWith('image/');
|
||||
if (!isImage) {
|
||||
message.error('只能上传图片文件!');
|
||||
return false;
|
||||
}
|
||||
const isLt5M = file.size / 1024 / 1024 < 5;
|
||||
if (!isLt5M) {
|
||||
message.error('图片大小不能超过 5MB!');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
onRemove: () => {
|
||||
setFileList([]);
|
||||
form.setFieldValue('brokerImage', '');
|
||||
return true;
|
||||
},
|
||||
onChange: ({ fileList: newFileList }) => {
|
||||
setFileList(newFileList);
|
||||
},
|
||||
};
|
||||
|
||||
// 处理图片变化(仅UI,实际应该上传后获取URL)
|
||||
const handleImageChange = (info: any) => {
|
||||
// 这里只做UI处理,实际应该调用上传接口获取图片URL
|
||||
// 临时处理:使用本地预览
|
||||
if (info.file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
// 实际应该调用上传接口,这里只是示例
|
||||
// form.setFieldValue('brokerImage', uploadUrl);
|
||||
};
|
||||
reader.readAsDataURL(info.file);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
@@ -148,20 +197,27 @@ const BrokerFormModal = ({ visible, editingBroker, onCancel, onSuccess }: Broker
|
||||
<Form.Item
|
||||
name="brokerImage"
|
||||
label="券商Logo"
|
||||
rules={[{ max: 200, message: '图片地址不能超过200个字符' }]}
|
||||
rules={[{ max: 500, message: '图片地址不能超过500个字符' }]}
|
||||
>
|
||||
<Input placeholder="请输入图片URL(或使用下方上传组件)" allowClear />
|
||||
<Input
|
||||
placeholder="图片URL(上传后自动填充)"
|
||||
allowClear
|
||||
readOnly
|
||||
style={{ cursor: 'not-allowed' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="上传Logo(仅UI演示)">
|
||||
<Upload {...uploadProps} onChange={handleImageChange} accept="image/*">
|
||||
<div>
|
||||
<PlusOutlined />
|
||||
<div style={{ marginTop: 8 }}>上传</div>
|
||||
</div>
|
||||
<Form.Item label="上传Logo">
|
||||
<Upload {...uploadProps}>
|
||||
{fileList.length >= 1 ? null : (
|
||||
<div>
|
||||
{uploading ? <LoadingOutlined /> : <PlusOutlined />}
|
||||
<div style={{ marginTop: 8 }}>上传</div>
|
||||
</div>
|
||||
)}
|
||||
</Upload>
|
||||
<div style={{ marginTop: 8, color: '#999', fontSize: 12 }}>
|
||||
注意:上传功能仅做UI演示,实际需要调用上传接口获取图片URL
|
||||
支持 JPG、PNG、GIF、WebP 格式,大小不超过 5MB
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
67
apps/web/src/services/storage.ts
Normal file
67
apps/web/src/services/storage.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { api } from './api';
|
||||
|
||||
export interface UploadFileParams {
|
||||
file: File;
|
||||
folder?: 'broker' | 'user' | 'temp';
|
||||
filename?: string;
|
||||
}
|
||||
|
||||
export interface UploadFileResponse {
|
||||
path: string;
|
||||
url: string;
|
||||
filename: string;
|
||||
size: number;
|
||||
mimetype: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储服务
|
||||
*/
|
||||
class StorageService {
|
||||
/**
|
||||
* 管理员上传文件(需要鉴权)
|
||||
* 用于上传券商Logo等基础数据
|
||||
*/
|
||||
async uploadFile(params: UploadFileParams): Promise<UploadFileResponse> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', params.file);
|
||||
|
||||
if (params.folder) {
|
||||
formData.append('folder', params.folder);
|
||||
}
|
||||
|
||||
if (params.filename) {
|
||||
formData.append('filename', params.filename);
|
||||
}
|
||||
|
||||
return await api.post<UploadFileResponse>('/storage/upload', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传用户头像(不需要鉴权)
|
||||
* 用于用户注册或更新头像
|
||||
*/
|
||||
async uploadAvatar(file: File): Promise<UploadFileResponse> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
return await api.post<UploadFileResponse>('/storage/upload/avatar', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件(需要管理员权限)
|
||||
*/
|
||||
async deleteFile(path: string): Promise<void> {
|
||||
await api.delete(`/storage/${path}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const storageService = new StorageService();
|
||||
Reference in New Issue
Block a user