演示
实现
index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { message } from 'antd';
import Dragger from './Dragger';
import 'antd/dist/antd.css';
let props = {
name: "file",
action: "http://localhost:8080/upload",
onChange: (info: any) => {
if (info.file.status === 'done') {
message.success(info.file.name + '上传成功!');
} else if (info.file.status === 'error') {
message.error(info.file.name + '上传失败!');
}
}
}
ReactDOM.render(<Dragger {...props} />, document.getElementById('root'));
types.tsx
export interface UploadInfo {
file: File & {
status?: string,
percent: number,
_url?: string
},
fileList: FileList,
event?: ProgressEvent
}
export interface DragProps {
name: string,
action: string,
onChange: (uploadInfo: UploadInfo) => void
}
Dragger.tsx
import React, { useState, useCallback, useRef, MutableRefObject, SFC, RefObject, useEffect, DragEvent } from 'react';
import { UploadInfo, DragProps } from './types';
import { Icon, Progress } from 'antd';
import './Dragger.css';
const Dragger: SFC<DragProps> = (props: DragProps) => {
//function useRef<T = undefined>(): MutableRefObject<T | undefined>;
let [uploadInfos, setUploadInfos] = useState<UploadInfo[]>([]);
let dragCotainer: MutableRefObject<HTMLDivElement | undefined> = useRef<HTMLDivElement | undefined>();
let fileInput: MutableRefObject<HTMLInputElement | undefined> = useRef<HTMLInputElement | undefined>();
const ondragenter = (e: Event) => {
e.preventDefault();
e.stopPropagation();
}
const ondragover = (e: Event) => {
e.preventDefault();
e.stopPropagation();
}
const ondragleave = (e: Event) => {
e.preventDefault();
e.stopPropagation();
};
const upload = useCallback((fileList: FileList) => {
for (let i = 0; i < fileList.length; i++) {
let file: any = fileList[i];
let formData = new FormData();
let uploadInfo: UploadInfo = {
file,
fileList
}
formData.append('filename', file.name);
formData.append(props.name, file);
let xhr = new XMLHttpRequest();
xhr.open('POST', props.action);
xhr.responseType = 'json';
xhr.timeout = 1000;
xhr.upload.onprogress = xhr.onprogress = (e) => {
file.status = 'progress';
file.percent = +(e.loaded / e.total * 100).toFixed(2);
setUploadInfos([...uploadInfos]);
props.onChange(uploadInfo);
}
xhr.ontimeout = xhr.onerror = (e) => {
file.status = 'error';
setUploadInfos([...uploadInfos]);
props.onChange(uploadInfo);
}
xhr.onload = () => {
if (/(png|jpe?g|gif|jfif)/.test(xhr.response.url.split('.').pop())) {
console.log(xhr.response.url.split('.').pop());
file._url = xhr.response.url;
}
file.status = 'done';
setUploadInfos([...uploadInfos]);
props.onChange(uploadInfo);
}
xhr.send(formData);
uploadInfos.push(uploadInfo);
}
setUploadInfos([...uploadInfos]);
}, [props, uploadInfos])
const ondrop = useCallback((e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
if (e.dataTransfer) {
upload(e.dataTransfer.files);
}
}, [upload]);
useEffect(() => {
let container = dragCotainer.current!;
container.addEventListener('dragenter', ondragenter);
container.addEventListener('dragover', ondragover);
container.addEventListener('dragleave', ondragleave);
container.addEventListener('drop', (ondrop as any));
return () => {
container.removeEventListener('dragenter', ondragenter);
container.removeEventListener('dragover', ondragover);
container.removeEventListener('dragleave', ondragleave);
container.removeEventListener('drop', (ondrop as any));
}
}, [ondrop]);
return (
<div className="drag">
<input multiple type="file" onChange={e => {
upload(e.currentTarget.files!);
}} ref={fileInput as RefObject<HTMLInputElement>} />
<div className="drag-container" onClick={e => fileInput.current!.click()} ref={dragCotainer as RefObject<HTMLDivElement>}>
<Icon type='inbox' />
<span>Upload</span>
</div>
<div >
{
uploadInfos.map((item, index) => {
return <div key={index}>
<div>
<Icon type={item.file.status === 'progress' ? 'loading' : 'paper-clip'} />
<span>{item.file.name}</span>
</div>
<Progress status={item.file.status === 'error' ? 'exception' : undefined} percent={item.file.percent} />
{item.file._url ? <img alt='上传的图片' height={100} src={item.file._url} /> : null}
</div>
})
}
</div>
</div>
)
}
export default Dragger;
Dragger.css
.drag{
margin: 100px auto;
width:600px ;
}
.drag input[type='file']{
display: none;
}
.drag-container{
width: 100%;
height: 150px;
background: #fafafa;
border-radius: 5px;
border: 1px dashed #ccc;
transition: all .35s;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
cursor: pointer;
font-size: 20px;
}
.drag-container:hover{
border-color: #58a;
}
api.js
const Koa = require('koa');
const koaStatic = require('koa-static');
const koaBody = require('koa-body');
const path = require('path');
const fs = require('fs');
const app = new Koa();
app.use(async (ctx, next) => {
ctx.set('Access-Control-Allow-Origin', '*');
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Accept');
ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
if (ctx.method == 'OPTIONS') {
ctx.body = 200;
} else {
await next();
}
});
app.use(koaBody({
formidable: { uploadDir: path.resolve(__dirname, './uploads') },
multipart: true
})).use(koaStatic(path.resolve(__dirname, './uploads')));
app.use(async (ctx, next) => {
if (ctx.url === '/upload') {
let file = ctx.request.files.file;
let filename = path.basename(file.path) + path.extname(file.name);
fs.renameSync(file.path, path.join(path.dirname(file.path), filename));
ctx.body = { url: "http://localhost:8080/" + filename };
} else {
await next();
}
})
app.listen('8080', () => {
console.log('8080端口服务启动成功!');
})