Ajax 文件上傳顯示進度條(支持取消)- 供自己查看

建議先封裝一下XhrRequest:

export default class xhrHelper {
    static createXHR() {
        if (typeof XMLHttpRequest !== "undefined") {
            return new XMLHttpRequest();
        } else if (typeof ActiveXObject !== "undefined") {
            let ActiveXObject = window.ActiveXObject;
            let argumentsA = window.arguments;
            if (typeof argumentsA.callee.activeXString !== "string") {
                let versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"];
                for (let i = 0, len = versions.length; i < len; i++) {
                    try {
                        let xhr = new ActiveXObject(versions[i]);
                        argumentsA.callee.activeXString = versions[i];
                        return xhr;
                    } catch (e) {
                        //跳過
                    }
                }
            }
            return new ActiveXObject(argumentsA.callee.activeXString);
        } else {
            throw new Error("No XHR object available")
        }
    }

    static sendXHRequest(formData, uri, onloadstartHandler, onprogressHandler, onloadHandler, onreadystatechangeHandler) {
        // Get an XMLHttpRequest instance
        let xhr = this.createXHR();
        // Set up events
        xhr.upload.addEventListener('loadstart', onloadstartHandler, false);
        xhr.upload.addEventListener('progress', onprogressHandler, false);
        xhr.upload.addEventListener('load', onloadHandler, false);
        xhr.addEventListener('readystatechange', onreadystatechangeHandler, false);
        // Set up request
        xhr.open('POST', uri, true);
        // Fire!
        xhr.send(formData);

        return xhr;
    }
}

在調用xhr的時候,可以獲取xhr對象(代碼不可複製直接用,僅供參考)。

實例代碼如下,主要是查看Xhr的幾個事件,詳情查看:https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/upload

import React from 'react';
import VideoPlayer from '../../common/commonComponent/player';
import MessageModal from '../../common/commonComponent/modalMessage';
import ErrorHandler from "../../common/error-handler";
import ServiceProxy from '../../service-proxy';
import xhrHelper from '../../common/xhrRequestHelper';
import './my-video.css';

export default class VideoLabel extends React.Component {
    constructor() {
        super();

        this.state = {
            lastSrc: '',
            progress: 'Uploading...'
        };

        this.handleVideoChange = this.handleVideoChange.bind(this);
        this.handleVideoChangeNone = this.handleVideoChangeNone.bind(this);
        this.onloadstartHandler = this.onloadstartHandler.bind(this);
        this.onloadHandler = this.onloadHandler.bind(this);
        this.onprogressHandler = this.onprogressHandler.bind(this);
        this.onreadystatechangeHandler = this.onreadystatechangeHandler.bind(this);
        this.onabortHandler = this.onabortHandler.bind(this);
        this.uploadErrHandler = this.uploadErrHandler.bind(this);
        this.abortUpload = this.abortUpload.bind(this);
    }

    handleVideoChangeNone(){
        this.props.onChange({
            target: {
                name: this.props.name,
                value: ''
            }
        });
    }

    render() {
        return <div className="video-label">
            <MessageModal modalName={this.state.messageName}
                          modalContent={this.state.messageContent}
                          modalShow={this.state.messageModal}/>
            {
                this.state.loadingModal && <div className="uploading">
                    <div className="content-load">
                        <img src="//cdn-corner.resource.buzzbuzzenglish.com/icon_user_video.svg" alt=""/>
                        <div>{this.state.progress}</div>
                        <div className="cancel" onClick={this.abortUpload}>Cancel</div>
                    </div>
                </div>
            }
            <div className="label">
                {this.props.label || 'Video about myself'}
                {
                    this.props.value ?
                        <div className="upload">
                            <img src="//cdn-corner.resource.buzzbuzzenglish.com/upload/icon_Upload.svg" alt=""/>Reupload
                            <input type="file" className="upload-input-file" accept="video/*" ref="uploadAgain"
                                   onChange={(e) => this.handleVideoChange(e)}
                            />
                        </div> : ''
                }
            </div>
            <div className="video-with-label" style={this.props.style ? this.props.style : {}}>
                {
                    this.props.value ?
                        <VideoPlayer videoSrc={this.props.value}
                        /> :
                        <div className="upload-video" style={{height: document.documentElement.clientWidth * 9 / 16 > 450 ? 450 : document.documentElement.clientWidth * 9 / 16}}>
                            <img src="//cdn-corner.resource.buzzbuzzenglish.com/upload/icon_Upload.svg" alt=""/>
                            <div className="info">
                                <p className="title">Click to upload</p>
                                <p>Name/Age/Grade/Country/Hobbies/why do you want to be a BuzzKid Language, Pal/use landscape mode.</p>
                            </div>
                            <input type="file" className="upload-input-file" accept="video/*" ref="uploadFirst"
                                   onChange={(e) => this.handleVideoChange(e)}
                            />
                        </div>
                }
            </div>
        </div>
    }

