网络编程
位置:首页>> 网络编程>> Python编程>> Python基于React-Dropzone实现上传组件的示例代码

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

作者:DisonTangor  发布时间:2021-04-01 19:53:03 

标签:Python,React-Dropzone,上传组件
目录
  • 实例演示

    • 1. axios上传普通文件:

    • 2. 大文件导入:

  • 结语

    这次我要讲述的是在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的方法每一个用户做文件种子,提供文件传输的辅助,目前国内并没有普及使用。

    来源:https://www.cnblogs.com/DisonTangor/p/15143516.html

    0
    投稿

    猜你喜欢

    手机版 网络编程 asp之家 www.aspxhome.com