友情链接管理;封装上传接口

This commit is contained in:
linxd
2025-06-18 18:46:32 +08:00
parent 2a0532f775
commit 73db059e7d
24 changed files with 562 additions and 385 deletions

View File

@ -0,0 +1,51 @@
.file-upload-wrapper {
.ant-upload-list {
margin-top: 8px;
}
.ant-upload-list-item-name {
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
}
.ant-upload-select {
margin-bottom: 8px;
}
.file-upload-tip {
margin-top: 8px;
color: rgba(0, 0, 0, 0.45);
font-size: 12px;
line-height: 1.5;
span {
cursor: help;
text-decoration: underline dotted;
}
}
.ant-upload-drag {
padding: 16px;
.ant-upload-drag-icon {
margin-bottom: 8px;
.anticon {
color: #40a9ff;
font-size: 48px;
}
}
.ant-upload-text {
margin: 0 0 4px;
color: rgba(0, 0, 0, 0.85);
font-size: 16px;
}
.ant-upload-hint {
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
}
}
}

View File

@ -0,0 +1,226 @@
import React, { useState, useEffect } from 'react';
import { Upload, Button, message, Tooltip } from 'antd';
import { UploadOutlined, InboxOutlined } from '@ant-design/icons';
import type { UploadFile, UploadProps } from 'antd/es/upload/interface';
import { validateFileSize } from '@/utils/utils';
import { useIntl } from 'umi';
import './FileUpload.less';
export interface FileUploadProps {
value?: UploadFile[];
onChange?: (fileList: UploadFile[]) => void;
maxCount?: number;
maxSize?: number;
allowedTypes?: string[];
listType?: 'text' | 'picture' | 'picture-card';
buttonText?: string;
disabled?: boolean;
accept?: string;
showUploadList?:
| boolean
| { showPreviewIcon?: boolean; showRemoveIcon?: boolean; showDownloadIcon?: boolean };
isDragger?: boolean;
tip?: string;
action?: string;
}
// 上传接口返回数据格式
interface UploadResponseData {
fileName: string;
fileSize: string;
filePath: string;
fileType: string;
url: string;
}
// API响应格式
interface ApiResponse<T> {
code: number;
success: boolean;
message: string;
data: T;
}
const FileUpload: React.FC<FileUploadProps> = ({
value,
onChange,
maxCount = 1,
maxSize = 5,
allowedTypes = ['*'],
listType = 'text',
buttonText,
disabled = false,
accept,
showUploadList = true,
isDragger = false,
tip,
action,
}) => {
const actionUrl = action ? `${UPLOAD_URL}${action}` : `${UPLOAD_URL}/api/files/upload`;
const intl = useIntl();
const [fileList, setFileList] = useState<UploadFile[]>([]);
// 监听value变化
useEffect(() => {
// 处理字符串URL值这是关键修复
if (typeof value === 'string' && value) {
const file: Partial<UploadFile> = {
uid: '-1',
name: 'image.jpg',
status: 'done',
url: value,
size: 0,
type: 'image/jpeg',
};
setFileList([file as UploadFile]);
return;
}
// 处理空值
if (!value || (Array.isArray(value) && value.length === 0)) {
setFileList([]);
return;
}
// 处理正常的文件数组
if (Array.isArray(value)) {
setFileList(value);
return;
}
// 处理对象格式
if (typeof value === 'object') {
// 处理fileList属性
if ('fileList' in value && Array.isArray((value as any).fileList)) {
const files = (value as { fileList: UploadFile[] }).fileList;
setFileList(files);
return;
}
// 尝试作为单个文件对象处理
if ('uid' in value && 'status' in value) {
setFileList([value as unknown as UploadFile]);
return;
}
console.warn('FileUpload: Unrecognized object format', value);
setFileList([]);
return;
}
// 无法识别的格式
console.warn('FileUpload component: unrecognized value format:', value);
setFileList([]);
}, [value]);
const defaultButtonText = intl.formatMessage({
id: 'component.fileUpload.buttonText',
defaultMessage: '上传文件',
});
// 处理上传接口返回的数据格式
const getFileUrl = (file: UploadFile) => {
// 如果response是API的标准响应格式
if (file.response && typeof file.response === 'object') {
// 首先检查是否是标准API响应格式
if ('success' in file.response && 'data' in file.response) {
const response = file.response as ApiResponse<UploadResponseData>;
if (response.success && response.data) {
return response.data.url;
}
}
// 如果response直接包含url属性
else if ('url' in file.response) {
return (file.response as UploadResponseData).url;
}
}
// 回退到文件的url或thumbUrl
return file.url || file.thumbUrl;
};
const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
const filteredList = newFileList.filter((file) => file.status !== 'error');
// 为每个文件添加正确的url
const processedList = filteredList.map(file => {
if (file.status === 'done' && file.response && !file.url) {
return {
...file,
url: getFileUrl(file)
};
}
return file;
});
setFileList(processedList);
if (onChange) {
onChange(processedList);
}
};
const beforeUpload = (file: File) => {
return validateFileSize(file, maxSize, allowedTypes);
};
const UploadComponent = isDragger ? Upload.Dragger : Upload;
const uploadProps = {
fileList,
onChange: handleChange,
beforeUpload,
listType,
maxCount,
disabled,
accept,
showUploadList,
action: actionUrl,
};
const renderUploadButton = () => {
if (fileList.length >= maxCount) {
return null;
}
if (isDragger) {
return (
<>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p className="ant-upload-text">
{buttonText ||
intl.formatMessage({
id: 'component.fileUpload.dragText',
defaultMessage: '点击或拖拽文件到此区域上传',
})}
</p>
{tip && <p className="ant-upload-hint">{tip}</p>}
</>
);
}
return (
<Button icon={<UploadOutlined />} disabled={disabled}>
{buttonText || defaultButtonText}
</Button>
);
};
return (
<div className="file-upload-wrapper">
<UploadComponent {...uploadProps}>{renderUploadButton()}</UploadComponent>
{!isDragger && tip && (
<div className="file-upload-tip">
<Tooltip title={tip}>
<span>{tip}</span>
</Tooltip>
</div>
)}
</div>
);
};
export default FileUpload;

