JS管理大文件上传和断点续传

网络请求封装

let instance = axios.create();
// 服务器地址
instance.defaults.baseURL = 'http:127.0.0.1:8888'; 
instance.defaults.headers['Content-Type'] = 'multipart/form-data';
instance.defaults.transformRequest = (data, headers) => {
    const contentType = headers['Content-Type'];
    if (contentType === "application/x-www-form-urlencoded") {
        return Qs.stringify(data);
    }
    return data;
};
instance.interceptors.response.use(response => {
    return response.data;
});

1.单文件上传

from-data 格式,我这里只贴上传部分的代码,至于获取文件等等一些操作我就不贴了,都比较简单

 const fromData = new FormData()
        fromData.append('file', _file)
        fromData.append('filename', _file.name)
        instance.post('/xxxx', fromData).then(data => {
            if (+data.code === 0) {
                alert('上传成功!')
            }
            return Promise.reject(data.codeText)
        }).catch(err => {
            alert('上传失败!,请稍后再试')
        }).finally(() => {
           // 不管成功还是失败的操作
        })

2.Base64上传

只需要将获取的图片专为base64,然后上传就可以了

 // 将文件转为BASE64
    const transfromBase64 = function (file) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader()
            reader.readAsDataURL(file)
            reader.onload = function (e) {
                resolve(e.target.result)
            }
        })
    }

上传代码

   // 将图片专为BASE64
        const base64 = await transfromBase64(file)
        try {
            const data = await instance.post('/xxxx', {
                // encodeURIComponent 确保base64的正确性
                file: encodeURIComponent(base64),
                filename: file.name
            }, {
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                }
            })
            if (+data.code === 0) {
                alert('上传成功')
            }
            throw data.codeText
        } catch (err) {
            alert('上传失败!')
        } finally {
           // 不管成功还是失败时的操作
        }

3.图片预览上传

图片预览 => 就是把用户获取到的图片转为base64,然后在将转换好的base64 赋值给img标签的src属性.

我们可以提前准备好一个img标签,把样式什么的都写好

const img = document.querySelector('#img')
const ipt =  document.querySelector('#ipt')

ipt.addEventLister('change', async function(){
    const file = ipt.files[0];
    img.src = await transfromBase64(file)
    // 其它操作
})

为了确保文件的唯一性,我们这里SparkMD5,生成文件的MD5哈希值( 可以省略 )

// 将文件转为BUFFER
    const transfromBuffer = function (file) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader()
            reader.readAsArrayBuffer(file)
            reader.onload = function (e) {
                const buffer = e.target.result
                const spark = new SparkMD5.ArrayBuffer()
                spark.append(buffer)
                const HASH = spark.end()
                const suffix = /.([a-zA-Z0-9]+)$/.exec(file.name)[1]
                console.log({
                    buffer,
                    hash: HASH,
                    suffix,
                    filename: `${HASH}.${suffix}`
                })
                resolve({
                    buffer,
                    hash: HASH,
                    suffix,
                    filename: `${HASH}.${suffix}`,
                })
            }
        })
    }

上传文件的代码

  const { filename } = await transfromBuffer(file)
        const fromData = new FormData()
        fromData.append('file', file)
        fromData.append('filename', filename)
        instance.post('/xxxx', fromData).then(data => {
            if (+data.code === 0) {
                alert('上传成功!')
            } else {
                return Promise.reject(data.codeText)
            }
        }).catch(err => {
            alert('上传失败!,请稍后再试')
        }).finally(() => {
            // 不管成功还是失败时操作,例如清除按钮loading等等
        })

4.单文件上传 + 进度条

所有的操作和步骤都跟 " 单文件上传 " 是一样,唯一不一样的是我们需要获取文件的总大小,和已经上传了多少

 try {
            const formData = new FormData()
            formData.append('file', file)
            formData.append('name', file.name)
            const data = await instance.post('/xxxx', formData, {
                onUploadProgress: (progressEvent) => {
                    const { loaded, total } = progressEvent
                    // loaded 表示已经上传的字节
                    // total 总数
                    // 可以在这里给提前准备好的div的宽度赋值即可
                    div.style.width = `${loaded / total * 100}%`
                }
            })
            if (+data.code === 0) {
                // 这里表示成功,成功后需要将提前准备的div的宽度设置为100%
                // 延迟函数.为了测试进度条
                await delay(300)
                alert('上传成功')
                return
            }
            throw data.codeText

        } catch (e) {
            alert('上传失败,请重试')
        } finally {
            // 不管成功还是失败时操作,例如清除按钮loading等等
        }

5.多文件上传

因为这里是多文件上传,我们需要展示用户选择的所有文件

这里使用li标签来展示用户选择的每一个文件

 inp.addEventListener('change', async function () {
        let file = Array.from(upload_inp.files)
        if (!file.length) return
        _files = file.map((file) => {
            return {
                file,
                filename: file.name,
                key: createRandom() // 唯一值,方便后期删除文件使用
            }
        })
        let str = ''
        _files.forEach((item, index) => {
            str += `<li key=${item.key}>
                        <span>文件${index + 1}:${item.filename}</span>
                        <span><em>移除</em></span>
                    </li>
            `
        })
        ul.style.display = 'block'
        ul.innerHTML = str
    })

// 随机数方法
    const createRandom = () => {
        // 使用随机数 + 时间戳在转16进制  降低随机数重复的概率
        const random = Math.random() * new Date().getTime()
        return random.toString(16).replace('.', '')
    }

