<template>
<div class="sImgEditorBox">
<div class="top" ref="viewDomP">
<a-spin :spinning="isShowLoading" class="fullBox">
<div ref="imageContainer"></div>
<canvas id="preview_canvas" class="preview_canvas" :style="canvasStyle" ref="viewDom"></canvas>
</a-spin>
</div>
<div class="bottom">
<div class="actionBox">
<span class="tip">{{isXsCpd?'提示:单击图片放大!':'提示:单击图片放大后,进行遮挡操作!'}}</span>
<div class="actionBox1" v-show="status!='WAIT_MAKE_DOCUMENT'&&status!='LEADER_APPROVED'">
<a
v-for="item in action"
:key="item.action"
:disabled="(!active && item.action !== 'upload') || item.disabled || isShowLoading"
:class="`btn ${
((!active && item.action !== 'upload') || item.disabled) &&
'disabledBox'
}`"
@click="onClickAction(item)"
>
<a-tooltip placement="bottom">
<template slot="title">
<span>{{ item.label }}</span>
</template>
<a-icon :type="item.icon" />
</a-tooltip>
</a>
</div>
</div>
<div class="viewList flex">
<a-tabs :activeKey="active" @change="onChangeTab">
<!-- <a-tab-pane key="upload">
<span slot="tab">
<div class="btnUpload" @click="onUpload">
<a-icon type="plus-circle" />
<input type="file" class="hideDom" ref="upload" />
</div>
</span>
</a-tab-pane> -->
<a-tab-pane v-for="item in imgs" :key="item.fileId">
<span slot="tab">
<img :src="item.src" class="viewListImg" />
</span>
</a-tab-pane>
</a-tabs>
</div>
</div>
<!-- <a-modal
v-model="isFullscreen"
:footer="null"
:closable="false"
:mask="false"
wrapClassName="transparent-modal"
width="100%"
style="top: 0; height: 100vh"
:dialog-style="{ top: '0px' }"
> -->
<div class="fullscreen-container" v-show="isFullscreen">
<!-- 关闭按钮,右上角 -->
<a-button class="close-fullscreen-btn" type="danger" icon="close" @click="toggleFullScreen" />
<a-carousel
ref="carousel"
:dots="false"
:arrows="true"
v-model="carouselIndex"
class="image-carousel"
@change="handleCarouselChange"
>
<!-- 上一张箭头(左侧) -->
<div slot="prevArrow" slot-scope="props" class="custom-slick-arrow" style="left: 10px">
<a-icon type="left-circle" />
</div>
<div v-for="(item, index) in imgs" :key="item.fileId" class="image-wrapper">
<img :src="item.src" />
</div>
<!-- 下一张箭头(右侧) -->
<div slot="nextArrow" slot-scope="props" class="custom-slick-arrow" style="right: 10px">
<a-icon type="right-circle" />
</div>
</a-carousel>
</div>
<!-- </a-modal> -->
</div>
</template>
<script>
import ImagePreviewModal from './ImagePreviewModal.vue'
import _ from "lodash";
export default {
components: {
ImagePreviewModal
},
props: {
dataSource: {
type: Array,
default: () => [],
},
disabled: {
type: Boolean,
default: false,
},
isXsCpd: { // 是否线上呈批单
type: Boolean,
default: false
}
},
data() {
this.onClickAction = _.debounce(this.onClickAction, 300);
return {
isShowLoading: false /* 是否显示loading */,
canvas: null /* canvas对象 */,
active: "" /* 当前编辑图片key */,
imgs: [
/* 图片list */
// {
// id: "1",
// src: "/files/1.png",
// },
// {
// id: "2",
// src: "/files/2.png",
// },
],
currentImageUrl: null,
currentImageName:null,
status: '', // 状态
Yongyin:false,
isShowLoading: false /* 是否显示loading */,
rotation: 0, // 当前旋转角度(0、90、180、270)
cachedImage: null, // 缓存的图片对象
isDraw: false /* 是否开始绘画 */,
custAction: "" /* cover画正方形; eraser橡皮擦 */,
canvas: null /* canvas对象 */,
ctx: null /* 2d画布对象 */,
startX: null /* 绘画开始X */,
startY: null /* 绘画开始Y */,
cache: [] /* 缓存步骤数据 */,
cacheNum: 10 /* 缓存步骤数量 */,
cacheDoubleBack: false /* 添加完缓存之后点击返回要返回两次 */,
isFromCsdsh: false, //是否从措施审核待审核界面进入,是的话图片的所有按钮放开权限
/**拖拽需要的参数 */
dragging: false, // 是否正在拖拽
startX: 0, // 拖拽开始X坐标
startY: 0, // 拖拽开始Y坐标
translateX: 0, // X轴偏移量
translateY: 0, // Y轴偏移量
canvasPosition: { x: 0, y: 0 }, // 用于存储canvas的位置,用于拖拽时计算偏移量
/**缩放需要的参数 */
scale: 1, // 当前缩放比例
minScale: 0.1, // 最小缩放比例
maxScale: 4, // 最大缩放比例
originX: 0, // 缩放原点X
originY: 0, // 缩放原点Y
/**全屏参数 */
isFullscreen: false, // 是否全屏显示
carouselIndex: 0,
};
},
computed: {
action() {
return [
/* 当前视图的操作 */
{
label: "全屏",
action: "fullscreen",
icon: "fullscreen",
disabled: this.disabled,
},
{
label: "上一个",
action: "prev",
icon: "left",
disabled: this.disabled,
},
{
label: "下一个",
action: "next",
icon: "right",
disabled: this.disabled,
},
{
label: "放大",
action: "zoomIn",
icon: "zoom-in",
disabled: this.disabled,
},
{
label: "缩小",
action: "zoomOut",
icon: "zoom-out",
disabled: this.disabled,
},
{
label: "逆时针旋转",
action: "rotateL",
icon: "undo",
disabled: this.disabled,
},
{
label: "顺时针旋转",
action: "rotateR",
icon: "redo",
disabled: this.disabled,
},
{
label: "撤回",
action: "back",
icon: "left",
disabled: this.disabled,
},
{
label: "重置",
action: "reset",
icon: "retweet",
disabled: this.disabled,
},
{
label: "下载",
action: "download",
icon: "download",
disabled: this.disabled,
},
{
label: "保存",
action: "save",
icon: "save",
disabled: this.disabled,
},
{
label: "遮挡",
action: "cover",
icon: "copy",
disabled: this.disabled,
},
{
label: "删除",
action: "delete",
icon: "delete",
disabled: this.disabled,
},
{
label: "上传",
action: "upload",
icon: "upload",
disabled: this.disabled,
},
];
},
/** 计算canvas的样式 */
canvasStyle() {
return {
transform: `translate(${this.canvasPosition.x}px, ${this.canvasPosition.y}px)`,
};
},
},
watch: {
dataSource: {
handler(val) {
this.imgs = val || [];
let hasActive = _.find(this.imgs, { fileId: this.active });
if (hasActive) {
this.resetView(this.getItem(this.active)?.src, true, true);
return;
}
if (this.imgs.length > 0) {
this.onChangeTab(this.imgs[0]?.fileId);
} else {
this.active = null;
}
},
deep: true, // 深度监听
immediate: true, // 立即执行
},
},
created() {
this.status = this.$utils.getSearchKeys('status')
this.resetView(this.currentImageUrl);
},
methods: {
handleImage() {
this.imgShow = true;
// this.currentImageUrl = this.getItem(this.active).src;
// this.currentImageName = this.getItem(this.active).downloadFileName;
},
onUpload() {
/* 点击上传摁扭 */
// let dom = this.$refs.upload;
// dom.click();
this.$emit("onUpload");
},
onChangeTab(e) {
/* 切换图片 */
if (e === this.active) return;
this.cache = []
this.active = e;
this.currentImageUrl = this.getItem(this.active).src;
this.currentImageName = this.getItem(this.active).downloadFileName;
/* 销毁对象 */
this.resetData();
this.resetView(this.getItem(this.active)?.src, true, true);
},
onClickAction(item) {
if (this.isShowLoading) return;
/* 点击当前视图操作 */
const { action } = item;
switch (action) {
case "fullscreen":
this.toggleFullScreen();
break;
case "prev":
this.prev();
break;
case "next":
this.next();
break;
case "zoomIn":
this.zoomIn();
break;
case "zoomOut":
this.zoomOut();
break;
case "rotateL":
this.rotateImage(-90);
break;
case "rotateR":
this.rotateImage(90);
break;
case "back":
this.onBackFromCache();
break;
case "reset":
this.onReset();
break;
case "download":
this.onSave("download");
break;
case "save":
this.onSave();
break;
case "cover":
this.onClickImgAction(item);
break;
case "upload":
this.$emit("onUpload");
break;
case "delete":
this.onDelete(item);
break;
}
},
onDelete() {
this.$emit("onDelete", this.getItem(this.active));
},
onReset() {
this.resetData();
this.$emit("onReset", this.getItem(this.active));
},
onSave(type) {
/* 保存、下载 */
/* 图片地址需允许跨域或者在同一域名下 */
var canvas = document.querySelector("#preview_canvas");
var data = canvas.toDataURL("image/jpeg");
if (type === "download") {
if(window.navigator.msSaveOrOpenBlob){
var bstr = atob(data.split(',')[1]);
var n = bstr.length;
var u8arr = new Uint8Array(n);
while(n--) {
u8arr[n] = bstr.charCodeAt(n);
}
var blob = new Blob([u8arr]);
window.navigator.msSaveOrOpenBlob(blob,this.currentImageName)
}else {
let a = document.createElement("a");
a.download = this.currentImageName;
a.href = data;
//审批材料下载方法
a.dispatchEvent(new MouseEvent("click"));
}
}
// this.cache = []
this.resetData()
this.$emit("onSave", { data, ...this.getItem(this.active) });
},
getItem(fileId) {
let item = _.find(this.imgs, { fileId });
return item;
},
// 获取合适的比例(要展示完全)
getImageRightProportion (imageWidth, imageHeight) {
const viewDomP = this.$refs.viewDomP;
const padding = 16;
const containerWidth = viewDomP.clientWidth - padding;
const containerHeight = viewDomP.clientHeight - padding;
const widthProportion = containerWidth / imageWidth;
const heightProportion = containerHeight / imageHeight;
const proportion = Math.min(widthProportion, heightProportion)
return Math.floor(proportion * 100) / 100;
},
// resetView(path) {
// console.log('=============================path', path);
// //创建图片
// let image = new Image();
// //设置图片地址
// image.src = path + `?uuid=${this.$utils.getId()}`;
// this.isShowLoading = true;
// image.onload = (e) => {
// const {width, height} = image;
// const proportion = this.getImageRightProportion(width, height);
// image.width = width * proportion;
// image.height = height * proportion;
// this.$refs.imageContainer.childNodes.forEach((node) => {
// this.$refs.imageContainer.removeChild(node);
// })
// this.$refs.imageContainer.appendChild(image)
// this.isShowLoading = false;
// };
// },
onClickImgAction(item) {
if (this.isShowLoading) return;
/* 点击图片操作 */
const { action } = item;
if (this.custAction === action) {
this.custAction = '';
} else {
this.custAction = action;
}
},
resetView(path) {
/* 重新绘制图片 */
let canvas, ctx;
if (!this.ctx) {
canvas = this.$refs.viewDom;
ctx = canvas.getContext("2d");
// 设置 willReadFrequently 属性
if (ctx.willReadFrequently) {
ctx.willReadFrequently = true;
}
this.ctx = ctx;
this.canvas = canvas;
this.initCanvas();
} else {
ctx = this.ctx;
canvas = this.canvas;
}
//创建图片
let image = new Image();
//设置图片地址
image.src = path + `?uuid=${this.$utils.getId()}`;
this.isShowLoading = true;
// 必须要在onLoad之后再进行绘制图片,否则不会渲染
image.onload = (e) => {
this.cachedImage = image; // 缓存图片对象
// 绘制图像(考虑旋转角度)
this.redrawImage();
this.isShowLoading = false;
};
},
initCanvas() {
/* 给canvas注入事件 */
//鼠标摁下时
this.canvas.onmousedown = (event) => {
const pos = this.getActualPosition(event);
if (this.custAction) {
// 绘图处理代码
this.isDraw = true;
if (this.custAction === "eraser") {
this.ctx.lineWidth = "10";
this.ctx.strokeStyle = "#fff";
}
if (this.custAction === "cover") {
this.ctx.lineWidth = 1;
this.ctx.fillStyle = "#ddd";
}
this.ctx.beginPath();
this.ctx.moveTo(pos.x, pos.y);
this.startX = pos.x;
this.startY = pos.y;
return;
}
this.startDrag(event); // 拖拽处理代码
};
//鼠标开始移动时绘图
this.canvas.onmousemove = (event) => {
const pos = this.getActualPosition(event);
// 如果正在进行绘图操作,跳过拖拽
if (this.custAction && this.isDraw) {
if (this.custAction === "eraser") {
this.ctx.lineTo(pos.x, pos.y);
}
if (this.custAction === "cover") {
this.ctx.fillRect(
this.startX,
this.startY,
pos.x - this.startX,
pos.y - this.startY
);
this.ctx.closePath();
}
this.ctx.stroke();
return;
}
this.doDrag(event);
};
//鼠标松开时停止绘图
this.canvas.onmouseup = () => {
if (this.custAction) {
this.isDraw = false;
//将此时绘制的信息存储到cache数组中
this.addCache();
}
this.endDrag();
};
//鼠标移出this.canvas后停止绘图
this.canvas.onmouseout = () => {
if (this.custAction) {
this.isDraw = false;
}
this.endDrag();
};
// 设置初始光标样式
// this.canvas.style.cursor = 'grab';
// 添加滚轮缩放支持
// this.canvas.addEventListener('wheel', this.handleWheel, { passive: false });
},
addCache() {
/* 添加缓存 */
let imgCache = this.ctx?.getImageData(
0,
0,
this.canvas.width,
this.canvas.height
);
this.cache.push(imgCache);
if (this.cache.length > this.cacheNum) {
this.cache.shift();
}
this.cacheDoubleBack = true;
},
/* 获取最新缓存 */
getCache() {
let imgCache;
if (this.cache.length === 1) {
imgCache = this.cache[0];
} else {
imgCache = this.cache.pop();
}
return imgCache;
},
onBackFromCache() {
/* 返回操作 */
let imgCache = this.getCache();
if (this.cacheDoubleBack) {
/* 如果需要返回两次,再拿一遍 */
imgCache = this.getCache();
this.cacheDoubleBack = false;
}
if (!imgCache) return;
this.ctx.putImageData(imgCache, 0, 0);
},
// 旋转图片方法
rotateImage(degrees) {
if (this.isShowLoading || !this.cachedImage) return;
// 重置缩放
this.scale = 1;
this.originX = 0;
this.originY = 0;
// 重置位置偏移量
this.translateX = 0;
this.translateY = 0;
this.lastPosition = { x: 0, y: 0 }; // 保存当前旋转角度
// 更新旋转角度(确保在0-360度范围内)
this.rotation = (this.rotation + degrees + 360) % 360;
// 清空之前的遮挡操作
this.cache = [];
this.custAction = "";
// 重新绘制画布
this.redrawImage();
},
// 重新绘制图像(使用当前旋转角度)
redrawImage() {
const ctx = this.ctx;
const canvas = this.canvas;
const image = this.cachedImage;
// const imgWidth = image.width;
// const imgHeight = image.height;
const {width, height} = image;
const proportion = this.getImageRightProportion(width, height);
const imgWidth = width * proportion;
const imgHeight = height * proportion;
// 根据旋转角度确定画布大小
if (this.rotation % 180 === 0) {
canvas.width = imgWidth;
canvas.height = imgHeight;
} else {
canvas.width = imgHeight;
canvas.height = imgWidth;
}
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 保存当前画布状态
ctx.save();
// 应用平移变换
ctx.translate(this.translateX, this.translateY);
// 应用缩放(以画布中心为缩放中心)
ctx.translate(this.originX, this.originY);
ctx.scale(this.scale, this.scale);
ctx.translate(-this.originX, -this.originY);
// 计算中心点并平移
ctx.translate(canvas.width / 2, canvas.height / 2);
// 执行旋转(转为弧度)
ctx.rotate((this.rotation * Math.PI) / 180);
// 绘制图片(将图片原点移到中心)
ctx.drawImage(image, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight);
// 恢复画布状态
ctx.restore();
// 设置canvas的显示尺寸(CSS尺寸)为实际像素尺寸乘以缩放比例
canvas.style.width = `${canvas.width * this.scale}px`;
canvas.style.height = `${canvas.height * this.scale}px`;
// 添加初始缓存(用于撤回操作)
this.addCache();
},
resetData() {
this.ctx?.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.rotation = 0;
this.cachedImage = null;
this.custAction = '';
this.cache = [];
this.canvas = null;
this.ctx = null;
// 重置拖拽状态
this.dragging = false;
this.translateX = 0;
this.translateY = 0;
this.canvasPosition.x = 0;
this.canvasPosition.y = 0;
// 重置缩放
this.scale = 1;
this.originX = 0;
this.originY = 0;
},
/**拖拽相关方法 */
startDrag(event) {
// 只响应左键拖拽
if (event.button !== 0 || this.custAction) return;
this.dragging = true;
this.startX = event.clientX;
this.startY = event.clientY;
this.canvas.style.cursor = 'grabbing';
},
doDrag(event) {
if (!this.dragging) return;
const deltaX = event.clientX - this.startX;
const deltaY = event.clientY - this.startY;
// 更新canvas的位置
this.canvasPosition.x += deltaX;
this.canvasPosition.y += deltaY;
},
endDrag() {
if (!this.dragging) return;
this.dragging = false;
this.canvas.style.cursor = 'default';
},
/**放大缩小相关方法 */
zoomIn() {
if (this.scale >= this.maxScale) return;
// 计算缩放中心点(画布中心)
const canvas = this.canvas;
this.originX = canvas.width / 2;
this.originY = canvas.height / 2;
// 增加缩放比例
this.scale = Math.min(this.scale * 1.2, this.maxScale);
this.redrawImage();
},
zoomOut() {
if (this.scale <= this.minScale) return;
// 计算缩放中心点(画布中心)
const canvas = this.canvas;
this.originX = canvas.width / 2;
this.originY = canvas.height / 2;
// 减少缩放比例
this.scale = Math.max(this.scale * 0.8, this.minScale);
this.redrawImage();
},
handleWheel(event) {
event.preventDefault();
// 计算缩放中心点(鼠标位置)
const rect = this.canvas.getBoundingClientRect();
this.originX = event.clientX - rect.left;
this.originY = event.clientY - rect.top;
// 根据滚轮方向调整缩放比例
if (event.deltaY < 0) {
this.zoomIn();
} else {
this.zoomOut();
}
},
/**上一张下一张所需方法 */
prev(){
let index = this.imgs.findIndex(item => item.fileId == this.active)
if (index-1 >= 0) {
this.onChangeTab(this.imgs[index-1].fileId)
}else{
return
}
},
next(){
let index = this.imgs.findIndex(item => item.fileId == this.active)
if (index+1 < this.imgs.length) {
this.onChangeTab(this.imgs[index+1].fileId)
}else{
return
}
},
/**全屏所需方法 */
toggleFullScreen() {
if (!this.isFullscreen) {
// 打开全屏前设置当前索引
this.carouselIndex = this.imgs.findIndex(
img => img.fileId === this.active
);
// 如果找不到,默认显示第一张
if (this.carouselIndex === -1) {
this.carouselIndex = 0;
}
// 使用 $nextTick 确保 DOM 更新
this.$nextTick(() => {
// 手动设置轮播位置
if (this.$refs.carousel && this.$refs.carousel.goTo) {
this.$refs.carousel.goTo(this.carouselIndex);
}
});
}
this.isFullscreen = !this.isFullscreen;
},
handleCarouselChange(current) {
this.carouselIndex = current;
// 更新激活的图片
if (this.imgs[current]) {
this.active = this.imgs[current].fileId;
this.currentImageUrl = this.imgs[current].src;
this.currentImageName = this.imgs[current].downloadFileName;
}
},
// 添加坐标转换方法
getActualPosition(event) {
const rect = this.canvas.getBoundingClientRect();
return {
x: (event.clientX - rect.left - this.translateX) / this.scale,
y: (event.clientY - rect.top - this.translateY) / this.scale
};
}
},
};
</script>
<style lang="less" scoped>
.sImgEditorBox {
width: 100%;
height: 100%;
}
.top {
height: calc(100% - 120px);
padding-top: 16px;
width: 100%;
text-align: center;
overflow: auto;
}
.bottom {
position: relative;
height: 120px;
background: #fff;
z-index: 1;
.actionBox {
height: 40px;
border-top: 1px solid #dadada;
border-bottom: 1px solid #dadada;
display: flex;
justify-content: space-between;
}
.tip {
display: flex;
align-items: center;
padding-left: 8px;
color: red;
}
.actionBox1 {
text-align: right;
display: flex;
align-items: center;
a,
div {
display: inline-block;
margin-right: 16px;
font-size: 20px;
}
}
.viewList {
height: calc(100% - 40px);
padding: 0 16px;
.viewListImg,
.btnUpload {
display: inline-block;
width: 40px;
height: 60px;
}
.btnUpload {
border: 1px dashed #2160b8;
display: flex;
justify-content: center;
align-items: center;
}
}
}
/deep/ .anticon {
margin: 0 !important;
}
/deep/ .ant-tabs-tab {
padding: 14px 5px !important;
}
/deep/ .ant-tabs,
/deep/.ant-tabs-bar,
/deep/ .ant-tabs-nav-container,
/deep/.ant-tabs-nav-wrap,
/deep/ .ant-tabs-nav-scroll,
/deep/.ant-tabs-nav {
height: 100%;
}
.hideDom {
display: inline-block;
visibility: hidden;
width: 0px;
height: 0px;
}
.btn {
color: #555;
}
.btn:hover {
color: #1890ff;
}
.selBtn {
color: #1890ff;
}
.disabledBox {
color: #ddd;
}
/**全屏样式 */
// 全屏容器
.fullscreen-container {
position: fixed;
top: 0;
left: 0;
z-index: 1000;
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.6);
// 轮播图容器
.image-carousel {
width: 100%;
height: 100%;
.slick-slide {
display: flex !important;
justify-content: center;
align-items: center;
height: 100%;
}
// 自定义左右箭头样式
.custom-slick-arrow {
position: absolute;
z-index: 1000;
top: 50%;
transform: translateY(-50%);
width: 60px;
height: 100px;
display: flex !important;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.4);
border-radius: 5px;
font-size: 32px;
color: white;
transition: all 0.3s ease;
&:before {
display: none;
}
&:hover {
background: rgba(0, 0, 0, 0.7);
transform: translateY(-50%) scale(1.1);
}
}
// 左侧箭头
[slot="prevArrow"] {
left: 10px;
}
// 右侧箭头
[slot="nextArrow"] {
right: 10px;
}
}
}
// 图片包装
.image-wrapper {
// max-width: 90%;
// max-height: 90%;
overflow: auto;
display: flex !important;
justify-content: center;
align-items: center;
height: 100%;
img {
max-width: 100%;
max-height: 100vh;
object-fit: contain;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
}
}
// 关闭按钮样式(右上角)
.close-fullscreen-btn {
position: absolute;
top: 20px;
right: 20px;
z-index: 1001; // 确保在最上层
background: rgba(255, 255, 255, 0.3);
border: none;
color: white;
font-size: 24px;
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.3s ease;
&:hover {
background: rgba(255, 0, 0, 0.7);
transform: scale(1.1);
}
}
</style>
这个是我改完的,先遮挡后放大,遮挡丢失为什么?