图片上传

<template>
    <div class="image-upload-component" :class="{multiple: multiple}">
        <input type="file" class="input-file" :disabled="disabled" :accept="accept" :multiple="multiple" ref="inputFile" @change="fileChange" />

        <slot name="default">
            <template v-for="(row, index) in images">
                <div class="image-wrapper" :key="index">
                    <img :src="row.absolute" v-if="isSuccess(row.status)" />
                    <div class="failed" v-if="isError(row.status)">上传失败</div>

                    <div class="tools" v-if="isSuccess(row.status) || isError(row.status)">
                        <el-button type="text" size="mini" title="预览" icon="el-icon-search" v-if="isSuccess(row.status)" />
                        <el-button type="text" size="mini" title="上传" icon="el-icon-upload2" v-if="!multiple && !disabled" @click="$refs.inputFile.click()" />
                        <el-button type="text" size="mini" title="删除" icon="el-icon-delete" v-if="!disabled" @click="deleteFile(index)" />
                    </div>

                    <div class="loading" v-if="isWait(row.status)"><i class="el-icon-loading" /></div>
                </div>
            </template>
        </slot>

        <slot name="add" v-if="addShow">
            <div :disabled="disabled" class="add-wrapper" @click="$refs.inputFile.click()">
                <i class="el-icon-plus" />
            </div>
        </slot>

        <slot name="tips">
            <div class="upload-tips">
                <span>大小不能超过1MB</span>
                <span>图片尺寸为1:1比例</span>
                <span>图片只能为jpeg、jpg、png格式</span>
            </div>
        </slot>
    </div>
</template>

