选择图片预览如下:
组件代码
<template> <div> <!-- 管理图片按钮 --> <el-button type="primary" @click="dialogVisible = true">管理图片</el-button> <!-- 弹窗 --> <el-dialog title="管理商品图片" :visible.sync="dialogVisible" width="80%" @close="handleClose" > <!-- 图片上传组件 --> <image-upload :file-size="maxFileSize" :is-multiple="true" :limit="maxImages" :value="form.goods_images" :file-type="allowedFileTypes" @input="afterUpload" class="upload-section" ></image-upload> <!-- 预览已上传的图片 --> <div v-if="form.goods_images.length" class="preview-section"> <el-row :gutter="18"> <el-col :span="3" v-for="(image, index) in form.goods_images" :key="index" > <div class="preview-item"> <img :src="image" alt="商品图片预览" class="preview-image"/> <el-button type="danger" icon="el-icon-delete" circle @click="removeImage(index)"></el-button> </div> </el-col> </el-row> </div> <!-- 提示信息 --> <span class="el-form-item__info">建议图片尺寸:400*400</span> <!-- 操作按钮 --> <span slot="footer" class="dialog-footer"> <el-button @click="dialogVisible = false">取 消</el-button> <el-button type="primary" @click="confirmChanges">确 定</el-button> </span> </el-dialog> </div> </template> <script> import ImageUpload from '@/components/UploadFile/image.vue' export default { components: { ImageUpload }, props: { value: { type: Array, default: () => [] }, maxImages: { type: Number, default: 5 }, maxFileSize: { type: Number, default: 1 }, allowedFileTypes: { type: Array, default: () => ['png', 'jpg', 'jpeg'] } }, data() { return { dialogVisible: false, form: { goods_images: this.value.slice() // 使用slice创建副本,避免直接修改props } } }, watch: { value(newValue) { this.form.goods_images = newValue.slice() // 确保数据同步 } }, methods: { afterUpload(list) { if (!list || !Array.isArray(list)) return // 清空当前图片数组并添加新上传的图片 this.form.goods_images = list.map(file => file.url) }, removeImage(index) { this.form.goods_images.splice(index, 1) }, confirmChanges() { this.$emit('input', this.form.goods_images) // 同步到父组件 this.dialogVisible = false }, handleClose() { // 取消时可以选择是否保留更改或恢复原状 // 这里简单处理为不保存更改 this.form.goods_images = this.value.slice() // 恢复原始状态 } } } </script> <style scoped> .upload-section { margin-bottom: 10px; } .preview-section { margin-bottom: 20px; } .preview-item { position: relative; text-align: center; } .preview-image { max-width: 100%; height: auto; border: 1px solid #ccc; margin-bottom: 5px; } .el-button--danger { position: absolute; top: -10px; right: -10px; } .el-form-item__info { font-size: 12px; color: #999; } </style>
上传组件:
<template> <!-- 用例: <image-upload :file-size="1" :is-multiple="false" :limit="1" :value="form.mpic" :file-type="['png', 'jpg', 'jpeg','gif']" @input="afterUpload" ></image-upload> afterUpload(list){ if (!list) return; let file = list[0]; if (file){ this.form.mpic = file.url } } --> <div class="component-upload-image"> <el-upload :action="uploadImgUrl" list-type="picture-card" :on-success="handleUploadSuccess" :before-upload="handleBeforeUpload" :limit="limit" :on-error="handleUploadError" :on-exceed="handleExceed" name="file" :on-remove="handleRemove" :show-file-list="true" :headers="headers" :file-list="fileList" :on-preview="handlePictureCardPreview" :on-progress="handleOnProgress" :class="{ hide: this.fileList.length >= this.limit }" :multiple="isMultiple" > <i class="el-icon-plus"></i> <p class="photo-text">点击上传</p> </el-upload> <!-- 上传提示 --> <div class="el-upload__tip" slot="tip" v-if="showTip"> 请上传 <template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template> <template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b> </template> 的文件 </div> <!-- 预览 --> <el-dialog :visible.sync="dialogVisible" width="800" append-to-body custom-class="dialog-preview"> <template #title> <div class="header"> <span>预览</span> </div> </template> <img :src="dialogImageUrl" /> </el-dialog> </div> </template> <script> import { getToken } from '@/utils/auth' import { cloneDeep } from 'lodash' export default { name: 'ImageUpload', props: { value: [String, Object, Array], // 图片数量限制 limit: { type: Number, default: 5, }, // 大小限制(MB) fileSize: { type: Number, default: 5, }, // 文件类型, 例如['png', 'jpg', 'jpeg'] fileType: { type: Array, default: () => ['png', 'jpg', 'jpeg'], }, // 是否显示提示 isShowTip: { type: Boolean, default: true, }, // 是否多选 isMultiple: { type: Boolean, default: true, }, // 组件下标,如果一个动态表单渲染多个图片组件时使用 index: { type: Number, default: 0 } }, data() { return { dialogImageUrl: '', dialogVisible: false, hideUpload: false, baseUrl: '', uploadImgUrl: process.env.VUE_APP_BASE_API + '/upload/image', // 上传的图片服务器地址 headers: { Authorization: 'Bearer ' + getToken(), }, fileList: [], uploadList: [], number: 0, isUploading: false, } }, watch: { value: { handler(val) { if (val) { // 首先将值转为数组 const list = Array.isArray(val) ? val : this.value.split(',') // 然后将数组转为对象数组 this.fileList = list.map((item) => { if (typeof item === 'string') { // console.log(item,this.baseUrl) if (item.indexOf(this.baseUrl) === -1) { item = { name: this.baseUrl + item, url: this.baseUrl + item } } else { item = { name: item, url: item } } } return item }) } else { this.fileList = [] return [] } }, deep: true, immediate: true, }, }, computed: { // 是否显示提示 showTip() { return this.isShowTip && (this.fileType || this.fileSize) }, }, methods: { // 删除图片 handleRemove(file, fileList) { const findex = this.fileList.map((f) => f.name).indexOf(file.name) if (findex > -1) { this.fileList.splice(findex, 1) this.$emit('input', cloneDeep(this.fileList)) } }, // 上传成功回调 handleUploadSuccess(res) { res = res.data; this.isUploading = false this.uploadList.push({name: res.name, url: res.full_url ? res.full_url : this.baseUrl + res.name}) if (this.uploadList.length === this.number) { this.fileList = this.fileList.concat(this.uploadList) this.uploadList = [] this.number = 0 this.$emit('input', cloneDeep(this.fileList), this.index) } }, // 上传前文件校验 handleBeforeUpload(file) { let isImg = false if (this.fileType.length) { let fileExtension = '' if (file.name.lastIndexOf('.') > -1) { fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1) } isImg = this.fileType.some((type) => { if (file.type.indexOf(type) > -1) return true if (fileExtension && fileExtension.indexOf(type) > -1) return true return false }) } else { isImg = file.type.indexOf('image') > -1 } if (!isImg) { this.$message.error(`文件格式不正确, 请上传${this.fileType.join('/')}图片格式文件!`) return false } if (this.fileSize) { const isLt = file.size / 1024 / 1024 < this.fileSize if (!isLt) { this.$message.error(`上传头像图片大小不能超过 ${this.fileSize} MB!`) return false } } this.number++ }, // 文件个数超出 handleExceed() { this.$message.error(`上传文件数量不能超过 ${this.limit} 个!`) }, // 上传失败 handleUploadError() { this.$message({ type: 'error', message: '上传失败', }) this.isUploading = false }, // 预览 handlePictureCardPreview(file) { this.dialogImageUrl = file.url this.dialogVisible = true }, // 对象转成指定字符串分隔 listToString(list, separator) { let strs = '' separator = separator || ',' for (let i in list) { strs += list[i].url.replace(this.baseUrl, '') + separator } return strs != '' ? strs.substr(0, strs.length - 1) : '' }, handleOnProgress(event, file, fileList) { this.isUploading = true }, }, } </script> <style scoped lang="scss"> ::v-deep .el-upload--picture-card { border-radius: 2px; } // .el-upload--picture-card 控制加号部分 ::v-deep.hide .el-upload--picture-card { display: none; } // 去掉动画效果 ::v-deep .el-list-enter-active, ::v-deep .el-list-leave-active { transition: all 0s; } ::v-deep .el-list-enter, .el-list-leave-active { opacity: 0; transform: translateY(0); } ::v-deep .el-upload-list--picture-card .el-upload-list__item-thumbnail { object-fit: contain; } // 角标颜色 ::v-deep .el-upload-list--picture-card .el-upload-list__item-status-label { background-color: #18a8dc; } // 已上传图片 ::v-deep .el-upload-list { line-height: 1; .el-upload-list__item { margin: 0 16px 16px 0; width: 104px; height: 104px; border: 1px solid #e5eaec; border-radius: 2px; .el-progress { width: 102px !important; .el-progress-circle { width: 102px !important; height: 102px !important; } } } } // 上传按钮 ::v-deep .el-upload { position: relative; width: 104px; height: 104px; line-height: 1; background: #f7f8fb; .el-icon-plus { margin-top: 29px; margin-bottom: 8px; font-size: 16px; } .photo-text { margin: 0; font-weight: 400; font-size: 14px; line-height: 22px; text-align: center; color: #adb1b3; } } // 预览 ::v-deep .dialog-preview { overflow: hidden; .el-dialog__header { position: relative; padding: 11px 24px !important; z-index: 2; background: #f8faff; border-radius: 4px 4px 0 0; text-align: left; .header { display: flex; align-items: center; font-size: 14px; height: 22px; line-height: 22px; color: #00244d; font-weight: bold; } .el-dialog__headerbtn { top: 15px; right: 24px; } } .el-dialog__body { padding: 24px; overflow-y: auto; img { display: block; max-width: 100%; margin: 0 auto; } } } </style>
实操:
<template> <div> <!-- 显示图片区域 --> <div v-if="productImages.length" class="display-images"> <el-row :gutter="18"> <el-col :span="3" v-for="(image, index) in productImages" :key="index" > <div class="preview-item"> <img :src="image" alt="商品图片预览" class="preview-image"/> </div> </el-col> </el-row> </div> <!-- 商品图片管理器 --> <GoodsImageManager v-model="productImages" :max-images="6" :max-file-size="2" :allowed-file-types="['png', 'jpg']" /> </div> </template> <script> import GoodsImageManager from './components/GoodsImageManager.vue'; export default { components: { GoodsImageManager, }, data() { return { productImages: [ 'http://example.com/image1.jpg', 'http://example.com/image2.jpg', ], }; }, }; </script> <style scoped> .display-images { margin-bottom: 20px; } .preview-item { text-align: center; } .preview-image { max-width: 100%; height: auto; border: 1px solid #ccc; margin-bottom: 5px; } </style>