arco-design for React Upload oss 自定义上传

in React with 0 comment

arco-design for React Upload oss 自定义上传

效果如下:

效果...

要素拆分

首先我们目前从已知的元素中能看到几个东西

1、loading
2、button
3、文件上传完成的显示

自定义上传

首先,我们需要编写自定义上传模块的代码。目前从接口得知,我们的 阿里云 oss 的上传地址如下:

{
    "code": 200,
    "message": "Success",
    "data": {
        "signedUrl": "http://***隐藏***-eu.oss-eu-west-1.aliyuncs.com/2536335147/Snipaste_2022-04-07_09-27-12.png?OSSAccessKeyId=LTAI5tHbTafrbPetMj8m7RxL&Expires=1649299000&Signature=06prxPq96xUmZ44uVauD%2BE0ndK4%3D"
    },
    "meta": []
}

自定义上传的前提,必须得到后端响应的上传地址。即可 Upload 中的 action

我们可以在 beforeUpload 中进行这一步操作,得到我们的 action

...
const [action, setAction] = useState('/');
...

/**
 * 自定义请求
 * @param {*} option
 * @returns
 */
const customRequest = async option => {
    const { onProgress, onError, onSuccess, file } = option;
    const xhr = new XMLHttpRequest();
    xhr.open('PUT', action);

    if (xhr.upload) {
        xhr.upload.onprogress = function (event) {
            let percent;
            if (event.total > 0) {
                percent = (event.loaded / event.total) * 100;
            }
            onProgress(parseInt(percent, 10), event);
        };
    }
    xhr.onerror = function error(e) {
        setLoading(false);
        onError(e);
    };
    xhr.onload = function onload() {
        if (xhr.status < 200 || xhr.status >= 300) {
            return onError(xhr.responseText);
        }
        onSuccess(xhr.responseText, xhr);
        setLoading(false);
        setTimeout(() => {
            changeFiles();
        }, 200);
    };

    const loadFileBob = file => {
        return new Promise(resolve => {
            let reader = new FileReader();
            reader.readAsArrayBuffer(file);
            let blob = null;
            reader.onload = e => {
                if (typeof e.target.result === 'object') {
                    blob = new Blob([e.target.result]);
                } else {
                    blob = e.target.result;
                }
                resolve(blob);
            };
        });
    };

    setLoading(true);
    const blob = await loadFileBob(file);
    xhr.send(blob);

    return {
        abort() {
            xhr.abort();
        },
    };
};


/**
 * 获取配置相关
 * @param {} param0
 */
const beforeUpload = async ({ name, size }) => {
    const { data, code, message } = await getOssUrl({ object: name });
    if (code !== 200) {
        Message.error(message);
        return;
    }
    setAction(data.signedUrl);
};

我们开始定义 Upload 组件

<Upload
    beforeUpload={beforeUpload}
    onAbort={() => {
        console.log('cancel');
    }}
    {...props}
    fileList={fileList}
    onChange={onChange}
    customRequest={customRequest}
>
    {props.text ? (
    <Button ref={uploadRef} icon={<IconUpload />} type="primary">
        {props.text}
    </Button>
    ) : (
    <></>
    )}
</Upload>

...props 为继承父组件传递的 props

uploadRef 主要是为了 Dom 节点操作

loading

xhr.send 为开始发送操作,所以我们在这一层面,进行 设置 loading 为 true

并且需要 在 onerror 和 上传完成中 设置 loading 为 false

文件上传完成的显示

因为我们目前,主要是向父组件传递 上传成功的文件,一个或者多个。我们可以再 onChange 中进行回调显示

/**
 * 变更
 */
const onChange = fileList => {
    const _fileList = fileList.map(file => {
        if (!file.url) {
            file.url = action.split('?')[0];
        }
        return file;
    });
    setFileList(_fileList);
};

const changeFiles = () => {
    setFileList(fileList => {
        let _fileList = fileList.filter(file => {
            return file.status === 'done';
        });

        const limit = props.limit ? props.limit : 1;
        if (limit === 1) {
            if (_fileList.length === 0) {
                props.onChange('');
                return;
            }
            props.onChange(fileList[0]['url']);
            return fileList;
        }

        props.onChange(fileList.map(file => file.url));
        setLoading(false);
        return fileList;
    });
};

完整代码如下

import { Button, Message, Spin, Upload } from '@arco-design/web-react';
import { IconUpload } from '@arco-design/web-react/icon';
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
import { getOssUrl } from '../apis/common';

