欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-12 23:41:12
目录这次我要讲述的是在react-flask框架上开发上传组件的技巧。我目前主要以react开发前端,在这个过程中认识到了许多有趣的前端ui框架——react-bootstrap、ant design...

这次我要讲述的是在react-flask框架上开发上传组件的技巧。我目前主要以react开发前端,在这个过程中认识到了许多有趣的前端ui框架——react-bootstrapant designmaterial uibulma等。而比较流行的上传组件也不少,而目前用户比较多的是jquery-file-uploaddropzone,而成长速度快的新晋有uppy和。比较惋惜的是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上传组件内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!