vue3 + ts + element plus 实现自定义分片上传组件以及上传进度弹窗

背景:vue3 + typescript + elementplus 开发的后台管理系统,由于多个页面需要上传图片/视频/音频,所以将上传逻辑写成一个公共组件,如果上传文件超过5M,则需要分片上传,从0开始分片,前端传递给后台接口参数chunk 0表示第一片,1表示第二片,2表示第三片。。。依次类推,如果文件没有超过5M,则不分片。

效果:页面点击上传按钮(可自定义传入上传样式)调起电脑的文件选择,选中文件后直接上传后台服务器,页面展示上传进度条。

vue3 + ts + element plus 实现自定义分片上传组件以及上传进度弹窗_第1张图片

vue3 + ts + element plus 实现自定义分片上传组件以及上传进度弹窗_第2张图片

vue3 + ts + element plus 实现自定义分片上传组件以及上传进度弹窗_第3张图片
vue3 + ts + element plus 实现自定义分片上传组件以及上传进度弹窗_第4张图片

vue3 + ts + element plus 实现自定义分片上传组件以及上传进度弹窗_第5张图片

代码实现:

  1. template代码
<template>
    <div class="upload">
   	 	
        <el-upload
            ref="uploadRefs"
            :action="action"
            :multiple="multiple"
            :limit="limit"
            :auto-upload="false"
            :show-file-list="false"
            :on-change="onUpload"
            :accept="getAccept"
        >
        	
            <slot>slot>
        el-upload>
        
        <el-dialog
            v-model="showProgress"
            title="上传进度"
            :close-on-click-modal="false"
            width="500px"
            :modal="false"
            :show-close="false"
        >
            <div class="file-list p-4">
                <div class="mb-5">
                	
                    <div>{{ fileName }}div>
                    <div class="flex-1">
                    	
                        <el-progress
                            :percentage="uploadPercentage"
                            :format="format"
                            max="100"
                        >el-progress>
                    div>
                div>
            div>
        el-dialog>
    div>
template>
  1. typescript代码
