手写原生input上传file组件

效果展示:

利用input的type='file'类型,获取上传file文件,用compressorjs工具压缩到合适的尺寸大小,调用后端接口上传,后端返回对应访问地址,前端然后put直推oss

组件封装代码:

<template>
    <div>
        <div class='upload-box'>
            <div v-for="(file, index) in files" :key="index" class="file-preview">
                <div style="position: relative;">
                    <img :src="file.preview" :alt="file.name" class="preview-image" @click="previewImage(file)">
                    <img :src="require('../assets/paper/analysis/delIcon.png')" class="delIcon"
                        @click.stop="delFile(index)" v-if="file.progress == 100">
                    <div class="loading" v-else>
                        <van-loading size="24px" vertical color="#fff">上传中...</van-loading>
                    </div>
                </div>
                <!-- <div class="progress">
                    <div class="progress-bar" :style="{ width: `${file.progress}%` }"></div>
                </div> -->
            </div>
            <div class='upload-btn' v-if="show">
                <img :src="require('../assets/paper/analysis/input.png')" class="addIcon" @click="triggerFileInput">
                <input type='file' :accept='accept' @change='handleFileChange' ref="fileInput" :multiple="multiple">
            </div>
        </div>
    </div>
</template>

<script>
import Compressor from 'compressorjs';
import { ImagePreview } from 'vant';
import { Loading } from 'vant';
import { getUploadTokenUrl } from '@/api/paper';
export default {
    props: {
        // 是否开启多选
        multiple: {
            type: Boolean,
            default: true
        },
        // 允许上传的文件类型
        accept: {
            type: String,
            default: 'image/*'
        },
        // 最大上传数量
        maxNum: {
            type: Number,
            default: 20
        },
    },
    data() {
        return {
            files: [],
            show: true,
            abortController: new AbortController(), // 用于取消请求
            imageUrl: [],
        };
    },
    watch: {
        files: {
            handler(val) {
                this.$emit('uploadSuccess', { imageUrl: this.imageUrl, files: this.files });

            },
            deep: true // 开启深度监听
        }
    },
    beforeDestroy() {
        this.abortController.abort()
    },
    methods: {
        handleFileChange(e) {
            const selectedFiles = e.target.files;
            if (!selectedFiles.length) return;
            if (selectedFiles.length + this.files.length == this.maxNum) {
                this.show = false
            }
            // 检查文件数量
            if (selectedFiles.length + this.files.length > this.maxNum) {
                this.$toast(`一次最多只能上传${this.maxNum}张图片`);
                return;
            }

            // 生成文件预览并添加到 files 数组
            for (let i = 0; i < selectedFiles.length; i++) {
                const file = selectedFiles[i];
                const reader = new FileReader();
                reader.onload = (event) => {
                    this.files.push({
                        suffix: file.name.substring(file.name.lastIndexOf('.')),// 截取图片后辍名,
                        name: file.name,
                        size: file.size,
                        preview: event.target.result,
                        progress: 0,
                        file
                    });
                };
                reader.readAsDataURL(file);
            }

            // 开始上传文件
            setTimeout(() => {
                this.uploadFiles();
            }, 100);
        },
        async uploadFiles() {
            // 压缩并添加文件到 formData 中
            const promises = this.files.map(el => {
                return new Promise((resolve, reject) => {
                    new Compressor(el.file, {
                        quality: 0.45,
                        minWidth: 15,
                        maxHeight: 2048,
                        checkOrientation: false,
                        success: (compressedFile) => {
                            el.file = compressedFile
                            resolve(el);
                        },
                        error: (err) => {
                            reject(err);
                        }
                    });
                });
            });

            try {
                let fileList = await Promise.all(promises);
                this.imageUrl = []
                fileList.forEach(file => {
                    this.sendRequest(file);
                })
            } catch (error) {
                console.error('Error during compression:', error);
            }
        },
        sendRequest(e) {
            let params = { suffix: e.suffix }
            getUploadTokenUrl(params, this.abortController.signal).then(res => {
                if (res.code == 200) {
                    let xhr = new XMLHttpRequest()
                    xhr.open('PUT', res.data.ossTokenUrl, true)
                    xhr.setRequestHeader("Content-Type", "application/octet-stream")

                    // 监听上传进度
                    xhr.upload.onprogress = (event) => {
                        if (event.lengthComputable) {
                            e.progress = Math.round((event.loaded / event.total) * 100);
                        }
                    };

                    xhr.send(e.file)
                    // 判断
                    xhr.onreadystatechange = () => {
                        if (xhr.readyState == 4 && xhr.status == 200) {
                            let url = xhr.responseURL
                            let str = url.substring(0, url.lastIndexOf("?") + 1).replace('?', ''); //截取?之前
                            this.imageUrl.push(str)
                        }
                    }
                }
            })
        },
        //点击上传
        triggerFileInput() {
            this.$refs.fileInput.click(); // 触发 input 元素的 click 事件
        },
        //删除图片
        delFile(index) {
            // console.log(index);
            this.files.splice(index, 1);
            this.imageUrl.splice(index, 1);
            if (this.files.length < this.maxNum) {
                this.show = true
                console.log(this.files.length < this.maxNum);
            }
        },
        previewImage(e) {
            ImagePreview({
                images: [e.preview]
            });
        },
    }
}
</script>

<style scoped>
.upload-box {
    width: 100%;
    box-sizing: border-box;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-template-rows: auto auto;
    gap: 10px;
}

.upload-btn {
    position: relative;
    display: inline-block;
}

.preview-image {
    width: 150px;
    height: 150px;
    border-radius: 12px;
}

.loading {
    width: 150px;
    height: 150px;
    border-radius: 12px;
    background: rgba(0, 0, 0, 0.5);
    position: absolute;
    top: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

.delIcon {
    width: 24px;
    height: 24px;
    position: absolute;
    top: -10px;
    right: 0px;
}

.addIcon {
    width: 150px;
    height: 150px;
    cursor: pointer;
}

input {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    opacity: 0;
    cursor: pointer;
}

.progress {
    width: 100%;
    background-color: #f3f3f3;
    border-radius: 5px;
    overflow: hidden;
}

.progress-bar {
    height: 20px;
    background-color: #4caf50;
    width: 0;
    transition: width 0.1s;
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值