<template>
|
|
<div
|
|
class="flex-row card-page"
|
|
style="background: #090918"
|
|
v-loading="loading"
|
|
>
|
|
<div style="flex: 1; height: 100%; display: flex; flex-direction: column">
|
|
<div class="img-wrap">
|
|
<div
|
|
class="zoom-container"
|
|
ref="zoomContainer"
|
|
@mousemove="handleMouseMove"
|
|
@mouseleave="handleMouseLeave"
|
|
@wheel="handleWheel"
|
|
>
|
|
<div
|
|
class="zoom-wrapper"
|
|
ref="zoomWrapper"
|
|
:style="{
|
|
transform: `scale(${imageScale}) translate(${imagePosition.x / imageScale}px, ${imagePosition.y / imageScale}px)`,
|
|
transformOrigin: '0 0',
|
|
width: '100%',
|
|
height: '100%'
|
|
}"
|
|
@mousedown="startDrag($event)"
|
|
@touchstart="startTouchDrag($event)"
|
|
>
|
|
<!-- :src="'/shaoxing/videoMonitor/xunshiht/htjcImage/'+selectImgUrl"
|
|
src="http://192.168.1.116:18891/shaoxing/videoMonitor/app/htjc6/htjcImage//test/39357.jpg"
|
|
|
|
-->
|
|
<img
|
|
ref="previewImage"
|
|
:src="'/app/htjc6/htjcImage/'+selectImgUrl"
|
|
style="width: 100%; height: 100%; object-fit: contain"
|
|
@load="handleImageLoad"
|
|
@error="handleImageError"
|
|
draggable="false"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 重置按钮 -->
|
|
<div class="zoom-controls">
|
|
<el-button size="mini" circle @click="resetImage" title="重置">
|
|
↺
|
|
</el-button>
|
|
</div>
|
|
|
|
<!-- 上一个/下一个图片按钮 -->
|
|
<el-button
|
|
v-if="bShowBackBtn"
|
|
@click="() => selectIndex--"
|
|
class="img-handle-btn img-handle-btn-left left-btn"
|
|
></el-button>
|
|
<el-button
|
|
v-if="bShowNextBtn"
|
|
@click="() => selectIndex++"
|
|
class="img-handle-btn img-handle-btn-right right-btn"
|
|
></el-button>
|
|
|
|
<!-- 温度悬浮层 -->
|
|
<div
|
|
v-if="dataShowTemperatureTool && hoverData"
|
|
class="temperature-display"
|
|
:style="{
|
|
left: hoverData.x + 'px',
|
|
top: hoverData.y + 'px'
|
|
}"
|
|
>
|
|
{{ hoverData.temperature }}°C
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="img-list-wrap" v-if="dataImageUrls.length > 1">
|
|
<ul ref="imgUlRef">
|
|
<li
|
|
v-for="(item, index) in dataImageUrls"
|
|
:key="index"
|
|
@click="onSelectImage(item, index)"
|
|
>
|
|
<div
|
|
:style="imgListItemStyle"
|
|
:class="{ 'img-list-item-selected': index === selectIndex }"
|
|
>
|
|
<img
|
|
:src="item"
|
|
style="width: 100%; height: 100%; object-fit: contain"
|
|
draggable="false"
|
|
/>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { getTemperatureValue } from "@/api/device";
|
|
import { downloadCsv } from "@/api/basedata/patrolpointmnt/patrolpointpreset.js";
|
|
import Papa from "papaparse";
|
|
|
|
const IMG_LIST_ITEM_HEIGHT = 170;
|
|
|
|
export default {
|
|
name: "ImagePreviewZoomTemperature",
|
|
components: {},
|
|
props: {
|
|
visible: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
showTemperatureTool: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
imageUrls: {
|
|
type: Array,
|
|
default: [],
|
|
require: false,
|
|
},
|
|
selectImgPosition: {
|
|
type: Number,
|
|
default: 0,
|
|
},
|
|
imageName: {
|
|
type: [String, Array],
|
|
default: "",
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
selectIndex: 0,
|
|
imgListItemStyle: {
|
|
width: "300px",
|
|
height: `${IMG_LIST_ITEM_HEIGHT}px`,
|
|
margin: "5px 0",
|
|
},
|
|
temperatureInfo: {},
|
|
loading: false,
|
|
dataShowTemperatureTool: false,
|
|
dataImageUrls: [],
|
|
showDebugInfo: true,
|
|
|
|
// 缩放相关
|
|
imageScale: 1,
|
|
imagePosition: { x: 0, y: 0 },
|
|
isDragging: false,
|
|
dragStartPos: { x: 0, y: 0 },
|
|
dragStartImagePos: { x: 0, y: 0 },
|
|
|
|
// 动画相关
|
|
animationFrameId: null,
|
|
|
|
// 温度数据相关
|
|
temperatureData: [],
|
|
tempRows: 0,
|
|
tempCols: 0,
|
|
hoverData: null,
|
|
imageSize: { width: 0, height: 0 },
|
|
currentImageName: "",
|
|
|
|
lastMouseX: null,
|
|
lastMouseY: null,
|
|
|
|
// 图片实际内容显示区域(在 zoom-container 坐标系中)
|
|
imageContentRect: {
|
|
x: 0,
|
|
y: 0,
|
|
width: 0,
|
|
height: 0
|
|
},
|
|
};
|
|
},
|
|
computed: {
|
|
selectImgUrl() {
|
|
return this.dataImageUrls.length > this.selectIndex
|
|
? this.dataImageUrls[this.selectIndex]
|
|
: "";
|
|
},
|
|
bShowNextBtn() {
|
|
return this.selectIndex + 1 < this.dataImageUrls.length;
|
|
},
|
|
bShowBackBtn() {
|
|
return this.selectIndex > 0;
|
|
},
|
|
},
|
|
watch: {
|
|
visible(value) {
|
|
if (value) {
|
|
this.reset();
|
|
}
|
|
},
|
|
selectImgPosition() {
|
|
if (this.selectImgPosition < this.dataImageUrls.length) {
|
|
this.selectIndex = this.selectImgPosition;
|
|
this.resetImage();
|
|
this.loadTemperatureDataForCurrentImage();
|
|
}
|
|
},
|
|
selectIndex() {
|
|
if (this.$refs.imgUlRef) {
|
|
this.$refs.imgUlRef.scrollTo({
|
|
top: IMG_LIST_ITEM_HEIGHT * this.selectIndex,
|
|
behavior: "smooth",
|
|
});
|
|
}
|
|
this.resetImage();
|
|
this.hoverData = null;
|
|
|
|
if (Array.isArray(this.imageName) && this.imageName.length > this.selectIndex) {
|
|
this.currentImageName = this.imageName[this.selectIndex];
|
|
} else if (typeof this.imageName === 'string') {
|
|
this.currentImageName = this.imageName;
|
|
}
|
|
|
|
this.loadTemperatureDataForCurrentImage();
|
|
},
|
|
},
|
|
created() {
|
|
this.dataImageUrls = this.imageUrls;
|
|
this.selectIndex = this.selectImgPosition;
|
|
this.dataShowTemperatureTool = this.showTemperatureTool;
|
|
|
|
if (Array.isArray(this.imageName) && this.imageName.length > this.selectIndex) {
|
|
this.currentImageName = this.imageName[this.selectIndex];
|
|
} else if (typeof this.imageName === 'string') {
|
|
this.currentImageName = this.imageName;
|
|
} else if (this.imageName) {
|
|
this.currentImageName = this.imageName;
|
|
}
|
|
|
|
if (!this.dataImageUrls || !this.dataImageUrls.length) {
|
|
const info = JSON.parse(sessionStorage.getItem("imageInfo"));
|
|
if (info && info.imageUrls) {
|
|
this.dataImageUrls = info.imageUrls;
|
|
}
|
|
if (info && info.selectImgPosition) {
|
|
this.selectIndex = info.selectImgPosition;
|
|
}
|
|
if (info && info.showTemperatureTool) {
|
|
this.dataShowTemperatureTool = info.showTemperatureTool;
|
|
}
|
|
if (info && info.imageName) {
|
|
this.currentImageName = info.imageName;
|
|
}
|
|
}
|
|
|
|
if (!this.currentImageName && this.selectImgUrl) {
|
|
this.extractImageNameFromUrl(this.selectImgUrl);
|
|
}
|
|
|
|
this.$nextTick(() => {
|
|
this.loadTemperatureDataForCurrentImage();
|
|
this.updateImageContentRect();
|
|
});
|
|
},
|
|
mounted() {
|
|
this.bindGlobalEvents();
|
|
window.addEventListener('resize', this.updateImageContentRect);
|
|
this.startAnimationLoop();
|
|
},
|
|
beforeDestroy() {
|
|
this.unbindGlobalEvents();
|
|
this.stopAnimationLoop();
|
|
window.removeEventListener('resize', this.updateImageContentRect);
|
|
},
|
|
methods: {
|
|
getDataWidth() {
|
|
return this.tempCols || 0;
|
|
},
|
|
|
|
getDataHeight() {
|
|
return this.tempRows || 0;
|
|
},
|
|
|
|
extractImageNameFromUrl(url) {
|
|
if (!url) return;
|
|
try {
|
|
const urlParts = url.split('/');
|
|
let fileName = urlParts[urlParts.length - 1];
|
|
if (fileName.includes('?')) {
|
|
fileName = fileName.split('?')[0];
|
|
}
|
|
this.currentImageName = fileName;
|
|
// console.log("提取的图片名称:", this.currentImageName);
|
|
} catch (error) {
|
|
// console.error("提取图片名称失败:", error);
|
|
}
|
|
},
|
|
|
|
reset() {
|
|
this.selectIndex = 0;
|
|
this.resetImage();
|
|
this.hoverData = null;
|
|
},
|
|
|
|
onSelectImage(item, index) {
|
|
this.selectIndex = index;
|
|
},
|
|
|
|
// 更新图片实际内容显示区域(在 zoom-container 坐标系中)
|
|
// 这个方法计算 object-fit: contain 后的图片内容区域
|
|
updateImageContentRect() {
|
|
const img = this.$refs.previewImage;
|
|
const container = this.$refs.zoomContainer;
|
|
if (!img || !container) return;
|
|
|
|
const containerRect = container.getBoundingClientRect();
|
|
const imgRect = img.getBoundingClientRect();
|
|
|
|
// 计算图片内容在 img 元素中的偏移和尺寸
|
|
// 由于 object-fit: contain,图片内容会居中显示在 img 元素内
|
|
const naturalWidth = this.imageSize.width || img.naturalWidth;
|
|
const naturalHeight = this.imageSize.height || img.naturalHeight;
|
|
|
|
if (!naturalWidth || !naturalHeight) return;
|
|
|
|
const imgDisplayWidth = imgRect.width;
|
|
const imgDisplayHeight = imgRect.height;
|
|
|
|
const imageAspect = naturalWidth / naturalHeight;
|
|
const containerAspect = imgDisplayWidth / imgDisplayHeight;
|
|
|
|
let contentWidth, contentHeight, contentX, contentY;
|
|
|
|
if (imageAspect > containerAspect) {
|
|
contentWidth = imgDisplayWidth;
|
|
contentHeight = imgDisplayWidth / imageAspect;
|
|
contentX = 0;
|
|
contentY = (imgDisplayHeight - contentHeight) / 2;
|
|
} else {
|
|
contentHeight = imgDisplayHeight;
|
|
contentWidth = imgDisplayHeight * imageAspect;
|
|
contentX = (imgDisplayWidth - contentWidth) / 2;
|
|
contentY = 0;
|
|
}
|
|
|
|
// 将坐标从视口坐标系转换到 zoom-container 坐标系
|
|
const containerLeft = containerRect.left;
|
|
const containerTop = containerRect.top;
|
|
|
|
// 图片内容在 zoom-container 坐标系中的位置
|
|
// imgRect 是变换后的位置,已经是相对于视口的
|
|
// 我们需要计算内容在容器中的位置
|
|
const contentLeftInViewport = imgRect.left + contentX;
|
|
const contentTopInViewport = imgRect.top + contentY;
|
|
|
|
this.imageContentRect = {
|
|
x: contentLeftInViewport - containerLeft,
|
|
y: contentTopInViewport - containerTop,
|
|
width: contentWidth,
|
|
height: contentHeight
|
|
};
|
|
|
|
// console.log('图片内容区域:', this.imageContentRect);
|
|
},
|
|
|
|
startAnimationLoop() {
|
|
const updateTemperature = () => {
|
|
if (this.dataShowTemperatureTool && this.lastMouseX && this.lastMouseY) {
|
|
this.calculateTemperatureAtPosition(this.lastMouseX, this.lastMouseY);
|
|
}
|
|
this.animationFrameId = requestAnimationFrame(updateTemperature);
|
|
};
|
|
this.animationFrameId = requestAnimationFrame(updateTemperature);
|
|
},
|
|
|
|
stopAnimationLoop() {
|
|
if (this.animationFrameId) {
|
|
cancelAnimationFrame(this.animationFrameId);
|
|
this.animationFrameId = null;
|
|
}
|
|
},
|
|
|
|
// ==================== 双线性插值 ====================
|
|
bilinearInterpolation(data, row, col, rows, cols) {
|
|
if (row < 0 || row > rows - 1 || col < 0 || col > cols - 1) {
|
|
return null;
|
|
}
|
|
|
|
const row0 = Math.floor(row);
|
|
const row1 = Math.min(row0 + 1, rows - 1);
|
|
const col0 = Math.floor(col);
|
|
const col1 = Math.min(col0 + 1, cols - 1);
|
|
|
|
const v00 = parseFloat(data[row0][col0]);
|
|
const v01 = parseFloat(data[row0][col1]);
|
|
const v10 = parseFloat(data[row1][col0]);
|
|
const v11 = parseFloat(data[row1][col1]);
|
|
|
|
if ([v00, v01, v10, v11].some(v => isNaN(v) || !isFinite(v))) {
|
|
const nearestRow = Math.round(row);
|
|
const nearestCol = Math.round(col);
|
|
const val = parseFloat(data[nearestRow]?.[nearestCol]);
|
|
return isNaN(val) ? null : val;
|
|
}
|
|
|
|
const rowWeight = row - row0;
|
|
const colWeight = col - col0;
|
|
|
|
const top = v00 * (1 - colWeight) + v01 * colWeight;
|
|
const bottom = v10 * (1 - colWeight) + v11 * colWeight;
|
|
const result = top * (1 - rowWeight) + bottom * rowWeight;
|
|
|
|
return result;
|
|
},
|
|
|
|
// 根据图片坐标实时计算温度值
|
|
getTemperatureAtImagePosition(imageX, imageY) {
|
|
if (!this.temperatureData || this.temperatureData.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
const tempRows = this.tempRows;
|
|
const tempCols = this.tempCols;
|
|
|
|
if (tempRows === 0 || tempCols === 0) {
|
|
return null;
|
|
}
|
|
|
|
// 将图片坐标映射到温度数据坐标
|
|
const floatRow = (imageY / this.imageSize.height) * (tempRows - 1);
|
|
const floatCol = (imageX / this.imageSize.width) * (tempCols - 1);
|
|
|
|
// 使用双线性插值计算温度
|
|
const temp = this.bilinearInterpolation(
|
|
this.temperatureData,
|
|
floatRow,
|
|
floatCol,
|
|
tempRows,
|
|
tempCols
|
|
);
|
|
|
|
return temp;
|
|
},
|
|
|
|
// 获取鼠标在图片原始坐标系中的位置
|
|
getMouseOnImage(clientX, clientY) {
|
|
const containerRect = this.$refs.zoomContainer.getBoundingClientRect();
|
|
|
|
// 鼠标相对于容器的位置
|
|
const relativeX = clientX - containerRect.left;
|
|
const relativeY = clientY - containerRect.top;
|
|
|
|
// 获取图片内容显示区域
|
|
const contentRect = this.imageContentRect;
|
|
if (!contentRect || contentRect.width === 0 || contentRect.height === 0) {
|
|
return null;
|
|
}
|
|
|
|
// 检查鼠标是否在图片内容区域内
|
|
if (relativeX < contentRect.x || relativeX > contentRect.x + contentRect.width ||
|
|
relativeY < contentRect.y || relativeY > contentRect.y + contentRect.height) {
|
|
return null;
|
|
}
|
|
|
|
// 计算在图片内容区域内的相对位置(0-1范围)
|
|
let imageRelativeX = (relativeX - contentRect.x) / contentRect.width;
|
|
let imageRelativeY = (relativeY - contentRect.y) / contentRect.height;
|
|
|
|
// 限制范围
|
|
imageRelativeX = Math.max(0, Math.min(1, imageRelativeX));
|
|
imageRelativeY = Math.max(0, Math.min(1, imageRelativeY));
|
|
|
|
// 映射到图片原始坐标
|
|
return {
|
|
x: imageRelativeX * this.imageSize.width,
|
|
y: imageRelativeY * this.imageSize.height,
|
|
containerX: relativeX,
|
|
containerY: relativeY
|
|
};
|
|
},
|
|
|
|
calculateTemperatureAtPosition(clientX, clientY) {
|
|
if (!this.temperatureData || this.temperatureData.length === 0) {
|
|
if (this.hoverData) {
|
|
this.hoverData = null;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 获取鼠标在图片上的位置
|
|
const mousePos = this.getMouseOnImage(clientX, clientY);
|
|
if (!mousePos) {
|
|
if (this.hoverData) {
|
|
this.hoverData = null;
|
|
}
|
|
return;
|
|
}
|
|
|
|
const { x: imageX, y: imageY, containerX, containerY } = mousePos;
|
|
|
|
// 实时计算温度值
|
|
const temperature = this.getTemperatureAtImagePosition(imageX, imageY);
|
|
|
|
if (temperature !== null && !isNaN(temperature) && isFinite(temperature)) {
|
|
const containerRect = this.$refs.zoomContainer.getBoundingClientRect();
|
|
const offsetX = 10;
|
|
const offsetY = 10;
|
|
|
|
let hoverX = containerX + offsetX;
|
|
let hoverY = containerY + offsetY;
|
|
|
|
const tooltipWidth = 80;
|
|
const tooltipHeight = 30;
|
|
|
|
// 确保tooltip不超出容器边界
|
|
if (hoverX + tooltipWidth > containerRect.width) {
|
|
hoverX = containerX - tooltipWidth - offsetX;
|
|
}
|
|
if (hoverY + tooltipHeight > containerRect.height) {
|
|
hoverY = containerY - tooltipHeight - offsetY;
|
|
}
|
|
if (hoverX < 0) {
|
|
hoverX = offsetX;
|
|
}
|
|
if (hoverY < 0) {
|
|
hoverY = offsetY;
|
|
}
|
|
|
|
this.hoverData = {
|
|
x: hoverX,
|
|
y: hoverY,
|
|
temperature: temperature.toFixed(1),
|
|
};
|
|
} else if (this.hoverData) {
|
|
this.hoverData = null;
|
|
}
|
|
},
|
|
|
|
handleMouseMove(event) {
|
|
if (!this.dataShowTemperatureTool) return;
|
|
|
|
this.lastMouseX = event.clientX;
|
|
this.lastMouseY = event.clientY;
|
|
this.calculateTemperatureAtPosition(event.clientX, event.clientY);
|
|
},
|
|
|
|
handleMouseLeave() {
|
|
this.hoverData = null;
|
|
this.lastMouseX = null;
|
|
this.lastMouseY = null;
|
|
},
|
|
|
|
handleWheel(event) {
|
|
event.preventDefault();
|
|
|
|
const container = this.$refs.zoomContainer;
|
|
const containerRect = container.getBoundingClientRect();
|
|
|
|
// 鼠标相对于容器的位置
|
|
const mouseX = event.clientX - containerRect.left;
|
|
const mouseY = event.clientY - containerRect.top;
|
|
|
|
const delta = event.deltaY;
|
|
const direction = delta > 0 ? -1 : 1;
|
|
let newScale = this.imageScale + direction * 0.1;
|
|
newScale = Math.max(0.1, Math.min(5, newScale));
|
|
|
|
const oldScale = this.imageScale;
|
|
const scaleChange = newScale / oldScale;
|
|
|
|
// 以鼠标位置为中心缩放
|
|
this.imagePosition = {
|
|
x: this.imagePosition.x - (mouseX - this.imagePosition.x) * (scaleChange - 1),
|
|
y: this.imagePosition.y - (mouseY - this.imagePosition.y) * (scaleChange - 1)
|
|
};
|
|
this.imageScale = newScale;
|
|
|
|
// 缩放后更新图片内容区域
|
|
this.$nextTick(() => {
|
|
this.updateImageContentRect();
|
|
if (this.lastMouseX && this.lastMouseY) {
|
|
this.calculateTemperatureAtPosition(this.lastMouseX, this.lastMouseY);
|
|
}
|
|
});
|
|
},
|
|
|
|
startDrag(event) {
|
|
if (event.button !== 0) return;
|
|
this.isDragging = true;
|
|
this.dragStartPos = { x: event.clientX, y: event.clientY };
|
|
this.dragStartImagePos = { x: this.imagePosition.x, y: this.imagePosition.y };
|
|
|
|
const wrapper = this.$refs.zoomWrapper;
|
|
if (wrapper) wrapper.classList.add('dragging');
|
|
|
|
document.body.style.userSelect = 'none';
|
|
event.preventDefault();
|
|
},
|
|
|
|
startTouchDrag(event) {
|
|
if (event.touches.length !== 1) return;
|
|
this.isDragging = true;
|
|
this.dragStartPos = { x: event.touches[0].clientX, y: event.touches[0].clientY };
|
|
this.dragStartImagePos = { x: this.imagePosition.x, y: this.imagePosition.y };
|
|
|
|
const wrapper = this.$refs.zoomWrapper;
|
|
if (wrapper) wrapper.classList.add('dragging');
|
|
|
|
event.preventDefault();
|
|
},
|
|
|
|
handleDragMove(event) {
|
|
if (!this.isDragging) return;
|
|
|
|
let clientX, clientY;
|
|
if (event.type === 'mousemove') {
|
|
clientX = event.clientX;
|
|
clientY = event.clientY;
|
|
} else if (event.type === 'touchmove' && event.touches.length === 1) {
|
|
clientX = event.touches[0].clientX;
|
|
clientY = event.touches[0].clientY;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
const deltaX = clientX - this.dragStartPos.x;
|
|
const deltaY = clientY - this.dragStartPos.y;
|
|
|
|
// 拖拽时修改 imagePosition
|
|
this.imagePosition = {
|
|
x: this.dragStartImagePos.x + deltaX,
|
|
y: this.dragStartImagePos.y + deltaY
|
|
};
|
|
|
|
// 拖拽后更新图片内容区域
|
|
this.$nextTick(() => {
|
|
this.updateImageContentRect();
|
|
if (this.lastMouseX && this.lastMouseY) {
|
|
this.calculateTemperatureAtPosition(this.lastMouseX, this.lastMouseY);
|
|
}
|
|
});
|
|
|
|
event.preventDefault();
|
|
},
|
|
|
|
handleDragEnd() {
|
|
if (!this.isDragging) return;
|
|
|
|
const wrapper = this.$refs.zoomWrapper;
|
|
if (wrapper) wrapper.classList.remove('dragging');
|
|
|
|
document.body.style.userSelect = '';
|
|
this.isDragging = false;
|
|
},
|
|
|
|
resetImage() {
|
|
this.imageScale = 1;
|
|
this.imagePosition = { x: 0, y: 0 };
|
|
this.$nextTick(() => {
|
|
this.updateImageContentRect();
|
|
});
|
|
},
|
|
|
|
bindGlobalEvents() {
|
|
document.addEventListener('mousemove', this.handleDragMove);
|
|
document.addEventListener('mouseup', this.handleDragEnd);
|
|
document.addEventListener('touchmove', this.handleDragMove, { passive: false });
|
|
document.addEventListener('touchend', this.handleDragEnd);
|
|
},
|
|
|
|
unbindGlobalEvents() {
|
|
document.removeEventListener('mousemove', this.handleDragMove);
|
|
document.removeEventListener('mouseup', this.handleDragEnd);
|
|
document.removeEventListener('touchmove', this.handleDragMove);
|
|
document.removeEventListener('touchend', this.handleDragEnd);
|
|
},
|
|
|
|
handleImageLoad(e) {
|
|
const img = e.target;
|
|
if (img) {
|
|
this.imageSize = {
|
|
width: img.naturalWidth,
|
|
height: img.naturalHeight,
|
|
};
|
|
// console.log("图片加载完成,尺寸:", img.naturalWidth, "x", img.naturalHeight);
|
|
|
|
this.$nextTick(() => {
|
|
this.updateImageContentRect();
|
|
});
|
|
|
|
if (!this.currentImageName && this.selectImgUrl) {
|
|
this.extractImageNameFromUrl(this.selectImgUrl);
|
|
this.loadTemperatureDataForCurrentImage();
|
|
}
|
|
}
|
|
},
|
|
|
|
handleImageError(e) {
|
|
// console.error("图片加载失败:", this.selectImgUrl);
|
|
},
|
|
|
|
async loadTemperatureDataForCurrentImage() {
|
|
if (!this.dataShowTemperatureTool) {
|
|
// console.log("测温工具未启用");
|
|
return;
|
|
}
|
|
|
|
let imageName = this.currentImageName;
|
|
if (!imageName && this.selectImgUrl) {
|
|
this.extractImageNameFromUrl(this.selectImgUrl);
|
|
imageName = this.currentImageName;
|
|
}
|
|
|
|
if (!imageName) {
|
|
// console.warn("无法获取图片名称");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const data = { filePath: this.selectImgUrl};
|
|
const response = await downloadCsv(data);
|
|
|
|
const blob = response instanceof Blob ? response : new Blob([response]);
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = (e) => {
|
|
try {
|
|
const csvText = e.target.result;
|
|
this.parseCSVInChunks(csvText);
|
|
} catch (error) {
|
|
// console.error("处理CSV数据出错:", error);
|
|
}
|
|
};
|
|
|
|
reader.readAsText(blob, "UTF-8");
|
|
} catch (error) {
|
|
// console.error("下载温度数据失败:", error);
|
|
}
|
|
},
|
|
|
|
parseCSVInChunks(csvText) {
|
|
try {
|
|
const lines = csvText.split('\n');
|
|
const tempData = [];
|
|
let maxCols = 0;
|
|
|
|
const BATCH_SIZE = 1000;
|
|
let index = 0;
|
|
|
|
const processBatch = () => {
|
|
const endIndex = Math.min(index + BATCH_SIZE, lines.length);
|
|
|
|
for (let i = index; i < endIndex; i++) {
|
|
const line = lines[i].trim();
|
|
if (!line) continue;
|
|
|
|
const values = line.split(',').map(v => {
|
|
const num = parseFloat(v.trim());
|
|
return isNaN(num) ? null : num;
|
|
});
|
|
|
|
const validValues = values.filter(v => v !== null && isFinite(v));
|
|
|
|
if (validValues.length > 0) {
|
|
tempData.push(validValues);
|
|
if (validValues.length > maxCols) {
|
|
maxCols = validValues.length;
|
|
}
|
|
}
|
|
}
|
|
|
|
index = endIndex;
|
|
|
|
if (index < lines.length) {
|
|
setTimeout(processBatch, 0);
|
|
} else {
|
|
this.finalizeCSVData(tempData, maxCols);
|
|
}
|
|
};
|
|
|
|
processBatch();
|
|
|
|
} catch (error) {
|
|
// console.error('解析CSV出错:', error);
|
|
// this.$message.error('解析CSV出错: ' + error.message);
|
|
}
|
|
},
|
|
|
|
finalizeCSVData(tempData, maxCols) {
|
|
try {
|
|
if (tempData.length === 0) {
|
|
this.$message.warning('CSV温度数据为空');
|
|
return;
|
|
}
|
|
|
|
const normalizedData = [];
|
|
for (let i = 0; i < tempData.length; i++) {
|
|
const row = tempData[i];
|
|
if (row.length < maxCols) {
|
|
const lastVal = row[row.length - 1] || 0;
|
|
while (row.length < maxCols) {
|
|
row.push(lastVal);
|
|
}
|
|
}
|
|
normalizedData.push(row);
|
|
}
|
|
|
|
this.temperatureData = normalizedData;
|
|
this.tempRows = normalizedData.length;
|
|
this.tempCols = normalizedData[0]?.length || 0;
|
|
|
|
let minTemp = Infinity;
|
|
let maxTemp = -Infinity;
|
|
|
|
for (let i = 0; i < normalizedData.length; i++) {
|
|
const row = normalizedData[i];
|
|
if (Array.isArray(row)) {
|
|
for (let j = 0; j < row.length; j++) {
|
|
const val = row[j];
|
|
if (typeof val === 'number' && !isNaN(val) && isFinite(val)) {
|
|
if (val < minTemp) minTemp = val;
|
|
if (val > maxTemp) maxTemp = val;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (this.showDebugInfo) {
|
|
// this.$message.success(`温度数据已加载,范围: ${minTemp}~${maxTemp}°C`);
|
|
}
|
|
|
|
} catch (error) {
|
|
// this.$message.error('处理CSV数据出错: ' + error.message);
|
|
}
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.flex-row {
|
|
display: flex;
|
|
height: 100%;
|
|
}
|
|
|
|
.img-wrap {
|
|
flex: 1;
|
|
height: 0;
|
|
position: relative;
|
|
|
|
.zoom-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
position: relative;
|
|
overflow: hidden;
|
|
cursor: grab;
|
|
|
|
&:active {
|
|
cursor: grabbing;
|
|
}
|
|
}
|
|
|
|
.zoom-wrapper {
|
|
width: 100%;
|
|
height: 100%;
|
|
position: absolute;
|
|
cursor: grab;
|
|
will-change: transform;
|
|
|
|
&.dragging {
|
|
cursor: grabbing;
|
|
}
|
|
|
|
img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: contain;
|
|
user-select: none;
|
|
-webkit-user-drag: none;
|
|
pointer-events: none;
|
|
}
|
|
}
|
|
|
|
.zoom-controls {
|
|
position: absolute;
|
|
top: 10px;
|
|
right: 10px;
|
|
z-index: 10;
|
|
|
|
.el-button {
|
|
background: rgba(0, 0, 0, 0.6);
|
|
color: white;
|
|
border: none;
|
|
font-size: 16px;
|
|
|
|
&:hover {
|
|
background: rgba(0, 0, 0, 0.8);
|
|
}
|
|
}
|
|
}
|
|
|
|
.temperature-display {
|
|
position: absolute;
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
color: white;
|
|
font-weight: bold;
|
|
font-size: 12px;
|
|
pointer-events: none;
|
|
z-index: 10;
|
|
background-color: rgba(0, 0, 0, 0.85);
|
|
white-space: nowrap;
|
|
font-family: monospace;
|
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
.img-handle-btn {
|
|
position: absolute;
|
|
top: 45%;
|
|
color: rgba(255, 255, 255, 0.5);
|
|
font-size: 25px;
|
|
width: 96px;
|
|
height: 96px;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
cursor: pointer;
|
|
z-index: 11;
|
|
}
|
|
|
|
.img-handle-btn-left {
|
|
left: 15px;
|
|
}
|
|
|
|
.img-handle-btn-right {
|
|
right: 15px;
|
|
}
|
|
}
|
|
|
|
.measure-temperature-btn {
|
|
padding: 10px 0;
|
|
display: flex;
|
|
}
|
|
|
|
.img-list-wrap {
|
|
width: 300px;
|
|
height: 100%;
|
|
padding: 0 0 0 10px;
|
|
|
|
ul {
|
|
width: 100%;
|
|
height: 100%;
|
|
padding: 0;
|
|
overflow-x: hidden;
|
|
overflow-y: auto;
|
|
margin: 0;
|
|
scrollbar-width: none;
|
|
|
|
li {
|
|
list-style: none;
|
|
|
|
div {
|
|
width: 300px;
|
|
height: 170px;
|
|
margin: 5px 0;
|
|
|
|
img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: contain;
|
|
}
|
|
}
|
|
}
|
|
|
|
.img-list-item-selected {
|
|
border: 3px solid #40486a;
|
|
}
|
|
}
|
|
}
|
|
|
|
::v-deep .left-btn .is-disabled {
|
|
background-image: none;
|
|
}
|
|
|
|
.left-btn {
|
|
background-image: url("../../assets/image-btn-left-normal.png");
|
|
}
|
|
|
|
.left-btn:disabled {
|
|
background-image: url("../../assets/image-btn-left-disable.png");
|
|
}
|
|
|
|
.left-btn:not(.is-disabled):active {
|
|
background-image: url("../../assets/image-btn-left-press.png");
|
|
}
|
|
|
|
.left-btn:not(.is-disabled):hover {
|
|
background-image: url("../../assets/image-btn-left-hover.png");
|
|
}
|
|
|
|
.right-btn {
|
|
background-image: url("../../assets/image-btn-right-normal.png");
|
|
}
|
|
|
|
.right-btn:disabled {
|
|
background-image: url("../../assets/image-btn-right-disable.png");
|
|
}
|
|
|
|
.right-btn:not(.is-disabled):active {
|
|
background-image: url("../../assets/image-btn-right-press.png");
|
|
}
|
|
|
|
.right-btn:not(.is-disabled):hover {
|
|
background-image: url("../../assets/image-btn-right-hover.png");
|
|
}
|
|
.card-dialog .el-dialog__body{
|
|
overflow: hidden;
|
|
}
|
|
</style>
|