友情链接管理;封装上传接口
This commit is contained in:
51
src/components/FileUpload/FileUpload.less
Normal file
51
src/components/FileUpload/FileUpload.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
226
src/components/FileUpload/FileUpload.tsx
Normal file
226
src/components/FileUpload/FileUpload.tsx
Normal 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;
|
127
src/components/FileUpload/README.md
Normal file
127
src/components/FileUpload/README.md
Normal 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 | - |
|
4
src/components/FileUpload/index.ts
Normal file
4
src/components/FileUpload/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import FileUpload from './FileUpload';
|
||||
|
||||
export default FileUpload;
|
||||
export type { FileUploadProps } from './FileUpload';
|
Reference in New Issue
Block a user