<template>
|
<div v-if="visible" class="custom-image-viewer" @wheel.prevent="handleWheel">
|
<!-- 遮罩层 -->
|
<div class="el-image-viewer__mask" @click.self="handleMaskClick"></div>
|
|
<!-- 关闭 -->
|
<div class="el-image-viewer__btn el-image-viewer__close" @click="close">
|
<i class="el-icon-close"></i>
|
</div>
|
|
<!-- 工具栏 -->
|
<div class="viewer-toolbar">
|
<div class="toolbar-btn" title="放大" @click="zoomIn">
|
<i class="el-icon-zoom-in"></i>
|
</div>
|
<div class="toolbar-btn" title="缩小" @click="zoomOut">
|
<i class="el-icon-zoom-out"></i>
|
</div>
|
<div class="toolbar-btn" title="顺时针旋转" @click="rotateRight">
|
<i class="el-icon-refresh-right"></i>
|
</div>
|
<div class="toolbar-btn" title="逆时针旋转" @click="rotateLeft">
|
<i class="el-icon-refresh-left"></i>
|
</div>
|
<div class="toolbar-btn" title="还原" @click="resetTransform">
|
<i class="el-icon-s-home"></i>
|
</div>
|
<span class="toolbar-info">{{ currentIndex + 1 }} / {{ urlList.length }}</span>
|
</div>
|
|
<!-- 上一张 -->
|
<div
|
class="el-image-viewer__btn el-image-viewer__prev"
|
:class="{ 'is-disabled': isFirst }"
|
@click="goPrev"
|
>
|
<i class="el-icon-arrow-left"></i>
|
</div>
|
|
<!-- 下一张 -->
|
<div
|
class="el-image-viewer__btn el-image-viewer__next"
|
:class="{ 'is-disabled': isLast }"
|
@click="goNext"
|
>
|
<i class="el-icon-arrow-right"></i>
|
</div>
|
|
<!-- 图片展示 -->
|
<div
|
class="el-image-viewer__canvas"
|
@mousedown="handleMouseDown"
|
@mousemove="handleMouseMove"
|
@mouseup="handleMouseUp"
|
@mouseleave="handleMouseUp"
|
>
|
<img
|
v-if="currentUrl"
|
:src="currentUrl"
|
:key="currentIndex"
|
@error="handleError"
|
class="el-image-viewer__img"
|
:style="imgStyle"
|
draggable="false"
|
/>
|
</div>
|
|
<!-- 提示信息 -->
|
<div v-if="showTip" class="custom-viewer-tip">
|
{{ tipMessage }}
|
</div>
|
</div>
|
</template>
|
|
<script>
|
export default {
|
name: 'CustomImageViewer',
|
props: {
|
urlList: { type: Array, default: () => [] },
|
initialIndex: { type: Number, default: 0 },
|
visible: { type: Boolean, default: false }
|
},
|
data() {
|
return {
|
currentIndex: this.initialIndex,
|
showTip: false,
|
tipMessage: '',
|
scale: 1,
|
rotate: 0,
|
// 拖拽
|
dragging: false,
|
dragStart: { x: 0, y: 0 },
|
offsetX: 0,
|
offsetY: 0,
|
};
|
},
|
computed: {
|
currentUrl() {
|
return this.urlList[this.currentIndex];
|
},
|
isFirst() {
|
return this.currentIndex === 0;
|
},
|
isLast() {
|
return this.currentIndex === this.urlList.length - 1;
|
},
|
imgStyle() {
|
return {
|
transform: `translate(${this.offsetX}px, ${this.offsetY}px) scale(${this.scale}) rotate(${this.rotate}deg)`,
|
transition: this.dragging ? 'none' : 'transform 0.3s',
|
cursor: this.scale > 1 ? (this.dragging ? 'grabbing' : 'grab') : 'default',
|
};
|
},
|
},
|
watch: {
|
initialIndex(newVal) {
|
this.currentIndex = newVal;
|
this.resetTransform();
|
},
|
visible(newVal) {
|
if (!newVal) {
|
this.showTip = false;
|
this.removeKeyListener();
|
} else {
|
this.$nextTick(() => { this.addKeyListener(); });
|
}
|
},
|
},
|
methods: {
|
close() {
|
this.$emit('update:visible', false);
|
this.$emit('close');
|
},
|
handleMaskClick() { this.close(); },
|
|
// 切换图片
|
goPrev() {
|
if (this.isFirst) { this.showTipMessage('已是第一张'); return; }
|
this.currentIndex--;
|
this.resetTransform();
|
},
|
goNext() {
|
if (this.isLast) { this.showTipMessage('已是最后一张'); return; }
|
this.currentIndex++;
|
this.resetTransform();
|
},
|
|
// 缩放
|
zoomIn() {
|
this.scale = Math.min(this.scale + 0.25, 5);
|
if (this.scale <= 1) { this.offsetX = 0; this.offsetY = 0; }
|
},
|
zoomOut() {
|
this.scale = Math.max(this.scale - 0.25, 0.25);
|
if (this.scale <= 1) { this.offsetX = 0; this.offsetY = 0; }
|
},
|
handleWheel(e) {
|
if (e.deltaY < 0) this.zoomIn();
|
else this.zoomOut();
|
},
|
|
// 旋转
|
rotateRight() { this.rotate = (this.rotate + 90) % 360; },
|
rotateLeft() { this.rotate = (this.rotate - 90) % 360; },
|
|
// 还原
|
resetTransform() {
|
this.scale = 1;
|
this.rotate = 0;
|
this.offsetX = 0;
|
this.offsetY = 0;
|
},
|
|
// 拖拽(仅在放大时启用)
|
handleMouseDown(e) {
|
if (this.scale <= 1) return;
|
this.dragging = true;
|
this.dragStart = { x: e.clientX - this.offsetX, y: e.clientY - this.offsetY };
|
},
|
handleMouseMove(e) {
|
if (!this.dragging) return;
|
this.offsetX = e.clientX - this.dragStart.x;
|
this.offsetY = e.clientY - this.dragStart.y;
|
},
|
handleMouseUp() {
|
this.dragging = false;
|
},
|
|
showTipMessage(msg) {
|
this.tipMessage = msg;
|
this.showTip = true;
|
setTimeout(() => { this.hideTip(); }, 2000);
|
},
|
hideTip() { this.showTip = false; this.tipMessage = ''; },
|
handleError() { console.error('图片加载失败:', this.currentUrl); },
|
|
// 键盘
|
handleKeydown(e) {
|
switch(e.key) {
|
case 'ArrowLeft': e.preventDefault(); this.goPrev(); break;
|
case 'ArrowRight': e.preventDefault(); this.goNext(); break;
|
case 'Escape': e.preventDefault(); this.close(); break;
|
case '+': case '=': e.preventDefault(); this.zoomIn(); break;
|
case '-': e.preventDefault(); this.zoomOut(); break;
|
case '0': e.preventDefault(); this.resetTransform(); break;
|
}
|
},
|
addKeyListener() { document.addEventListener('keydown', this.handleKeydown); },
|
removeKeyListener() { document.removeEventListener('keydown', this.handleKeydown); },
|
},
|
mounted() { if (this.visible) this.addKeyListener(); },
|
beforeUnmount() { this.removeKeyListener(); },
|
};
|
</script>
|
|
<style scoped>
|
.custom-image-viewer {
|
position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 2000;
|
}
|
.el-image-viewer__mask {
|
position: absolute; width: 100%; height: 100%;
|
background-color: rgba(0, 0, 0, 0.5);
|
}
|
.el-image-viewer__btn {
|
position: absolute; z-index: 1; color: #fff;
|
background-color: rgba(0, 0, 0, 0.3); border-radius: 50%;
|
width: 40px; height: 40px; display: flex;
|
align-items: center; justify-content: center; cursor: pointer; user-select: none;
|
}
|
.el-image-viewer__btn.is-disabled { cursor: not-allowed; opacity: 0.5; }
|
.el-image-viewer__close { top: 20px; right: 20px; }
|
.el-image-viewer__prev { left: 20px; top: 50%; transform: translateY(-50%); }
|
.el-image-viewer__next { right: 20px; top: 50%; transform: translateY(-50%); }
|
|
.viewer-toolbar {
|
position: absolute; bottom: 30px; left: 50%; transform: translateX(-50%);
|
z-index: 1; display: flex; align-items: center; gap: 8px;
|
padding: 8px 16px; background: rgba(0, 0, 0, 0.6); border-radius: 24px;
|
}
|
.toolbar-btn {
|
width: 36px; height: 36px; border-radius: 50%; display: flex;
|
align-items: center; justify-content: center; cursor: pointer;
|
color: #fff; font-size: 18px; transition: background 0.2s;
|
}
|
.toolbar-btn:hover { background: rgba(255, 255, 255, 0.2); }
|
.toolbar-info { color: rgba(255, 255, 255, 0.7); font-size: 13px; margin-left: 8px; white-space: nowrap; }
|
|
.el-image-viewer__canvas {
|
display: flex; justify-content: center; align-items: center;
|
height: 100%; overflow: hidden;
|
}
|
.el-image-viewer__img {
|
max-width: 90%; max-height: 90%;
|
object-fit: contain; user-select: none; -webkit-user-drag: none;
|
}
|
.custom-viewer-tip {
|
position: absolute; bottom: 100px; left: 50%; transform: translateX(-50%);
|
background-color: rgba(0, 0, 0, 0.7); color: #fff;
|
padding: 10px 20px; border-radius: 4px; z-index: 2001;
|
}
|
</style>
|