<script>

    export default {
        data (){
            return {
                UPLOAD_WAIT: 0, //等待
                UPLOAD_ING: 1, //上传中
                UPLOAD_SUCCESS: 2, //上传成功
                UPLOAD_FAILED: 3, //上传失败

                accept: '.jpeg,.jpg,.png',
                accepts: ['image/jpg', 'image/jpeg', 'image/png'],

                images: [], //
            };
        },

        props: {

            /**
             * 图片列表
             * @example @returns
             * [{
             *      relative: '/xxx/xxx.png',
             *      absolute: 'https://xxx/xxx.png'
             * }]
             */
            value: {
                type: Array,
                required: true
            },

            // 上传保存的文件夹
            folder: {
                type: String,
                required: true
            },

            // 是否支持多选
            multiple: {
                type: Boolean,
                default: false
            },

            // 是否禁用
            disabled: {
                type: Boolean,
                default: false
            },

            // 允许上传的文件大小,单位kb
            size: {
                type: Number,
                default: 1024
            },

            // 最大允许上传个数
            limit: {
                type: Number,
                default: 10
            }
        },

        watch: {
            value (arr){
                if( arr && arr instanceof Array && arr.length ){
                    for (let i = 0; i < arr.length; i++) {
                        let exist = this.images.find(row => row.relative == arr[i].relative);

                        if( !exist ) {
                            arr[i].status = 2;
                            this.recombination(arr[i]);
                        };
                    };
                } else {
                    this.images = [];
                };
            }
        },

        computed: {
            // 是否等待上传
            isWait ( status ){
                return status => {
                    return this.UPLOAD_WAIT == status;
                };
            },

            // 是否上传中
            isUping ( status ){
                return status => {
                    return this.UPLOAD_ING == status;
                };
            },

            // 是否上传成功
            isSuccess ( status ){
                return status => {
                    return this.UPLOAD_SUCCESS === status;
                };
            },

            // 是否上传失败状态
            isError ( status ){
                return status => {
                    return this.UPLOAD_FAILED === status;
                };
            },

            // 在部分情况下显示上传按钮
            addShow (){
                let { images, multiple, limit, disabled } = this
                ,   length = images.length;
                if( disabled ) return false;
                if( multiple && length < limit ) return true;
                if( !multiple && !length ) return true;
                return false;
            }
        },

        methods: {

            // 选择文件
            fileChange (e){
                let pomises = []
                ,   { files } = e.target
                ,   fileCount = files.length //本次选择的文件数
                ,   vacancy = this.limit - this.images.length //剩余可上传文件数
                ,   forNumber = vacancy > fileCount ? fileCount : vacancy;

                for (let i = 0; i < forNumber; i++) {
                    pomises.push(this.validateFile(files[i]));
                };

                Promise.all(pomises).then( files => {
                    for (let i = 0; i < files.length; i++) {
                        this.beginUpload(files[i]).then( file => {
                            this.recombination(file);
                            this.emitData();
                        });
                    };
                }).catch( msg => {
                    this.$message.warning( msg );
                }).finally(e => {
                    this.clearInput();
                });
            },

            // 验证文件是否符合规范
            validateFile (file){
                let image = new Image()
                ,   isSize = (file.size / 1024) > this.size
                ,   isAccept = this.accepts.includes(file.type);

                return new Promise((resolve, reject) => {
                    if(!isAccept){
                        reject('只能上传 jpeg、jpg、png 格式图片');
                    };

                    if (isSize) {
                        reject(`图片大小不能超过${this.renderSize(this.size)}`);
                    };

                    image.onload = e => {
                        if((image.width / image.height) == 1){
                            resolve(file);
                        } else {
                            reject(`请上传1:1比例的图片`);
                        };
                    };

                    image.onerror = e => {
                        reject('图片加载失败,请重新选择');
                    };

                    image.src = window.URL.createObjectURL(file);
                });
            },

            recombination (file){
                this.multiple ? this.images.push(file) : (this.images = [file]);
            },

            beginUpload (file){
                return new Promise((resolve, reject) => {
                    this.$alioss.upload({ file, folder: this.folder }).then(({ relative, absolute }) => {
                        resolve({ relative, absolute, status: this.UPLOAD_SUCCESS });
                    });
                }).catch(e => {
                    resolve({ status: this.UPLOAD_FAILED });
                });
            },

            // 清空文件选择器
            clearInput (){
                this.$refs.inputFile.value = '';
            },

            // 删除文件
            deleteFile (index){
                this.images.splice(index, 1);
                this.emitData();
            },

            // 只提交上传成功的文件
            emitData (){
                let data = this.images.filter(row => row.status == this.UPLOAD_SUCCESS).map( row => {
                    let { relative, absolute } = row;
                    return { relative, absolute };
                });

                this.$emit('input', data);
                this.$emit('change', data);
            },

            // 格式化文件大小
            renderSize( value = null ){
                if ( !value ) return "0B";
                let srcsize = parseFloat(value)
                ,   index = Math.floor(Math.log(srcsize) / Math.log(1024))
                ,   size = srcsize / Math.pow(1024, index)
                ,   unitArr = ["KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
                size = size.toFixed(0);//保留的小数位数
                return `${size}${unitArr[index]}`;
            }
        }
    };
</script>

<style lang="scss">
    .image-upload-component{
        display: flex;

        .input-file{
            display: none;
        }

        .image-wrapper,
        .add-wrapper{
            width: 120px;
            height: 120px;
            display: block;
            overflow: hidden;
            border-radius: 4px;
            box-sizing: border-box;
            border: 1px solid #DCDFE6;
        }

        .image-wrapper{
            display: inline-flex;
            position: relative;
            align-items: center;
            justify-content: center;

            img{
                max-width: 100%;
                max-height: 100%;
            }

            .tools,
            .failed,
            .loading{
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                display: flex;
                color: #fff;
                font-size: 2rem;
                align-items: center;
                position: absolute;
                justify-content: center;
                background: rgba(0, 0, 0, .5);
            }

            .failed{
                color: #ccc;
                font-size: 14px;
                background: #fff;
            }

            .tools{
                opacity: 0;
                transition: all .3s;
                pointer-events: none;

                .el-button{
                    color: #fff;
                    font-size: 18px;
                    cursor: pointer;

                    &:hover{
                        transform: scale(1.2);
                        text-shadow: 0 0 1px #fff;
                    }
                }
            }

            &:hover{

                .tools{
                    opacity: 1;
                    pointer-events: auto;
                }
            }
        }

        .add-wrapper{
            font-size: 2rem;
            cursor: pointer;
            color: #DCDFE6;
            transition: all .3s;
            align-items: center;
            display: inline-flex;
            border-style: dashed;
            justify-content: center;

            &:hover{
                color: #409EFF;
                border-color: #409EFF;
            }

            &[disabled]{
                pointer-events: none;
            }
        }

        .upload-tips{
            flex: 1;
            margin-left: 20px;
            display: inline-block;
           
            span{
                color: #ccc;
                font-size: 12px;
                display: block;
                line-height: 24px;
            }
        }

        &.multiple{
            display: block;

            .image-wrapper{
                float: left;
                margin: 0 20px 20px 0;

                &:last-child{
                    margin: 0;
                }
            }

            .add-wrapper{
                float: left;
                margin: 0 20px 20px 0;
            }

            .upload-tips{
                width: 100%;
                float: left;
                display: block;
                margin: 0;
            }
        }
    }
</style>

  

 
 
页面引用
import imageUpload from "@/components/image-upload";
  components: { imageUpload },
<image-upload
            v-model="goodslistPhoto"
            multiple
            :limit="20"
            folder="goodslistPhoto"
          />
 
posted @ 2021-11-18 15:00  小小小小小前端  阅读(81)  评论(0编辑  收藏  举报