View File

@ -0,0 +1,127 @@
# FileUpload 文件上传组件
基于Ant Design Upload组件封装的文件上传组件支持文件类型和大小校验。
## 组件特性
- 支持文件类型校验
- 支持文件大小限制
- 支持拖拽上传
- 支持上传多个文件
- 支持国际化
- 支持自定义提示信息
- 支持表单受控模式
## 使用示例
### 基本使用
```tsx
import React from 'react';
import { Form } from 'antd';
import FileUpload from '@/components/FileUpload';
const App: React.FC = () => {
const [form] = Form.useForm();
return (
<Form form={form}>
<Form.Item
name="files"
label="上传文件"
rules={[{ required: true, message: '请上传文件' }]}
>
<FileUpload maxSize={5} allowedTypes={['pdf', 'doc', 'docx']} />
</Form.Item>
</Form>
);
};
export default App;
```
### 拖拽上传
```tsx
import React from 'react';
import { Form } from 'antd';
import FileUpload from '@/components/FileUpload';
import { useIntl } from 'umi';
const App: React.FC = () => {
const [form] = Form.useForm();
const intl = useIntl();
const fileTip = intl.formatMessage(
{ id: 'component.fileUpload.fileTypeTip' },
{ types: 'PDF, Word' }
);
return (
<Form form={form}>
<Form.Item
name="files"
label="上传文件"
rules={[{ required: true, message: '请上传文件' }]}
>
<FileUpload
maxSize={10}
allowedTypes={['pdf', 'doc', 'docx']}
isDragger={true}
tip={fileTip}
/>
</Form.Item>
</Form>
);
};
export default App;
```
### 图片上传
```tsx
import React from 'react';
import { Form } from 'antd';
import FileUpload from '@/components/FileUpload';
const App: React.FC = () => {
const [form] = Form.useForm();
return (
<Form form={form}>
<Form.Item
name="images"
label="上传图片"
rules={[{ required: true, message: '请上传图片' }]}
>
<FileUpload
maxSize={2}
allowedTypes={['jpg', 'png']}
listType="picture-card"
tip="支持jpg, png格式大小不超过2MB"
/>
</Form.Item>
</Form>
);
};
export default App;
```
## API
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| value | 文件列表 | UploadFile[] | [] |
| onChange | 文件列表改变时的回调 | (fileList: UploadFile[]) => void | - |
| maxCount | 最大上传数量 | number | 1 |
| maxSize | 文件大小限制MB | number | 5 |
| allowedTypes | 允许的文件类型 | string[] | ['*'] |
| listType | 上传列表的样式 | 'text' \| 'picture' \| 'picture-card' | 'text' |
| buttonText | 上传按钮文字 | string | '上传文件' |
| disabled | 是否禁用 | boolean | false |
| accept | 接受的文件类型 | string | - |
| showUploadList | 是否展示文件列表 | boolean \| { showPreviewIcon?: boolean; showRemoveIcon?: boolean; showDownloadIcon?: boolean } | true |
| isDragger | 是否启用拖拽上传 | boolean | false |
| tip | 提示文字 | string | - |

View File

@ -0,0 +1,4 @@
import FileUpload from './FileUpload';
export default FileUpload;
export type { FileUploadProps } from './FileUpload';