需求分析
upload组件的一些样式:
一步步来看,点击“点击上传”按钮后,选择你想上传的文件,然后开始自动上传,展示进度条,进度条满了之后会显示图片名称及绿色的标志,鼠标浮在上面会展示×,删除已上传的文件,若上传失败,展示error.jpg及红色的标志
上传一个文件的流程如下,点击“点击上传”按钮后,选择你想上传的文件,然后触发beforeUpload(file)函数,然后开始上传(onProgress(event, file)),上传完毕后触发onChange(file),若上传成功,触发onSuccess(response, file),上传失败触发onError(error, file),点击已经上传文件的删除按钮,调用onRemoved(file)。
据此,我们可以知道upload组件需要的属性:
使用Axios发送HTTP请求
fetch的缺点:
- 只对网络请求报错,对400 500都当作成功的请求
- 默认不带cookie
- 不支持abort,不支持超时控制
- 无法原生监测请求的进度
在线服务器:JSONPlaceholder和mock(它可以根据你自己填入的JSON定制化输出,即response的结果是你自己输入的)
本节我们使用Axios发送HTTP请求,在项目中安装:npm install axios --save
上传文件的基本方式
两种方法完成文件的上传:
- 传统的表单提交的方式
import React,
import axios from 'axios'
const App: React.FC = () => {
return (
<div className='App' style={{marginTop:'100px',marginLeft:'100px'}}>
<form method='post' encType='multipart/form-data' action='https://www.baid.com'>
<input type='file' name='myFile' />
<button type='submit'>Submit</button>
</from>
</div>
)
}
当你要使用文件上传功能时,上传文件的表单和普通表单的不同之处在于type='file'
,文件和普通的字符串不同,它属于二进制格式,所以form要设置encType='multipart/form-data'
(注意你要上传文件或你表单发送的内容中有二进制格式文件,都要设置这个),除此之外,encType的取值还有:
(1)application/x-www-form-urlencoded:默认,表单数据被encoded到body或Url中去传送,如果要发送大量二进制数据,使用这种形式是低效的
(2)multipart/form-data:发送二进制文件
(3)text/plain
- 使用JS发送异步请求的方式
import React,
import axios from 'axios'
const App: React.FC = () => {
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// e.target.files返回值是一个数组,数组里放着一个个文件数据,因为支持选择多个文件
const files = e.target.files;
if(files){
const uploadedFile = file[0]
//模拟表单的数据
const formData = new FormData();
// 使用formData的append方法为其附加input值
// formData.append(文件名, 文件),数据会以键值对文件名:文件的形式保存起来
formData.append(uploadedFile.name, uploadedFile)
axios.post('https://jsonplaceholder.typicode.com/posts',formData,{
headers:{
// 这里的Content-Type就等同于form表单中的encType
'Content-Type': 'multipart/form-data'
}
}).then(res => console.log(res))
}
}
return (
<div className='App' style={{marginTop:'100px',marginLeft:'100px'}}>
<input type='file' name='myFile' onChange={handleFileChange}/>
</div>
)
}
编码实现上传文件基本流程
新建文件src/components/Upload/upload.tsx
import React,{FC, useRef} from 'react'
import axios from 'axios'
import Button from '../Button/button'
export interface UploadProps{
action: string;
beforeUpload?: (file: File) => boolean | Promise<File>;// 上传前我们要做两件事:1.验证2.对文件进行转换,所以返回值为Boolean或promise
onProgress?: (percentage: number,file: File) => void;
onSuccess?: (data: any, file: File) => void;
onError?: (err: any, file: File) => void;
onChange?: (file: File) => void;
}
export const Upload: FC<UploadProps> = (props) => {
const {action, onProgress, onSuccess, onError} = props;
const fileInput = useRef<HTMInputElement>(null)
const handleClick = () => {
if(fileInput.current){
fileInput.current.click();
}
}
// 值变化时选择文件
const handleFileChange = (e:ChangeEvent<HTMLInputElement>) => {
const file = e.target.files;
if(!files) return;
uploadFiles(files);
if(fileInput.current){
fileInput.current.value = '';
}
}
// FileList是一个类数组对象,想在类数组上使用数组的方法,需要先将类数组转换为数组
const uploadFiles = (files: FileList) => {
let postFiles = Array.from(files)
postFiles.forEach(file => {
if(!beforeUpload){// 没有beforeUpload,直接上传
post(file)
}else{
const result = beforeUpload(file)
if(result && result instanceof Promise){
result.then(res => post(res))
}else if(result !== false) {
post(result)
}
}
})
}
const post = (file: File) => {
const formData = new FormData()
formData.append(file.name,file);
axios.post(action, formData,{
headers:{
'Content-Type': 'multipart/form-data'
}
//此时我们需要拿到onProgress事件,axios给我们提供了一个onUploadProgress来显示上传进度
onUploadProgress: (e) => {
let percentage = Math.round((e.loaded * 100) / e.total) || 0;
if(percentage < 100) {
if(onProgress) {
onProgress(percentage, file)
}
}
}
}).then(res => {
console.log(res)
if(onSuccess){
onSuccess(res.data, file)//第一个参数是服务器返回的数据,第二个参数是file对象
}
if(onChange) {
onChange(file)
}
}).catch(err => {
console.error(err)
if(onError){
onError(err, file)//第一个参数是err对象,第二个参数是file对象
}
if(onChange) {
onChange(file)
}
})
}
return (
<div className='viking-upload-component'>
<button btnType='primary' onClick={handleClick}}>Upload File</button>
<input
className='viking-file-input'
style={{display: 'none'}}
ref={fileInput}
onChang={handleFileChange}
type='file' />
</div>
)
}
创建列表数据
下面画勾的这些就是数据列表
先创建列表数据的类型:
export type UploadFileStatus = 'ready' | 'uploading' | 'success' | 'error'
export interface UploadFile {
uid: string;
size: number;
name: string;
status?: UploadFileStatus;
percent?: number;
raw?: File;
response?: any;
error?: any;
}
列表数据保存在state中:const [ fileList, setFileList ] = useState<UploadFile[]>([])
在上传开始的时候更新列表数据
// 更新列表的某一项(updateFile项),
const updateFileList = (updateFile: UploadFile, updateObj: Partial<UploadFile>) => {
// 上传过程中更改状态。setFileList不仅可以接收新值,也可以接收函数,函数的参数就是当前的值
setFileList(prevList => {
return prevList.map(file => {
if (file.uid === updateFile.uid) {
//用我们要更新的属性值,覆盖该项的属性值
return { ...file, ...updateObj }
} else {
return file
}
})
})
}
const post = (file: File) => {
let _file: UploadFile = {
uid: Date.now() + 'upload-file',
status: 'ready',
name: file.name,
size: file.size,
percent: 0,
raw: file
}
//更新数据列表
setFileList([_file, ...fileList])
const formData = new FormData()
formData.append(file.name,file);
axios.post(action, formData,{
headers:{
'Content-Type': 'multipart/form-data'
}
//此时我们需要拿到onProgress事件,axios给我们提供了一个onUploadProgress来显示上传进度
onUploadProgress: (e) => {
let percentage = Math.round((e.loaded * 100) / e.total) || 0;
if(percentage < 100) {
updateFileList(_file, { percent: percentage, status: 'uploading'})
if(onProgress) {
onProgress(percentage, file)
}
}
}
}).then(res => {
console.log(res)
updateFileList(_file, {status: 'success', response: resp.data})
if(onSuccess){
onSuccess(res.data, file)//第一个参数是服务器返回的数据,第二个参数是file对象
}
if(onChange) {
onChange(file)
}
}).catch(err => {
console.error(err)
updateFileList(_file, { status: 'error', error: err})
if(onError){
onError(err, file)//第一个参数是err对象,第二个参数是file对象
}
if(onChange) {
onChange(file)
}
})
}
显示上传的数据
根据文件信息展示不同的样式,以上是三个不同状态下的列表模式,分为左右两栏,左边是固定的文件图标,然后是文件名称,右边根据不同的状态添加三种不同的图标,假如是uploading的状态,还需要添加一个带百分比的进度条,当鼠标hover到条目上时,会显示×图标,点击该图标后会删除这个条目,并且触发用户自定义的onRemove事件。进度条我们本节先不做。
新建src/components/Upload/uploadList.tsx
import React, { FC } from 'react'
import { UploadFile } from './upload'
import Icon from '../Icon/icon'
interface UploadListProps {
fileList: UploadFile[];
onRemove: (_file: UploadFile) => void;
}
export const UploadList: FC<UploadListProps> = (props) => {
const {
fileList,
onRemove,
} = props
return (
<ul className="viking-upload-list">
{fileList.map(item => {
return (
<li className="viking-upload-list-item" key={item.uid}>
<span className={`file-name file-name-${item.status}`}>
<Icon icon="file-alt" theme="secondary" />
{item.name}
</span>
<span className="file-status">
{(item.status === 'uploading' || item.status === 'ready') && <Icon icon="spinner" spin theme="primary" />}
{item.status === 'success' && <Icon icon="check-circle" theme="success" />}
{item.status === 'error' && <Icon icon="times-circle" theme="danger" />}
</span>
<span className="file-actions">
<Icon icon="times" onClick={() => { onRemove(item)}}/>
</span>
</li>
)
})}
</ul>
)
}
export default UploadList;
将UploadList添加进Upload组件中:
import React,{FC, useRef} from 'react'
import axios from 'axios'
import Button from '../Button/button'
import UploadList from './uploadList'
export type UploadFileStatus = 'ready' | 'uploading' | 'success' | 'error'
export interface UploadFile {
uid: string;
size: number;
name: string;
status?: UploadFileStatus;
percent?: number;
raw?: File;
response?: any;
error?: any;
}
export interface UploadProps{
action: string;
defaultFileList?: UploadFile[];
beforeUpload?: (file: File) => boolean | Promise<File>;// 上传前我们要做两件事:1.验证2.对文件进行转换,所以返回值为Boolean或promise
onProgress?: (percentage: number,file: File) => void;
onSuccess?: (data: any, file: File) => void;
onError?: (err: any, file: File) => void;
onChange?: (file: File) => void;
onRemove?: (file: UploadFile) => void;
}
export const Upload: FC<UploadProps> = (props) => {
const {action, defaultFileList, onProgress, onSuccess, onError, onRemove} = props;
const fileInput = useRef<HTMInputElement>(null)
const [ fileList, setFileList ] = useState<UploadFile[]>(defaultFileList || [])
const handleClick = () => {
if(fileInput.current){
fileInput.current.click();
}
}
// 值变化时选择文件
const handleFileChange = (e:ChangeEvent<HTMLInputElement>) => {
const file = e.target.files;
if(!files) return;
uploadFiles(files);
if(fileInput.current){
fileInput.current.value = '';
}
}
const handleRemove = (file: UploadFile) => {
setFileList((prevList) => {
return prevList.filter(item => item.uid !== file.uid)
})
if (onRemove) {
onRemove(file)
}
}
// FileList是一个类数组对象,想在类数组上使用数组的方法,需要先将类数组转换为数组
const uploadFiles = (files: FileList) => {
let postFiles = Array.from(files)
postFiles.forEach(file => {
if(!beforeUpload){// 没有beforeUpload,直接上传
post(file)
}else{
const result = beforeUpload(file)
if(result && result instanceof Promise){
result.then(res => post(res))
}else if(result !== false) {
post(result)
}
}
})
}
const post = (file: File) => {
const formData = new FormData()
formData.append(file.name,file);
axios.post(action, formData,{
headers:{
'Content-Type': 'multipart/form-data'
}
//此时我们需要拿到onProgress事件,axios给我们提供了一个onUploadProgress来显示上传进度
onUploadProgress: (e) => {
let percentage = Math.round((e.loaded * 100) / e.total) || 0;
if(percentage < 100) {
if(onProgress) {
onProgress(percentage, file)
}
}
}
}).then(res => {
console.log(res)
if(onSuccess){
onSuccess(res.data, file)//第一个参数是服务器返回的数据,第二个参数是file对象
}
if(onChange) {
onChange(file)
}
}).catch(err => {
console.error(err)
if(onError){
onError(err, file)//第一个参数是err对象,第二个参数是file对象
}
if(onChange) {
onChange(file)
}
})
}
return (
<div className='viking-upload-component'>
<button btnType='primary' onClick={handleClick}}>Upload File</button>
<input
className='viking-file-input'
style={{display: 'none'}}
ref={fileInput}
onChang={handleFileChange}
type='file' />
<UploadList
fileList={fileList}
onRemove={handleRemove}
/>
</div>
)
}
进度条
根据传入的百分比显示一个进度条,
最外面有个灰色的条,高度可配置,使用相对定位:position:relative;
然后右边是个进度条,颜色可配置,使用绝对定位:position:absolute;
,让他浮在灰色的条上面去,还可以配置文字是否显示,如果显示,里面的字体垂直居中。新建src/components/Progress/progress.tsx
import React, { FC } from 'react'
import { ThemeProps } from '../Icon/icon'
export interface ProgressProps {
percent: number;// 显示加载到百分之几
strokeHeight?: number;// 灰色的条的高度
showText?: boolean;// 是否展示里面的字
styles?: React.CSSProperties;
theme?: ThemeProps;
}
const Progress: FC<ProgressProps> = (props) => {
const {
percent,
strokeHeight,
showText,
styles,
theme,
} = props
return (
<div className="viking-progress-bar" style={styles}>
<div className="viking-progress-bar-outer" style={{ height: `${strokeHeight}px`}}>
<div
className={`viking-progress-bar-inner color-${theme}`}
style={{width: `${percent}%`}}
>
{showText && <span className="inner-text">{`${percent}%`}</span>}
</div>
</div>
</div>
)
}
Progress.defaultProps = {
strokeHeight: 15,
showText: true,
theme: "primary",
}
export default Progress;
在src/components/Upload/uploadList.tsx中使用
return (
<li className="viking-upload-list-item" key={item.uid}>
<span className={`file-name file-name-${item.status}`}>
<Icon icon="file-alt" theme="secondary" />
{item.name}
</span>
<span className="file-status">
{(item.status === 'uploading' || item.status === 'ready') && <Icon icon="spinner" spin theme="primary" />}
{item.status === 'success' && <Icon icon="check-circle" theme="success" />}
{item.status === 'error' && <Icon icon="times-circle" theme="danger" />}
</span>
<span className="file-actions">
<Icon icon="times" onClick={() => { onRemove(item)}}/>
</span>
// 使用
{item.status === 'uploading' && <Progress percent={item.percent || 0} />}
</li>
)
丰富上传数据
我们的upload组件发送http的post请求时,可定制性非常差,在发送请求时,有几个属性非常重要,用户有可能会自定义,如:自定义header,自定义name属性(发送到后台的文件参数名称),自定义data属性(上传所需的额外参数),添加input本身的file约束属性(multiple、accept等)。用户界面和交互功能也需要完善,如自定义触发的元素,支持推拽上传,点击上传文件名称,添加onPreivew事件(点击文件展示文件,点击文件名更改文件名等操作)
现在我们先实现【自定义header,自定义name属性(发送到后台的文件参数名称),自定义data属性(上传所需的额外参数),添加input本身的file约束属性(multiple、accept等)】
import React,{FC, useRef} from 'react'
import axios from 'axios'
import Button from '../Button/button'
import UploadList from './uploadList'
export type UploadFileStatus = 'ready' | 'uploading' | 'success' | 'error'
export interface UploadFile {
uid: string;
size: number;
name: string;
status?: UploadFileStatus;
percent?: number;
raw?: File;
response?: any;
error?: any;
}
export interface UploadProps{
action: string;
defaultFileList?: UploadFile[];
beforeUpload?: (file: File) => boolean | Promise<File>;// 上传前我们要做两件事:1.验证2.对文件进行转换,所以返回值为Boolean或promise
onProgress?: (percentage: number,file: File) => void;
onSuccess?: (data: any, file: File) => void;
onError?: (err: any, file: File) => void;
onChange?: (file: File) => void;
onRemove?: (file: UploadFile) => void;
headers?: {[key: string]: any };
name?: string;
data?: {[key: string]: any };
withCredentials?: boolean;// 是否携带cookie
accept?: string;
multiple?: boolean;
}
export const Upload: FC<UploadProps> = (props) => {
const {action, defaultFileList, onProgress, onSuccess, onError, onRemove, name, headers, data, withCredentials, accept, multiple,} = props;
const fileInput = useRef<HTMInputElement>(null)
const [ fileList, setFileList ] = useState<UploadFile[]>(defaultFileList || [])
const handleClick = () => {
if(fileInput.current){
fileInput.current.click();
}
}
// 值变化时选择文件
const handleFileChange = (e:ChangeEvent<HTMLInputElement>) => {
const file = e.target.files;
if(!files) return;
uploadFiles(files);
if(fileInput.current){
fileInput.current.value = '';
}
}
const handleRemove = (file: UploadFile) => {
setFileList((prevList) => {
return prevList.filter(item => item.uid !== file.uid)
})
if (onRemove) {
onRemove(file)
}
}
// FileList是一个类数组对象,想在类数组上使用数组的方法,需要先将类数组转换为数组
const uploadFiles = (files: FileList) => {
let postFiles = Array.from(files)
postFiles.forEach(file => {
if(!beforeUpload){// 没有beforeUpload,直接上传
post(file)
}else{
const result = beforeUpload(file)
if(result && result instanceof Promise){
result.then(res => post(res))
}else if(result !== false) {
post(result)
}
}
})
}
const post = (file: File) => {
let _file: UploadFile = {
uid: Date.now() + 'upload-file',
status: 'ready',
name: file.name,
size: file.size,
percent: 0,
raw: file
}
//更新数据列表
//setFileList([_file, ...fileList])
//如果我们选中了多个文件,但是只有一个上传了,因为上一行代码中,fileList多次调用post时,fileList还没更新到最新值,
//所以最后就剩了一个file,所以还是需要使用回调函数的方式更新,将上面代码注释,改为下面这行:
setFileList(prevList => {
return [_file, ...prevList]
})
const formData = new FormData()
formData.append(name || 'file', file)
//把data添加进formData一起上传
if (data) {
Object.keys(data).forEach(key => {
formData.append(key, data[key])
})
}
axios.post(action, formData,{
headers:{
...headers,// 添加自定义headers
'Content-Type': 'multipart/form-data'
}
withCredentials,// 是否携带cookie
//此时我们需要拿到onProgress事件,axios给我们提供了一个onUploadProgress来显示上传进度
onUploadProgress: (e) => {
let percentage = Math.round((e.loaded * 100) / e.total) || 0;
if(percentage < 100) {
updateFileList(_file, { percent: percentage, status: 'uploading'})
if(onProgress) {
onProgress(percentage, file)
}
}
}
}).then(res => {
updateFileList(_file, {status: 'success', response: resp.data})
console.log(res)
if(onSuccess){
onSuccess(res.data, file)//第一个参数是服务器返回的数据,第二个参数是file对象
}
if(onChange) {
onChange(file)
}
}).catch(err => {
updateFileList(_file, { status: 'error', error: err})
console.error(err)
if(onError){
onError(err, file)//第一个参数是err对象,第二个参数是file对象
}
if(onChange) {
onChange(file)
}
})
}
return (
<div className='viking-upload-component'>
<button btnType='primary' onClick={handleClick}}>Upload File</button>
<input
className='viking-file-input'
style={{display: 'none'}}
ref={fileInput}
onChang={handleFileChange}
type='file'
accept={accept}
multiple={multiple} />
<UploadList
fileList={fileList}
onRemove={handleRemove}
/>
</div>
)
}
拖拽上传
当有文件被拖拽到区域时,触发DragOver,给区域添加一个class,增强区域效果,接下来,若文件被拖出区域,删除特定的class,区域变回初始状态,所以这里需要一个state来管理class的添加与删除,若你拖拽到该区域,然后松开了鼠标,文件就被drag进去了,此时触发onDrag事件,我们能从该事件对象中拿到被drag的文件,他是个FileList,然后我们将该文件传到外面去,让upload事件完成整个上传的工作。流程图如下:
通过一个属性控制是否开启拖拽上传,新建src/components/Upload/dragger.tsx。
import React, { FC, useState, DragEvent } from 'react'
import classNames from 'classnames'
interface DraggerProps {
//我们需要一个事件,来告诉我们被drag的文件
onFile: (files: FileList) => void;
}
export const Dragger: FC<DraggerProps> = (props) => {
const { onFile, children } = props
//文件是否被拖拽到区域
const [ dragOver, setDragOver ] = useState(false)
const klass = classNames('viking-uploader-dragger', {
'is-dragover': dragOver// 如果文件被拖拽到区域,添加is-dragover类名
})
const handleDrop = (e: DragEvent<HTMLElement>) => {
e.preventDefault()//先禁止掉默认事件
setDragOver(false)//设置变量,文件是否被拖拽到区域
//e.dataTransfer.files可以拿到fileList
onFile(e.dataTransfer.files)
}
const handleDrag = (e: DragEvent<HTMLElement>, over: boolean) => {
e.preventDefault()//先禁止掉默认事件
setDragOver(over)//设置变量,文件是否被拖拽到区域
}
//ondragover 事件在可拖动元素或选取的文本正在拖动到放置目标时触发。
//ondragleave 事件在可拖动的元素或选取的文本移出放置目标时执触发。
//ondrop 事件在可拖动元素或选取的文本放置在目标区域时触发。
return (
<div
className={klass}
onDragOver={e => { handleDrag(e, true)}}
onDragLeave={e => { handleDrag(e, false)}}
onDrop={handleDrop}
>
{children}
</div>
)
}
export default Dragger;
在upload.tsx中使用,src/components/Upload/upload.tsx完整代码:
import React, { FC, useRef, ChangeEvent, useState } from 'react'
import axios from 'axios'
import UploadList from './uploadList'
import Dragger from './dragger'
export type UploadFileStatus = 'ready' | 'uploading' | 'success' | 'error'
export interface UploadFile {
uid: string;
size: number;
name: string;
status?: UploadFileStatus;
percent?: number;
raw?: File;
response?: any;
error?: any;
}
export interface UploadProps {
action: string;
defaultFileList?: UploadFile[];
beforeUpload? : (file: File) => boolean | Promise<File>;
onProgress?: (percentage: number, file: File) => void;
onSuccess?: (data: any, file: File) => void;
onError?: (err: any, file: File) => void;
onChange?: (file: File) => void;
onRemove?: (file: UploadFile) => void;
headers?: {[key: string]: any };
name?: string;
data?: {[key: string]: any };
withCredentials?: boolean;// 是否携带cookie
accept?: string;
multiple?: boolean;
drag?: boolean;// 是否拖拽上传
}
export const Upload: FC<UploadProps> = (props) => {
const {
action,
defaultFileList,
beforeUpload,
onProgress,
onSuccess,
onError,
onChange,
onRemove,
name,
headers,
data,
withCredentials,
accept,
multiple,
children,
drag,
} = props
const fileInput = useRef<HTMLInputElement>(null)
const [ fileList, setFileList ] = useState<UploadFile[]>(defaultFileList || [])
// 更新列表的某一项(updateFile项),
const updateFileList = (updateFile: UploadFile, updateObj: Partial<UploadFile>) => {
// 上传过程中更改状态。setFileList不仅可以接收新值,也可以接收函数,函数的参数就是当前的值
setFileList(prevList => {
return prevList.map(file => {
if (file.uid === updateFile.uid) {
//用我们要更新的属性值,覆盖该项的属性值
return { ...file, ...updateObj }
} else {
return file
}
})
})
}
const handleClick = () => {
if (fileInput.current) {
fileInput.current.click()
}
}
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
const files = e.target.files
if(!files) {
return
}
uploadFiles(files)
if (fileInput.current) {
fileInput.current.value = ''
}
}
const handleRemove = (file: UploadFile) => {
setFileList((prevList) => {
return prevList.filter(item => item.uid !== file.uid)
})
if (onRemove) {
onRemove(file)
}
}
const uploadFiles = (files: FileList) => {
let postFiles = Array.from(files)
postFiles.forEach(file => {
if (!beforeUpload) {
post(file)
} else {
const result = beforeUpload(file)
if (result && result instanceof Promise) {
result.then(processedFile => {
post(processedFile)
})
} else if (result !== false) {
post(file)
}
}
})
}
const post = (file: File) => {
let _file: UploadFile = {
uid: Date.now() + 'upload-file',
status: 'ready',
name: file.name,
size: file.size,
percent: 0,
raw: file
}
//更新数据列表
//setFileList([_file, ...fileList])
//如果我们选中了多个文件,但是只有一个上传了,因为上一行代码中,fileList多次调用post时,fileList还没更新到最新值,
//所以最后就剩了一个file,所以还是需要使用回调函数的方式更新,将上面代码注释,改为下面这行:
setFileList(prevList => {
return [_file, ...prevList]
})
const formData = new FormData()
formData.append(name || 'file', file)
//把data添加进formData一起上传
if (data) {
Object.keys(data).forEach(key => {
formData.append(key, data[key])
})
}
axios.post(action, formData, {
headers: {
...headers,// 添加自定义headers
'Content-Type': 'multipart/form-data'
},
withCredentials,// 是否携带cookie
onUploadProgress: (e) => {
let percentage = Math.round((e.loaded * 100) / e.total) || 0;
if (percentage < 100) {
updateFileList(_file, { percent: percentage, status: 'uploading'})
if (onProgress) {
onProgress(percentage, file)
}
}
}
}).then(resp => {
updateFileList(_file, {status: 'success', response: resp.data})
if (onSuccess) {
onSuccess(resp.data, file)
}
if (onChange) {
onChange(file)
}
}).catch(err => {
updateFileList(_file, { status: 'error', error: err})
if (onError) {
onError(err, file)
}
if (onChange) {
onChange(file)
}
})
}
return (
<div
className="viking-upload-component"
>
<div className="viking-upload-input"
style={{display: 'inline-block'}}
onClick={handleClick}>
{/*
把固定的触发元素改为children传入,一开始我们这里使用的是button,现在使用children代替
如果drag为true,允许拖拽上传,那么就是用drag组件,否则之间展示children
*/}
{drag ?
<Dragger onFile={(files) => {uploadFiles(files)}}>
{children}
</Dragger>:
children
}
<input
className="viking-file-input"
style={{display: 'none'}}
ref={fileInput}
onChange={handleFileChange}
type="file"
accept={accept}
multiple={multiple}
/>
</div>
<UploadList
fileList={fileList}
onRemove={handleRemove}
/>
</div>
)
}
Upload.defaultProps = {
name: 'file'
}
export default Upload;