有时候在项目中有多处需要图片大图展示的功能,这时候一个一个独立的写有些麻烦,维护起来也很难,那么这时候就需要写成一个统一的组件,维护性和复用性高。
功能简介:点击按钮或拖动上传图片,大图展示,缩略图选中,切换页面,比例选择(等比缩放,原始大小,变比缩放),具备自适应大小。
1.预览
2.如何实现
tip:本文采用vue3+element Plus
1.模版
<template><!--该组件为编辑图片弹窗组件:可上传删除查看图片-->
<div>
<el-dialog v-model="imgDialogShow" ref="uploadImg" top="5vh" style="max-width:800px" width="80%" draggable modal-class="bgcfff1" :title="$getText('添加')+'/'+$getText('修改图片')" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="true" @close="closeUpload">
<div class="insideBox">
<div class="btns">
<el-upload
v-model:file-list="fileList"
class="upload-demo"
ref="uploadFolder"
accept="image/*"
:show-file-list="false"
list-type="picture"
:http-request="uploadHttpRequest"
>
<el-button icon="DocumentAdd" type="primary" :id="addImgBtnId" size="small" plain class="upload-button" v-dk="{ dom: () => $refs.uploadImg.d_o, fn:()=>{uploadImg()}, key: 'a' }">{
{$getText('添加')}}</el-button>
</el-upload>
<el-button icon="Delete" type="danger" :id="delImgBtnId" size="small" plain @click="delImg" v-dk="{ dom: () => $refs.uploadImg.d_o, fn:()=>{delImg()}, key: 'd' }">{
{$getText('删除')}}</el-button>
</div>
<div class="flexBox flex1 allImgBox">
<div class="flex1">
<el-upload
v-model:file-list="fileList"
class="uploadBigImg"
ref="uploadFolder"
accept="image/*"
:show-file-list="false"
list-type="picture"
:http-request="uploadHttpRequest"
drag
@click.stop="()=>{}"
@keydown.enter.stop="imgEnter"
>
<!-- 覆盖点击事件 -->
<div class="upload-overlay" @click.stop >
<div class="imgBox" @click.stop="changeFocus">
<el-image class="bigImg" :src="currentImg.url" :class="imgClass" ref="bigImg"
:preview-src-list="[currentImg.url]" />
</div>
</div>
</el-upload>
<div class="imgInfoBox">
<div class="imgInfoInput">
<span class="imgInfoField">{
{$getText('产地')}}:</span>
<el-input v-model="currentImg.fa" size="small" placeholder="" disabled/>
</div>
<div class="imgInfoInput">
<span class="imgInfoField">{
{ $getText('零件说明') }}:</span>
<el-input v-model="currentImg.note" size="small" placeholder="" disabled/>
</div>
</div>
</div>
<div class="rightBox">
<el-select v-model="imgClass" @keyup.enter.stop="() => { }"
size="small"
ref="imgSelectClass"
class="imgSelectClass"
placeholder="">
<el-option v-for="item in imgClassOptions"
:key="item.label" :label="item.label" :value="item.value" />
</el-select>
<el-select v-model="imgSelect.faSelect" @keyup.enter.stop="() => { }"
size="small"
ref="faSelect"
placeholder="">
<el-option v-for="item in faOptionData"
:key="item.name" :label="item.name" :value="item.name" />
</el-select>
<span class="imgTotal">{
{ $getText('图片总数') }}:{
{imgList.length}}</span>
<el-button icon="ArrowUpBold" type="primary" size="small" plain @click="upPage" :disabled="this.currentPage==1">{
{$getText('上一页')}}</el-button>
<div class="flex1 smallImgBox" ref="smallImgBox">
<div v-for="item in pageImgList" :key="item" class="smallImgItem" :class="{'activeImg':item.id==currentImg.id}" @click="selectSmallImg(item)">
<el-image :src="item.url" >
</el-image>
</div>
</div>
<el-button icon="ArrowDownBold" type="primary" size="small" plain @click="downPage" :disabled="(currentPage*smallPageSize)>=imgList.length" >{
{$getText('下一页')}}</el-button>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
2.js方法
<script>
export default {
inheritAttrs:false,
name: 'UploadImg',
props:{
uploadImgShow:{
type:Boolean,
default:false,
},
//以下参数根据实际情况来,非必要
nno:{
type:String,
default:''
},
imgType:{
type:String,
default:''
},
fa:{
type:String,
default:''
},
addImgBtnId:{
type:String,
default:''
},
delImgBtnId:{
type:String,
default:''
},
},
emits:['uploadShow','getImgList'],
computed:{
smallPageSize(){
return parseInt(this.smallImgBoxHeight/75)-this.fract(this.smallImgBoxHeight/75)>0?parseInt(this.smallImgBoxHeight/75)-this.fract(this.smallImgBoxHeight/75):6;
},
pageImgList(){
return this.imgList.slice((this.currentPage-1)*this.smallPageSize,this.currentPage*this.smallPageSize)
},
},
data () {
return {
imgDialogShow:false,
imgurl: 'https://picsum.photos/seed/picsum/400/400',//底部图片
bigShowImg: ['https://picsum.photos/seed/picsum/400/400'],//大图展示
imgSelect:{
faSelect:'全部产地'
},
faOptionData:[
{name:'全部产地'},
{name:'当前产地'}
],
currentImg:{
id:0,
url:'https://picsum.photos/seed/picsum/400/400',
fa:'',
note:'',
},
fileList:[],
imgList:[],
currentPage:1,
imgBase64:'',
imgClassOptions:[
{label:'原始大小',value:'originalImg'},
{label:'等比缩放',value:'containImg'},
{label:'变比缩放',value:'fixImg'},
],
imgClass:'containImg',
smallImgBoxHeight: 0,
resizeListenerAdded: false, // 用于标记是否已经添加了窗口大小变化的事件监听器
}
},
watch: {
uploadImgShow(newVal) {
if (this.imgDialogShow !== newVal) {
this.imgDialogShow = newVal;
}
if(this.imgDialogShow){
this.$nextTick(()=>{
this.handleResize()
})
this.currentPage=1
this.getImgList()
this.addResizeListener();
}else{
this.removeResizeListener();
}
},
smallPageSize(newValue){
if (newValue) this.currentPage=Math.ceil((this.imgList.findIndex(img => img.id === this.currentImg.id) + 1) / this.smallPageSize);
if(this.currentPage<=0) this.currentPage=1;
}
},
mounted(){
this.currentImg.fa=this.$props.fa//传参自定义
},
methods:{
fract(num){//边缘计算
if(num - Math.trunc(num)>0.2)return 0;
else return 1;
},
handleResize() {
setTimeout(()=>{
if (document.getElementsByClassName('smallImgBox')) {
this.smallImgBoxHeight = document.getElementsByClassName('smallImgBox')[0].clientHeight;
}
},500)
},
addResizeListener() {
if (!this.resizeListenerAdded) {
window.addEventListener('resize', this.handleResize);
this.resizeListenerAdded = true;
}
},
removeResizeListener() {
if (this.resizeListenerAdded) {
window.removeEventListener('resize', this.handleResize);
this.resizeListenerAdded = false;
}
},
changeFocus(){
document.getElementsByClassName("uploadBigImg")[0].children[0].blur();
},
imgEnter(e){
console.log(e)
//阻止默认事件
e.preventDefault();
//阻止事件冒泡
e.stopPropagation();
return;
},
uploadHttpRequest(data){
this.toBase64(data.file)
},
suofang(base64,callback) {//图片压缩函数
//处理缩放,转格式
var _img = new Image();
_img.src = base64;
_img.onload = function () {
var quality=1;
var _canvas = document.createElement("canvas");
var w = this.width / 1.5;
var h = this.height / 1.5;
_canvas.setAttribute("width", w);
_canvas.setAttribute("height", h);
_canvas.getContext("2d").drawImage(this, 0, 0, w, h);
var base64 = _canvas.toDataURL("image/jpeg",quality);
while(base64.length>1024*1024*3){
quality-=0.05;
base64=_canvas.toDataURL("image/jpeg",quality);
}
base64=base64.substring(base64.indexOf (',')+1)
callback(base64);//将压缩后的图片传入最终回调函数中执行
}
},
toBase64(file){//转base64
let base64=''
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (e) => {
// 转换完成,获取Base64编码
this.imgBase64 = e.target.result;
if(this.imgBase64){
this.suofang(this.imgBase64,this.getImgList)
}
// 这里可以根据需要对base64进行处理,比如存储到Vuex、Data等
return base64
}
},
getImgList(base64=''){
console.log(base64?'上传':'获取')
//此处为获取或上传图片,获取到的数据赋值到this.imgList
},
selectSmallImg(data){
this.currentImg=data;
},
upPage(){
if(this.currentPage==1) return;
this.currentPage--;
},
downPage(){
if(this.currentPage*this.smallPageSize>=this.imgList.length) return;
this.currentPage++;
},
closeUpload(status){
this.$emit('uploadShow',false)
this.$emit('getImgList',this.imgList)
},
uploadImg(){
this.$refs.uploadFolder.$el.querySelector('.upload-button').click();
},
delImg(){
}
},
}
</script>
3.css样式
<style scoped>
.insideBox{
display: flex;
flex-direction: column;
height: 100%;
}
.btns {
padding: 0px 8px;
box-sizing: border-box;
display: flex;
height: 32px;
gap: 8px;
}
.imgBox{
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.flexBox{
display:flex;
}
.flex1{
flex:1;
}
.rightBox{
display: flex;
flex-direction: column;
padding: 0 10px;
width:100px;
max-width: 100px;
overflow: hidden;
}
.smallImgBox{
display: flex;
gap: 4px;
flex-direction: column;
height: 470px;
overflow: hidden;
padding: 4px 0px;
}
.smallImgItem{
width: 100%;
height: 75px;
min-height:75px;
overflow: hidden;
box-sizing: border-box;
border: 1px solid #b1b0b0;
display: flex;
align-items: center;
justify-content: center;
}
.imgInfoBox{
display: flex;
flex-direction: column;
gap: 6px;
padding-top: 10px;
}
.imgInfoField{
min-width:5rem;
white-space:nowrap;
}
.imgInfoInput{
display:flex;
white-space:nowrap;
}
.activeImg{
border: 2px solid #409EFF;
}
.imgTotal{
white-space: nowrap;
}
:deep(.el-image__placeholder) {
background: url('../../../public/image/loading.gif') no-repeat 50% 50% !important;
background-size: 25px !important;
}
:deep(.uploadBigImg .el-upload-dragger){
border:none;
padding:0px;
height:100%;
border-radius: 0px;
}
.upload-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1; /* 确保覆盖在内容之上 */
pointer-events: auto; /* 默认情况下,这个值可能是 none,但我们需要设置为 auto 来接收点击事件,然后通过 @click.stop 来阻止它冒泡 */
/* 可以添加透明背景或其他样式来避免影响用户体验 */
background-color: rgba(0, 0, 0, 0); /* 透明背景 */
}
.uploadBigImg{
width: 100%;
height:calc(100% - 64px);
border: 1px dashed lightgray;
}
:deep(.uploadBigImg .el-upload){
height:100%;
}
.originalImg{
position: relative;
width: 100%;
height: 100%;
}
:deep(.originalImg img){
position: absolute;
width:auto;
height:auto;
left:0px;
top:0px;
}
.containImg{
max-width: 100%;
max-height: 100%;
height: 100%;
display: flex;
align-items: center;
}
:deep(.containImg img){
object-fit: contain;
max-height: 100%;
height: auto !important;
max-width: 100%;
width: auto !important;
}
.fixImg{
width:100%;
height: 100%;
}
:deep(.fixImg img){
width:100%;
height: 100%;
}
:deep(.el-image-viewer__canvas img){
position: relative;
}
:deep(.smallImgItem .el-image img){
max-height:75px;
object-fit: contain;
}
.allImgBox{
height:calc(100% - 24px)
}
.imgSelectClass{
margin-bottom: 6px;
}
</style>
3.如何使用
1.在父组件中,引入子组件(在scrpit中使用import UploadImg from '子组件文件位置')
2.在模版标签(template)中使用(带:的为参数,可自定义,根据实际情况来)
<UploadImg v-model:uploadImgShow="uploadImgShow" :nno="''" :imgType="''" :fa="''" @uploadShow="closeUploadImgShow" @getImgList="getImgList"></UploadImg>
3.创建新变量uploadImgShow,默认值为false,控制弹窗显示
4.最后在父组件中添加方法:
getImgList(data){
console.log(data);//获取子组件返回的图片数据
},
closeUploadImgShow(status){
this.uploadImgShow=status
},
imgUpload() {//上传图片
console.log('上传')
this.uploadImgShow = true
},