const UploadComponent = (props, ref) => {
    const [action, setAction] = useState('/');
    const [fileList, setFileList] = useState(props.defaultFileList ? props.defaultFileList : []);
    const [loading, setLoading] = useState(false);
    const uploadRef = useRef();

    useImperativeHandle(ref, () => ({
        click: () => {
            console.log(uploadRef.current);
            uploadRef.current.click();
        },
    }));

    useEffect(() => {
        console.log('props', props);
        if (props.loadingchange) {
            props.loadingchange(loading);
        }
    }, [loading]);

    /**
    * 获取配置相关
    * @param {} param0
    */
    const beforeUpload = async ({ name, size }) => {
        const { data, code, message } = await getOssUrl({ object: name });
        if (code !== 200) {
            Message.error(message);
            return;
        }
        setAction(data.signedUrl);
    };

    /**
    * 自定义请求
    * @param {*} option
    * @returns
    */
    const customRequest = async option => {
        const { onProgress, onError, onSuccess, file } = option;
        const xhr = new XMLHttpRequest();
        xhr.open('PUT', action);

        if (xhr.upload) {
            xhr.upload.onprogress = function (event) {
                let percent;
                if (event.total > 0) {
                    percent = (event.loaded / event.total) * 100;
                }
                onProgress(parseInt(percent, 10), event);
            };
        }
        xhr.onerror = function error(e) {
            setLoading(false);
            onError(e);
        };
        xhr.onload = function onload() {
            if (xhr.status < 200 || xhr.status >= 300) {
                return onError(xhr.responseText);
            }
            onSuccess(xhr.responseText, xhr);
            setLoading(false);
            setTimeout(() => {
                changeFiles();
            }, 200);
        };

        const loadFileBob = file => {
            return new Promise(resolve => {
                let reader = new FileReader();
                reader.readAsArrayBuffer(file);
                let blob = null;
                reader.onload = e => {
                    if (typeof e.target.result === 'object') {
                        blob = new Blob([e.target.result]);
                    } else {
                        blob = e.target.result;
                    }
                    resolve(blob);
                };
            });
        };

        setLoading(true);
        const blob = await loadFileBob(file);
        xhr.send(blob);

        return {
            abort() {
                xhr.abort();
            },
        };
    };

    /**
    * 变更
    */
    const onChange = fileList => {
        const _fileList = fileList.map(file => {
            if (!file.url) {
                file.url = action.split('?')[0];
            }
            return file;
        });
        setFileList(_fileList);
    };

    const changeFiles = () => {
        setFileList(fileList => {
            let _fileList = fileList.filter(file => {
                return file.status === 'done';
            });

            const limit = props.limit ? props.limit : 1;
            if (limit === 1) {
                if (_fileList.length === 0) {
                    props.onChange('');
                    return;
                }
                props.onChange(fileList[0]['url']);
                return fileList;
            }

            props.onChange(fileList.map(file => file.url));
            setLoading(false);
            return fileList;
        });
    };

    return (
        <>
            <div style={{ display: `${loading ? 'block' : 'none'}` }}>
                <Spin />
            </div>
            <div style={{ display: `${loading ? 'none' : 'block'}` }}>
                <Upload
                    beforeUpload={beforeUpload}
                    onAbort={() => {
                        console.log('cancel');
                    }}
                    {...props}
                    fileList={fileList}
                    onChange={onChange}
                    customRequest={customRequest}
                >
                    {props.text ? (
                        <Button ref={uploadRef} icon={<IconUpload />} type="primary">
                            {props.text}
                        </Button>
                    ) : (
                        <></>
                    )}
                </Upload>
            </div>
        </>
    );
};

export default forwardRef(UploadComponent);

使用方式

<Upload accept="image/*" ref={uploadRef} onChange={uploadAvatar} text={'Upload'} />

<Upload
    accept=".mp4"
    // tip="Only video, .mp4 can be uploaded, and the size does not exceed 50MB"
    showUploadList={true}
    limit={1}
    text={'上传'}
    defaultFileList={
        item.video
            ? [
                  {
                      uid: '1',
                      name: item.video,
                      url: item.video,
                  },
              ]
            : []
    }
    onChange={v => formRef.current.setFieldValue('video', v)}
/>

github 代码

https://github.com/surest-sky/example/blob/master/React/ArcoDesignUpload

Comments are closed.