<template>
|
<transition name="el-fade-in-linear el-fade-in">
|
<div class="img-preview-wrapper" v-show="visible">
|
<!-- 顶部信息栏 & 工具栏 -->
|
<div class="tip-wrapper" v-if="visible">
|
<i
|
class="fold-icon"
|
:class="
|
isShowMinImgList
|
? 'no-fold el-icon-d-arrow-left'
|
: 'fold el-icon-d-arrow-right'
|
"
|
:title="isShowMinImgList ? '折叠缩略图' : '展开缩略图'"
|
@click="isShowMinImgList = !isShowMinImgList"
|
></i>
|
<div class="name" :title="activeImageName">
|
{{ activeImageName }}
|
</div>
|
<div class="opera-btn-group">
|
<el-input-number
|
v-model="inputActiveIndex"
|
:min="1"
|
:max="imgList.length"
|
size="mini"
|
@change="handleChangeInputIndex"
|
></el-input-number>
|
<span class="split-line">/</span>{{ imgList.length }}
|
</div>
|
<div class="tool-wrapper">
|
<i
|
class="item el-icon-refresh-right"
|
title="向右旋转"
|
@click="handleRotateImg"
|
></i>
|
<a
|
class="item download-link"
|
target="_blank"
|
:href="activeDownloadLink"
|
:download="activeImageName"
|
>
|
<i class="el-icon-download" title="保存到本地"></i>
|
</a>
|
<el-tooltip effect="dark" placement="bottom">
|
<div slot="content" style="line-height: 1.5">
|
1. 点击图片以外的区域可退出预览<br />
|
2. 按 Escape 键可退出预览<br />
|
3. 按左、右方向键可切换为上一张、下一张图片<br />
|
4. 鼠标滚轮可放大、缩小图片<br />
|
5. 点击左上角
|
<i class="el-icon-d-arrow-left"></i> 图标可折叠、展开缩略图<br />
|
</div>
|
<div class="item text-wrapper">
|
<span class="text">操作提示</span>
|
<i class="el-icon-s-opportunity"></i>
|
</div>
|
</el-tooltip>
|
<i
|
class="item close-icon el-icon-close"
|
title="关闭预览"
|
@click="handleCloseImgPreview"
|
></i>
|
</div>
|
</div>
|
<!-- 左侧缩略图 -->
|
<ul class="min-img-list" ref="minImgListRef" v-show="isShowMinImgList">
|
<li
|
class="min-img-item"
|
v-for="(item, index) in imgList"
|
:key="index"
|
:class="{ active: activeIndex === index }"
|
:title="$file.getFileNameComplete(item)"
|
ref="minImgRef"
|
@click="activeIndex = index"
|
>
|
<img
|
class="min-img"
|
:src="$file.setFileImg(item)"
|
:alt="`${item.fileName} 缩略图`"
|
/>
|
</li>
|
</ul>
|
<!-- 右侧预览区域 -->
|
<div
|
class="img-wrapper"
|
:class="{ 'full-width': !isShowMinImgList }"
|
@mousewheel.prevent="handleMouseWheel()"
|
@click.self="handleCloseImgPreview"
|
>
|
<!-- 大图查看 -->
|
<img
|
class="img-large"
|
ref="imgLarge"
|
v-for="(item, index) in imgList"
|
:key="index"
|
:src="item.fileUrl"
|
v-show="index === activeIndex"
|
/>
|
<!-- 左右切换图标 -->
|
<i
|
class="pre-icon el-icon-arrow-left"
|
title="上一张"
|
v-show="activeIndex > 0"
|
@click.stop="handleChangeActiveImg(activeIndex - 1)"
|
></i>
|
<i
|
class="next-icon el-icon-arrow-right"
|
title="下一张"
|
v-show="activeIndex < imgList.length - 1"
|
@click.stop="handleChangeActiveImg(activeIndex + 1)"
|
></i>
|
<!-- 底部显示放大缩小比例 -->
|
<div class="zoom-bar">
|
<el-slider
|
v-model="imgZoom"
|
:min="imgZoomMin"
|
:max="imgZoomMax"
|
:format-tooltip="formatZoomSize"
|
@input="handleZoomImg"
|
></el-slider>
|
<div class="zoom-count">{{ imgZoom }}%</div>
|
</div>
|
</div>
|
</div>
|
</transition>
|
</template>
|
|
<script>
|
import store from '@/store/index.js'
|
|
export default {
|
name: 'ImgPreview',
|
data() {
|
return {
|
visible: false, // 图片预览遮罩层组件是否显示
|
inputActiveIndex: 1, // 对用户而言 显示的图片索引 从 1 开始 顶部栏输入框控制此值变化
|
rotate: 0, // 旋转角度
|
activeIndex: 0, // 当前图片索引 从 0 开始
|
imgZoom: 100, // 图片缩放比例
|
imgZoomMin: 1, // 图片缩放最小比例
|
imgZoomMax: 200, // 图片缩放最大比例
|
isShowMinImgList: true // 是否显示缩略图列表
|
}
|
},
|
computed: {
|
activeImage() {
|
return this.imgList[this.activeIndex]
|
},
|
activeImageName() {
|
return this.$file.getFileNameComplete(this.activeImage)
|
},
|
imageHeight() {
|
return this.activeImage.imageHeight
|
},
|
imageWidth() {
|
return this.activeImage.imageWidth
|
},
|
// 当前显示的图片下载链接
|
activeDownloadLink() {
|
return this.activeImage.downloadLink
|
},
|
// 屏幕宽度
|
screenWidth() {
|
return store.state.common.screenWidth
|
}
|
},
|
watch: {
|
// 监听 图片查看组件 显隐状态变化
|
visible(val) {
|
if (val) {
|
this.activeIndex = this.defaultIndex
|
document.addEventListener('keyup', this.handleAddKeyupEvent) // 添加键盘相关事件
|
// 获取用户是否显示缩略图的操作习惯设置
|
this.isShowMinImgList =
|
localStorage.getItem('qiwen_file_img_preview_show_min') === 'false'
|
? false
|
: true
|
this.$nextTick(() => {
|
this.handleCalculateImgZoom(this.activeIndex) // 确定图片缩放比例
|
this.handleScrollMinImg() // 将当前查看图片对应的缩略图滚动到视野中
|
})
|
} else {
|
// 移除键盘相关事件
|
document.removeEventListener('keyup', this.handleAddKeyupEvent)
|
}
|
},
|
// 监听 图片索引变化
|
activeIndex(newValue) {
|
this.rotate = 0
|
this.$nextTick(() => {
|
if (this.$refs.imgLarge[newValue].style.zoom) {
|
this.imgZoom = Number(
|
this.$refs.imgLarge[newValue].style.zoom.split('%')[0]
|
)
|
} else {
|
// 确定图片缩放比例
|
this.handleCalculateImgZoom(newValue)
|
}
|
})
|
},
|
// 监听 是否展示缩略图列表,将变化值保存在 localStorage 中
|
isShowMinImgList(val) {
|
localStorage.setItem('qiwen_file_img_preview_show_min', val)
|
}
|
},
|
methods: {
|
/**
|
* 顶部栏输入框绑定值被改变时触发
|
* @param {number} currentValue 当前值
|
*/
|
handleChangeInputIndex(currentValue) {
|
this.activeIndex = currentValue - 1
|
this.handleChangeActiveImg(currentValue - 1)
|
},
|
/**
|
* DOM 绑定 Esc 键、左方向键、右方向键的键盘按下事件
|
* @param {event} event 事件
|
*/
|
handleAddKeyupEvent(event) {
|
switch (event.key) {
|
case 'Escape': {
|
this.handleCloseImgPreview() // 关闭预览
|
break
|
}
|
case 'ArrowLeft': {
|
this.handleChangeActiveImg(this.activeIndex - 1) // 切换到上一张
|
break
|
}
|
case 'ArrowRight': {
|
this.handleChangeActiveImg(this.activeIndex + 1) // 切换到下一张
|
break
|
}
|
}
|
},
|
/**
|
* 计算图片缩放比例
|
*/
|
handleCalculateImgZoom(currentIndex) {
|
// 包裹元素
|
const wrapperDom = document.querySelector('.img-wrapper')
|
// 宽度原比例
|
const widthRate = Number(
|
((wrapperDom.clientWidth - 264) / this.imageWidth).toFixed(2)
|
)
|
// 高度原比例
|
const heightRate = Number(
|
((wrapperDom.clientHeight - 80) / this.imageHeight).toFixed(2)
|
)
|
let zoomRate = 1 // 缩放比例
|
if (widthRate >= 1 && heightRate >= 1) {
|
// 原图宽、高都小于/等于包裹元素
|
zoomRate = 1
|
} else if (widthRate >= 1 && heightRate < 1) {
|
// 原图宽小于/等于显示区域,高大于包裹元素
|
zoomRate = heightRate
|
} else if (widthRate < 1 && heightRate >= 1) {
|
// 原图宽大于显示区域,高小于/等于包裹元素
|
zoomRate = widthRate
|
} else if (widthRate < 1 && heightRate < 1) {
|
// 原图宽、高都大于包裹元素
|
zoomRate = Math.min(widthRate, heightRate)
|
}
|
this.imgZoom = Number(zoomRate.toFixed(2)) * 100 // 缩放百分比
|
this.$refs.imgLarge[currentIndex].style.zoom = `${this.imgZoom}%`
|
},
|
/**
|
* 关闭图片预览,恢复旋转角度
|
*/
|
handleCloseImgPreview() {
|
this.rotate = 0
|
this.$refs.imgLarge[
|
this.activeIndex
|
].style.transform = `rotate(${this.rotate}deg)`
|
this.visible = false
|
this.callback('cancel')
|
},
|
/**
|
* 格式化 tooltip message - 显示图片缩放比例
|
* @param {number} value 缩放数字
|
* @returns {string} 图片缩放比例
|
*/
|
formatZoomSize(value) {
|
return value + '%'
|
},
|
/**
|
* 数据改变时触发(使用鼠标拖曳时,活动过程实时触发)
|
* @param {number} value 缩放数字
|
*/
|
handleZoomImg(value) {
|
if (this.$refs.imgLarge) {
|
this.$refs.imgLarge[this.activeIndex].style.zoom = value + '%' // 实时设置图片缩放比例
|
}
|
},
|
/**
|
* 鼠标滚动事件
|
* @description 缩放图片
|
*/
|
handleMouseWheel() {
|
let zoom =
|
parseInt(this.$refs.imgLarge[this.activeIndex].style.zoom) || 100
|
zoom += event.wheelDelta / 12
|
if (zoom >= this.imgZoomMin && zoom < this.imgZoomMax) {
|
this.imgZoom = zoom
|
this.$refs.imgLarge[this.activeIndex].style.zoom = zoom + '%'
|
}
|
return false
|
},
|
/**
|
* 旋转图片
|
*/
|
handleRotateImg() {
|
this.rotate += 90
|
this.$refs.imgLarge[
|
this.activeIndex
|
].style.transform = `rotate(${this.rotate}deg)`
|
},
|
/**
|
* 改变当前查看的图片索引
|
*/
|
handleChangeActiveImg(index) {
|
if (index >= 0 && index < this.imgList.length) {
|
this.activeIndex = index
|
this.handleScrollMinImg()
|
}
|
},
|
/**
|
* 将缩略图中当前查看的图片缩略图滚动到视野中
|
*/
|
handleScrollMinImg() {
|
const parentEle = this.$refs.minImgListRef // 包裹缩略图的父元素
|
const activeEle = this.$refs.minImgRef[this.activeIndex] // 当前缩略图
|
// 当前元素距离可视区域顶部的距离
|
const currentEleTop = activeEle.offsetTop - activeEle.clientHeight
|
// 当前元素距离可视区域底部的距离
|
const currentEleBottom = parentEle.clientHeight - activeEle.offsetTop
|
// 如果当前查看的图片缩略图有一部分在可视区域之外(之上或之下)
|
if (
|
currentEleBottom < activeEle.clientHeight ||
|
currentEleTop < activeEle.clientHeight
|
) {
|
// 将父元素的滚动条向可视区域方向滚动两个图片的高度
|
parentEle.scrollTop = -currentEleBottom + activeEle.clientHeight * 2
|
}
|
}
|
}
|
}
|
</script>
|
|
<style lang="less" scoped>
|
@import '~@assets/file/less/varibles.less';
|
@import '~@assets/file/less/mixins.less';
|
|
.img-preview-wrapper {
|
position: fixed;
|
top: 0;
|
right: 0;
|
bottom: 0;
|
left: 0;
|
overflow: auto;
|
width: 100%;
|
height: 100%;
|
z-index: 30;
|
text-align: center;
|
display: flex;
|
align-items: center;
|
animation: imgPreviewAnimation 0.3s;
|
animation-iteration-count: 0.3;
|
animation-fill-mode: forwards;
|
|
@keyframes imgPreviewAnimation {
|
0% {
|
background: transparent;
|
}
|
|
100% {
|
background: rgba(0, 0, 0, 0.8);
|
}
|
}
|
|
.tip-wrapper {
|
position: fixed;
|
top: 0;
|
left: 0;
|
z-index: 30;
|
background: rgba(0, 0, 0, 0.5);
|
padding-right: 48px;
|
width: 100%;
|
height: 48px;
|
line-height: 48px;
|
color: #fff;
|
font-size: 16px;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
|
.fold-icon {
|
margin-right: 16px;
|
font-size: 24px;
|
cursor: pointer;
|
&:hover {
|
opacity: 0.6;
|
}
|
&.no-fold {
|
width: 120px;
|
}
|
&.fold {
|
width: 64px;
|
}
|
}
|
|
.name {
|
flex: 1;
|
padding-right: 16px;
|
text-align: left;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
|
.opera-btn-group {
|
width: 100px;
|
display: flex;
|
|
/deep/ .el-input-number {
|
width: 40px;
|
|
.el-input-number__decrease, .el-input-number__increase {
|
display: none;
|
}
|
|
.el-input__inner {
|
margin-top: 14px;
|
background: rgba(0, 0, 0, 0.5);
|
height: 20px;
|
line-height: 20px;
|
padding: 0;
|
font-size: 16px;
|
color: #fff;
|
}
|
}
|
|
.split-line {
|
margin: 0 8px;
|
}
|
}
|
|
.tool-wrapper {
|
flex: 1;
|
display: flex;
|
justify-content: flex-end;
|
align-items: center;
|
|
.item {
|
margin-left: 16px;
|
cursor: pointer;
|
|
&:hover {
|
opacity: 0.7;
|
}
|
}
|
|
.el-icon-refresh-right,
|
.close-icon,
|
.download-link {
|
font-size: 18px;
|
}
|
|
.download-link {
|
color: inherit;
|
}
|
|
.text-wrapper {
|
margin-left: 32px;
|
|
.text {
|
margin-right: 8px;
|
}
|
}
|
}
|
}
|
.min-img-list {
|
position: fixed;
|
top: 48px;
|
left: 0;
|
background: rgba(0, 0, 0, 0.5);
|
height: calc(100vh - 48px);
|
padding: 8px 16px;
|
overflow-y: auto;
|
list-style: none;
|
.setScrollbar(8px, transparent, #C0C4CC);
|
|
.min-img-item {
|
position: relative;
|
margin-bottom: 4px;
|
width: 80px;
|
height: 80px;
|
cursor: pointer;
|
.min-img {
|
width: 100%;
|
height: 100%;
|
}
|
&:last-of-type {
|
margin-bottom: 0;
|
}
|
&:not(.active)::after {
|
content: '';
|
position: absolute;
|
top: 0;
|
left: 0;
|
display: block;
|
width: 80px;
|
height: 80px;
|
background: #000;
|
opacity: 0.4;
|
}
|
&:hover {
|
&:not(.active)::after {
|
opacity: 0.2;
|
}
|
}
|
}
|
}
|
.img-wrapper {
|
position: fixed;
|
top: 48px;
|
right: 0;
|
left: 120px;
|
overflow: auto;
|
height: calc(100vh - 48px);
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
&.full-width {
|
left: 0;
|
}
|
.img-large {
|
margin: 0 auto;
|
transition: transform 0.5s;
|
}
|
|
.pre-icon, .next-icon {
|
font-size: 60px;
|
color: #fff;
|
position: absolute;
|
top: 50%;
|
cursor: pointer;
|
z-index: 31;
|
|
&:hover {
|
opacity: 0.7;
|
}
|
}
|
|
.pre-icon {
|
left: 64px;
|
}
|
|
.next-icon {
|
right: 64px;
|
}
|
|
.zoom-bar {
|
position: absolute;
|
right: 0;
|
bottom: 20px;
|
left: 0;
|
margin: 0 auto;
|
width: 600px;
|
display: flex;
|
|
/deep/ .el-slider {
|
flex: 1;
|
|
.el-slider__bar {
|
background: @PrimaryText;
|
}
|
|
.el-slider__button {
|
border-color: @PrimaryText;
|
}
|
}
|
|
.zoom-count {
|
width: 60px;
|
height: 38px;
|
line-height: 38px;
|
text-align: right;
|
color: #fff;
|
}
|
}
|
}
|
|
}
|
</style>
|