随机数展示

li长这样

上传代码

ipt.addEventListener('click', async function () {
        // 获取所有的li 也就是用户选择的文件
        let upload_list_li = Array.from(upload_list.querySelectorAll('li'))
        _files = _files.map((item) => {
            let fm = new FormData,
                // 根据key找到对应的li
                curLi = upload_list_li.find(liBox => liBox.getAttribute('key') === item.key),
                // 根据li选中span,后续为了展示进度
                curSpan = curLi ? curLi.querySelector('span:nth-last-child(1)') : null;
            fm.append('file', item.file)
            fm.append('filename', item.filename)
            return instance.post('/xxx', fm, {
                onUploadProgress: (progressEvent) => {
                    // 获取上传进度
                    const { loaded, total } = progressEvent
                    if (curSpan) {
                        curSpan.innerHTML = `${((loaded / total) * 100).toFixed(2)}%`
                    }
                }
            }).then(data => {
                if (+data.code === 0) {
                    if (curSpan) {
                        curSpan.innerHTML = '100%'
                    }
                    return
                }
                return Promise.reject()
            })
        })
        Promise.all(_files).then(() => {
            alert('上传成功')
        }).catch(() => {
            alert('上传失败')
        }).finally(() => {
            // 不管成功还是失败时操作,例如清除按钮loading等等
        })
    })

6.拖拽上传

拖拽也很简单,一共有四个API,我们这里用到其中的两个就可以了,分别是 dragover、drop 事件

    div.addEventListener('dragenter', function (ev) {
        ev.preventDefault()
        console.log(ev, '进入了');
    })

    div.addEventListener('dragleave', function (ev) {
        ev.preventDefault()
        console.log('ev', '离开了');
    })

    div.addEventListener('dragover', function (ev) {
        console.log(ev, '拖动元素位于目标元素之上');
        // 当我们拖动文件到浏览器时,浏览器会默认打开一个新的窗口,展示你的文件
        // 所以我们需要组织事件的默认行文
        ev.preventDefault()
    })

    div.addEventListener('drop', function (ev) {
        ev.preventDefault()
        console.log(ev, '当拖动的元素被放置到目标元素上时触发');
    })

上传代码

 upload.addEventListener('dragover', function (ev) {
        // console.log(ev, '拖动元素位于目标元素之上');
        ev.preventDefault()
    })

    upload.addEventListener('drop', function (ev) {
        ev.preventDefault()
        const file = ev.dataTransfer.files[0]
        if (!file) return
        uploadFile(file) // 上传方法
        console.log(ev, '当拖动的元素被放置到目标元素上时触发');
    })

ev.dataTransfer 展示

上传代码

 const uploadFile = async file => {
        try {
            const fm = new FormData()
            fm.append('file', file)
            fm.append('filename', file.name)
            const data = await instance.post('/xxxx', fm)
            if (+data.code === 0) {
                alert('上传成功')
                return
            }
            throw data.codeText
        } catch (err) {
            alert('上传失败')
        } finally {
          // 不管成功还是失败时操作,例如清除按钮loading等等
        }
    }

7.大文件上传 

大文件上传其实利用Blob对象给我们提供的slice方法,放大文件按照要求切成指定大小的文件,然后在上传

 设置切片大小

let max = 1024 * 100 * 100, // 切片大小
          count = Math.ceil(file.size / max), // 切片数量
          index = 0, // 切片计数
          chunks = []; // 存放切片的数组
        if (count > 100) {
            max = file.size / 100;
            count = 100;
        }
        while (index < count) {
            chunks.push({
                file: file.slice(index * max, (index + 1) * max),
                filename: `${HASH}_${index + 1}.${suffix}`,
            })
            index++
        }

实现文件秒传,就是在文件上传之前,先向后端发送一个请求,来获取已经上传的文件

// transfromBuffer 代码在 *3.图片预览上传* 中
const { HASH, suffix } = await transfromBuffer(file) 
let alreadyList = [] // 已经上传的文件
        try {
            let data = await instance.get('/xxx', {
                params: {
                    HASH
                }
            });
            if (+data.code === 0) {
                alreadyList = data.fileList;
            }
        } catch (err) { }

上传代码

  chunks.forEach(chunk => {
            // 已经上传的无需在上传
            if (already.length > 0 && already.includes(chunk.filename)) {
                complate();
                return;
            }
            let fm = new FormData;
            fm.append('file', chunk.file);
            fm.append('filename', chunk.filename);
            instance.post('/xxxx', fm).then(data => {
                if (+data.code === 0) {
                    complate();
                    return;
                }
                return Promise.reject(data.codeText);
            }).catch(() => {
                alert('当前切片上传失败,请您稍后再试~~');
                clear();
            });
        });

complate 方法实现的事,当所有的切片上传完毕后,我们发送一个合并的请求,并将文件的HASH值和文件切片的数量传递给后端,然后后端开始和合并切片.

 const complate = async () => {
            // 管控进度条
            index++;
            console.log(`${index / count * 100}%`); // 进度条
            // 当所有切片都上传成功,我们合并切片
            if (index < count) return;
            try {
                data = await instance.post('/xxxx', {
                    HASH,
                    count
                }, {
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded'
                    }
                });
                if (+data.code === 0) {
                    alert(`恭喜您,文件上传成功`);
                    return;
                }
                throw data.codeText;
            } catch (err) {
                alert('切片合并失败,请您稍后再试~~');
            }
        };

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值