新闻动态

Python基于React-Dropzone实现上传组件的示例代码

发布日期:2022-02-11 16:50 | 文章来源:脚本之家

这次我要讲述的是在React-Flask框架上开发上传组件的技巧。我目前主要以React开发前端,在这个过程中认识到了许多有趣的前端UI框架——React-Bootstrap、Ant Design、Material UI、Bulma等。而比较流行的上传组件也不少,而目前用户比较多的是jQuery-File-Upload和Dropzone,而成长速度快的新晋有Uppy和filepond。比较惋惜的是Fine-Uploader的作者自2018年后就决定不再维护了,原因作为后来者的我就不多过问了,但请各位尊重每一位开源作者的劳动成果。

这里我选择React-Dropzone,原因如下:

  • 基于React开发,契合度高
  • 网上推荐度高,连Material UI都用他开发上传组件
  • 主要以 Drag 和 Drop 为主,但是对于传输逻辑可以由开发者自行设计。例如尝试用socket-io来传输file chunks。对于node全栈估计可行,但是我这里使用的是Flask,需要将Blob转ArrayBuffer。但是如何将其在Python中读写,我就没进行下去了。

实例演示

1. axios上传普通文件:

通过yarn将react-dropzone和引入:

yarn add react-dropzone axios

前端js如下(如有缺失,请自行修改):