    async handleVideoChange(e) {
        let video = e.target;

        try {
            if (video.files[0].type && video.files[0].type.indexOf('video') < 0 && video.files[0].size && video.files[0].size < 200 * 1024 * 1024) {
                //error
                this.setState({
                    messageModal: true,
                    messageName: 'error',
                    messageContent: video.files[0].type.indexOf('video') === 0 ? 'Wrong type file' : 'The file too large to upload'
                });
                this.closeMessageModal();
                return;
            }

            this.setState({loadingModal: true});

            let qiniu_token = await ServiceProxy.proxyTo({
                body: {
                    uri: '{config.endPoints.buzzService}/api/v1/qiniu/token',
                    method: 'GET'
                }
            });

            if (!qiniu_token.uptoken) {
                throw new Error('Wrong qiniu token');
            }

            let fileForm = new FormData();

            fileForm.append("name", video.files[0].name);
            fileForm.append("file", video.files[0]);
            fileForm.append("token", qiniu_token.uptoken);

            this.setState({
                resources_url: qiniu_token.resources_url,
                xhr: xhrHelper.sendXHRequest(fileForm, qiniu_token.upload_url, this.onloadstartHandler, this.onprogressHandler, this.onloadHandler, this.onreadystatechangeHandler)
            });
        } catch (ex) {
            //something wrong
            ErrorHandler.notify('Uploading failed', ex);
            this.setState({
                loadingModal: false,
                messageModal: true,
                messageContent: 'Upload failed, try later.'
            });

            this.closeMessageModal();
        }
    };

    closeMessageModal() {
        const interval = setTimeout(() => {
            if (this.state.messageModal) {
                this.setState({messageModal: false});
            }
            clearTimeout(interval);
            //browserHistory.push('/user?refresh=true');
        }, 2000)
    }

    onloadstartHandler(evt) {
        this.setState({
            progress: 'Uploading...'
        });
    }
    
    onloadHandler(evt) {

    }

    abortUpload(){
        if(this.state.xhr){
            this.state.xhr.abort();

            this.onabortHandler();
        }
    }

    async onabortHandler(){
        if(this.props.value){
            this.refs.uploadAgain.value = '';

            this.setState({
                progress: 'Canceled!'
            });
        }else{
            this.refs.uploadFirst.value = '';

            this.setState({
                progress: 'Canceled!'
            });
        }

        await new Promise(resolve => window.setTimeout(resolve, 2000));
        this.uploadErrHandler('Uploading cancelled!');
    }

    onprogressHandler(evt) {
        this.setState({
            progress: Math.floor(evt.loaded/evt.total*100) + '%'
        });
    }
    
    async onreadystatechangeHandler(evt) {
        let status = '', text = '', readyState = '';
        try {
            text = evt.target.responseText;
            status = evt.target.status;
            readyState = evt.target.readyState;
        }
        catch(e) {
            return;
        }
        if (readyState === 4 && status === 200 && text) {
            try {
                let result = JSON.parse(text);
                if (!result.key || !result.hash) {
                    this.uploadErrHandler();
                } else {
                    if(this.props.onChange){
                        this.props.onChange({
                            target: {
                                name: this.props.name,
                                value: this.state.resources_url + result.key
                            }
                        });
                    }
                    await new Promise(resolve => window.setTimeout(resolve, 2000));

                    this.setState({
                        loadingModal: false
                    });
                }
            }
            catch (ex){
                this.uploadErrHandler();
            }
        }
    }

    uploadErrHandler(meg){
        this.setState({
            loadingModal: false,
            messageModal: true,
            messageContent: meg || 'Upload failed, try later.'
        });

        this.closeMessageModal();
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章