Python基于React-Dropzone實(shí)現(xiàn)上傳組件的示例代碼
這次我要講述的是在React-Flask框架上開(kāi)發(fā)上傳組件的技巧。我目前主要以React開(kāi)發(fā)前端,在這個(gè)過(guò)程中認(rèn)識(shí)到了許多有趣的前端UI框架——React-Bootstrap、Ant Design、Material UI、Bulma等。而比較流行的上傳組件也不少,而目前用戶(hù)比較多的是jQuery-File-Upload和Dropzone,而成長(zhǎng)速度快的新晉有Uppy和filepond。比較惋惜的是Fine-Uploader的作者自2018年后就決定不再維護(hù)了,原因作為后來(lái)者的我就不多過(guò)問(wèn)了,但請(qǐng)各位尊重每一位開(kāi)源作者的勞動(dòng)成果。
這里我選擇React-Dropzone,原因如下:
基于React開(kāi)發(fā),契合度高 網(wǎng)上推薦度高,連Material UI都用他開(kāi)發(fā)上傳組件 主要以 Drag 和 Drop 為主,但是對(duì)于傳輸邏輯可以由開(kāi)發(fā)者自行設(shè)計(jì)。例如嘗試用socket-io來(lái)傳輸file chunks。對(duì)于node全棧估計(jì)可行,但是我這里使用的是Flask,需要將Blob轉(zhuǎn)ArrayBuffer。但是如何將其在Python中讀寫(xiě),我就沒(méi)進(jìn)行下去了。 實(shí)例演示1. axios上傳普通文件:通過(guò)yarn將react-dropzone和引入:
yarn add react-dropzone axios
前端js如下(如有缺失,請(qǐng)自行修改):
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’;/*** 計(jì)算文件大小* @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 filessetFiles([]) },[]) return (<section className='container'><div {...getRootProps({className: ’dropzone’})}> <input {...getInputProps()} /> <p>拖動(dòng)文件或點(diǎn)擊選擇文件😊</p></div><div className='demo-infinite-container'> <InfiniteScrollinitialLoad={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’: ’沒(méi)有文件!’}), 200files = request.files.getlist(’files’)for file in files: if file:# 通過(guò)拼音解決secure_filename中文問(wèn)題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. 大文件導(dǎo)入:
通過(guò)file.slice()方法生成文件的chunks。不要用Promise.all容易產(chǎn)生非順序型的請(qǐng)求,導(dǎo)致文件損壞。
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 existsif 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結(jié)語(yǔ)
文件傳輸一直都是HTTP的痛點(diǎn),尤其是大文件傳輸。最好的方式是自己做個(gè)Client,通過(guò)FTP和FTPS的協(xié)議進(jìn)行傳輸。第二種來(lái)自于大廠很中心化的方法,通過(guò)文件的checksum來(lái)確定文件是否已經(jīng)上傳了,來(lái)營(yíng)造秒傳的效果。第三種來(lái)自去中心化的Bittorrent的方法每一個(gè)用戶(hù)做文件種子,提供文件傳輸?shù)妮o助,目前國(guó)內(nèi)并沒(méi)有普及使用。
到此這篇關(guān)于Python基于React-Dropzone實(shí)現(xiàn)上傳組件的示例代碼的文章就介紹到這了,更多相關(guān)Python React-Dropzone上傳組件內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!
相關(guān)文章:
1. 怎樣才能用js生成xmldom對(duì)象,并且在firefox中也實(shí)現(xiàn)xml數(shù)據(jù)島?2. 得到XML文檔大小的方法3. 詳解CSS偽元素的妙用單標(biāo)簽之美4. 小技巧處理div內(nèi)容溢出5. XML入門(mén)的常見(jiàn)問(wèn)題(二)6. XHTML 1.0:標(biāo)記新的開(kāi)端7. html小技巧之td,div標(biāo)簽里內(nèi)容不換行8. 使用Spry輕松將XML數(shù)據(jù)顯示到HTML頁(yè)的方法9. 解析原生JS getComputedStyle10. ASP刪除img標(biāo)簽的style屬性只保留src的正則函數(shù)