<script lang="ts">
// 使用axios进行上传接口
import axios from 'axios'
import { computed, defineComponent, ref, shallowRef } from 'vue'
// 反馈提示(根据实际项目进行修改)
import feedback from '@/utils/feedback'
import type { ElUpload } from 'element-plus'
// 获取上传图片、视频、音频token接口(根据实际项目情况进行修改)
import { getImgToken, getVodToken, getAudioToken } from '@/api/mediaResources'
export default defineComponent({
    props: {
        // 上传文件类型,不传默认图片类型,传递'video'只选择视频文件,传递'audio'只选择音频文件
        type: {
            type: String,
            default: 'image'
        },
        // 是否支持多选
        multiple: {
            type: Boolean,
            default: true
        },
        // 多选时最多选择几条
        limit: {
            type: Number,
            default: 10
        },
        // 是否分片上传
        isChunk: {
            type: Boolean,
            default: true
        }
    },
    emits: ['change', 'error'],
    setup(props, { emit }) {
		// 计算文件总分片数
		// 两个入参,file:文件流,chunkSize:分片大小
        function sliceFile(file: File, chunkSize: number): Blob[] {
            const chunks: Blob[] = []
            let start = 0
            while (start < file.size) {
                const chunk = file.slice(start, start + chunkSize)
                chunks.push(chunk)
                start += chunkSize
            }
            return chunks
        }
        // 文件名称,渲染进度弹窗时使用
        const fileName = ref('')
        // 格式化进度,使用百分比进行展示
        const format = (percentage: any) => `${percentage}%`
        const uploadRefs = shallowRef<InstanceType<typeof ElUpload>>()
        // 上传接口
        const action = 'https://up.rmt.omtech.cn/api/upload'
        // 上传百分比
        const uploadPercentage = ref(0)
        // 分片大小,默认5M(可根据实际项目分片大小进行修改)
        const chunkSize = 5 * 1024 * 1024
        // 控制进度条弹窗显示与隐藏
        const showProgress = ref(false)
        
		// 上传事件
        const onUpload = async (File: any) => {
        	// 不分片文件提示(根据实际项目进行修改)
            if (!props.isChunk && File.size > 5 * 1024 * 1024) {
                feedback.msgError('文件大小不能超过5MB!')
                return
            }
            const chunks = sliceFile(File.raw, chunkSize)
            // 总分片数
            const totalChunks = chunks.length
            // 文件从0开始分片
            let uploadedChunks = 0
            // 打开进度条弹窗
            showProgress.value = true
            // 设置文件名称
            fileName.value = File.name
            // 获取上传token(根据实际项目进行修改/删除)
            const keyAndtoken = await (File.raw.type.includes('image')
                ? getImgToken
                : File.raw.type.includes('video')
                ? getVodToken
                : getAudioToken)({
                name: File.name,
                timestamp: File.raw.lastModified,
                size: File.size,
                chunk: 0,
                chunks: totalChunks
            })
            // 定义上传完成后的文件数据,传递给父组件的数据
            let responseFile = {}
            // 循坏上传,一片上传完成后再进行下一片的上传
            for (let i = 0; i < totalChunks; i++) {
                try {
                	// 传递给后台上传接口的参数,form-data格式(根据实际项目进行修改)
                    const formData = new FormData()
                    // key
                    formData.append('key', keyAndtoken.key)
                    // token
                    formData.append('token', keyAndtoken.token)
                    // 文件名称
                    formData.append('name', File.name)
                    // 时间戳
                    formData.append('timestamp', File.raw.lastModified)
                    // 分片文件流
                    formData.append('file', chunks[i])
                    // 当前分片数
                    formData.append('chunk', i.toString())
                    // 总分片数
                    formData.append('chunks', totalChunks.toString())
                    const response = await axios.post(action, formData)
                    // 上传接口响应成功
                    if (response.status === 200) {
                        if (response.data.code === 0) {
                        	// 上传成功后,进行下一片的上传
                            uploadedChunks++
                            // 上传进度条更新
                            uploadPercentage.value = Math.round(
                                (uploadedChunks / totalChunks) * 100
                            )
                            // 获取上传成功后接口返回的文件数据
                            responseFile = response.data.data
                        } else {
                        	// 上传失败,页面提示
                            feedback.msgError(response.data.message)
                            // 上传失败,停止上传
                            break
                        }
                    } else {
                    	// 上传失败,停止上传
                        console.error(`Failed to upload chunk ${i}`)
                        feedback.msgError(`上传第${i}片失败`)
                        break
                    }
                } catch (error) {
                	// 上传失败,停止上传
                    console.error('Error uploading chunk:', error)
                    feedback.msgError('上传失败')
                    break
                }
            }
            // 设置进度条百分比
            uploadPercentage.value = 100
            setTimeout(() => {
            	// 关闭进度条弹窗
                showProgress.value = false
                uploadPercentage.value = 0
                // 将上传成功后的文件信息传递给父组件(根据实际项目需要的数据进行修改,博主这里传递了接口数据和文件大小)
                emit('change', { responseFile: responseFile, size: File.size })
            }, 500)
        }
		// 设置上传文件的格式
        const getAccept = computed(() => {
            switch (props.type) {
                case 'image':
                    return '.jpg,.png,.gif,.jpeg,.ico,.bmp'
                case 'video':
                    return '.wmv,.avi,.mov,.mp4,.flv,.rmvb'
                case 'audio':
                    return '.mp3,.wav,.aac,.flac,.ogg'
                default:
                    return '*'
            }
        })
        return {
            uploadRefs,
            format,
            fileName,
            uploadPercentage,
            action,
            showProgress,
            getAccept,
            onUpload
        }
    }
})
</script>
  1. 页面使用上传组件(已在全局引入,页面不需要单独引入,直接使用即可,根据具体项目进行修改)
<upload
	 type="image"
	 :show-progress="true"
	 @change="handleCommon"
	 ><div class="upload-btn">图片div>
upload>
<upload
	 type="video"
	 :show-progress="true"
	 @change="handleCommon"
	 ><div class="upload-btn">视频div>
upload>
<upload
	 type="audio"
	 :show-progress="true"
	 @change="handleCommon"
	>
	 <div class="upload-btn">音频div>
upload>
// 获取文件上传后的接口数据
const handleCommon = (data) => {
	console.log(data, '子组件传递过来的数据')
	...
	...
	//进行页面渲染等操作
}

结语:整体分片上传组件是比较简单的,重点就在计算分片的函数,祝大家永不宕机,永无bug

你可能感兴趣的:(前端,typescript,上传组件,elementplus,vue3)