图片上传
<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"
/>
不求大富大贵,但求一生平凡