import React, { 
 useState, 
 useCallback,
 useEffect,
} from 'react';
import {useDropzone} from 'react-dropzone';
import "./dropzone.styles.css"
import InfiniteScroll from 'react-infinite-scroller';
import {
 List,
 message,
 // Avatar,
 Spin,
} from 'antd';
import axios from 'axios';
/**
* 计算文件大小
* @param {*} bytes 
* @param {*} decimals 
* @returns 
*/
function formatBytes(bytes, decimals = 2) {
 if (bytes === 0) return '0 Bytes';
 const k = 1024;
 const dm = decimals < 0 ? 0 : decimals;
 const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
 const i = Math.floor(Math.log(bytes) / Math.log(k));
 return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
/**
* Dropzone 上传文件
* @param {*} props 
* @returns 
*/
function DropzoneUpload(props) {
 const [files, setFiles] = useState([])
 const [loading, setLoading] = useState(false);
 const [hasMore, setHasMore] = useState(true);
 const onDrop = useCallback(acceptedFiles => {
  setLoading(true);
  const formData = new FormData();
  smallFiles.forEach(file => {
formData.append("files", file);
  });
  axios({
method: 'POST',
url: '/api/files/multiplefiles',
data: formData,
headers: {
 "Content-Type": "multipart/form-data",
}
  })
  then(resp => {
addFiles(acceptedFiles);
setLoading(false);
  });
 }, [files]);
 // Dropzone setting
 const { getRootProps, getInputProps } = useDropzone({
  multiple:true,
  onDrop,
 });
 // 删除附件
 const removeFile = file => {
  const newFiles = [...files]
  newFiles.splice(newFiles.indexOf(file), 1)
  setFiles(newFiles)
 }
 useEffect(() => {
  // init uploader files
  setFiles([])
 },[])
 return (
  <section className="container">
  <div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
<p>拖动文件或点击选择文件😊</p>
  </div>
  
  <div className="demo-infinite-container">
<InfiniteScroll
 initialLoad={false}
 pageStart={0}
 loadMore={handleInfiniteOnLoad}
 hasMore={!loading && hasMore}
 useWindow= {false}
>
 <List
  dataSource={files}
  renderItem={item=> (<List.Item 
actions={[
 // <a key="list-loadmore-edit">编辑</a>, 
 <a key="list-loadmore-delete" onClick={removeFile}>删除</a>
]}
// extra={
 
// }
key={item.path}>
<List.Item.Meta 
 avatar={
  <>
  {
!!item.type && ['image/gif', 'image/jpeg', 'image/png'].includes(item.type) &&
<img 
 width={100}
 alt='logo'
 src={item.preview}
/>
  }
  </>
 }
 title={item.path}
 description={formatBytes(item.size)}
/></List.Item>
  )}
 >
  {loading && hasMore && (<div className="demo-loading-container">
<Spin /></div>
  )}
 </List>
</InfiniteScroll>
  </div>
  </section>
 );
}

flask代码:

def multiplefiles():
if 'files' not in request.files:
 return jsonify({'message': '没有文件!'}), 200
files = request.files.getlist('files')
for file in files:
 if file:
  # 通过拼音解决secure_filename中文问题
  filename = secure_filename(''.join(lazy_pinyin(file.filename))
  Path(UPLOAD_FOLDER + '/' + file_info['dir_path']).mkdir(parents=True, exist_ok=True)
  file.save(os.path.join(UPLOAD_FOLDER + '/' + file_info['dir_path'], filename))
return jsonify({'message': '保存成功!!'})

2. 大文件导入:

通过file.slice()方法生成文件的chunks。不要用Promise.all容易产生非顺序型的请求,导致文件损坏。

js代码:

const promiseArray = largeFiles.map(file => new Promise((resolve, reject) => {
 const chunkSize = CHUNK_SIZE;
 const chunks = Math.ceil(file.size / chunkSize);
 let chunk = 0;
 let chunkArray = new Array();
 while (chunk <= chunks) {
  let offset = chunk * chunkSize;
  let slice = file.slice(offset, offset+chunkSize)
  chunkArray.push([slice, offset])
  ++chunk;
 }
 const chunkUploadPromises = (slice, offset) => {
  const largeFileData = new FormData();
  largeFileData.append('largeFileData', slice)
  return new Promise((resolve, reject) => {
axios({
 method: 'POST',
 url: '/api/files/largefile',
 data: largeFileData,
 headers: {
  "Content-Type": "multipart/form-data"
 }
})
.then(resp => {
 console.log(resp);
 resolve(resp);
})
.catch(err => {
 reject(err);
})
  })
 };
 chunkArray.reduce( (previousPromise, [nextChunk, nextOffset]) => {
  return previousPromise.then(() => {
return chunkUploadPromises(nextChunk, nextOffset);
  });
 }, Promise.resolve());
 resolve();
}))

flask代码:

filename = secure_filename(''.join(lazy_pinyin(filename)))
Path(UPLOAD_FOLDER + '/' + file_info['dir_path']).mkdir(parents=True, exist_ok=True)
save_path = os.path.join(UPLOAD_FOLDER + '/' + file_info['dir_path'], filename)
# rm file if exists
if offset == 0 and save_path.exists(filename):
 os.remove(filename)
try:
 with open(save_path, 'ab') as f:
  f.seek(offset)
  f.write(file.stream.read())
  print("time: "+ str(datetime.now())+" offset: " + str(offset))
except  OSError:
 return jsonify({'Could not write to file'}), 500

结语

文件传输一直都是HTTP的痛点,尤其是大文件传输。最好的方式是自己做个Client,通过FTP和FTPS的协议进行传输。第二种来自于大厂很中心化的方法,通过文件的checksum来确定文件是否已经上传了,来营造秒传的效果。第三种来自去中心化的Bittorrent的方法每一个用户做文件种子,提供文件传输的辅助,目前国内并没有普及使用。

到此这篇关于Python基于React-Dropzone实现上传组件的示例代码的文章就介绍到这了,更多相关Python React-Dropzone上传组件内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

香港稳定服务器

版权声明:本站文章来源标注为YINGSOO的内容版权均为本站所有,欢迎引用、转载,请保持原文完整并注明来源及原文链接。禁止复制或仿造本网站,禁止在非www.yingsoo.com所属的服务器上建立镜像,否则将依法追究法律责任。本站部分内容来源于网友推荐、互联网收集整理而来,仅供学习参考,不代表本站立场,如有内容涉嫌侵权,请联系alex-e#qq.com处理。

相关文章

实时开通

自选配置、实时开通

免备案

全球线路精选!

全天候客户服务

7x24全年不间断在线

专属顾问服务

1对1客户咨询顾问

在线
客服

在线客服:7*24小时在线

客服
热线

400-630-3752
7*24小时客服服务热线

关注
微信

关注官方微信
顶部