From 4c9cd63ac04fc0a69af434124079ea48067ef749 Mon Sep 17 00:00:00 2001
From: Ironsp <1522278303@qq.com>
Date: Thu, 14 May 2026 16:30:06 +0800
Subject: [PATCH] =?UTF-8?q?fix=20=E5=AE=9E=E6=97=B6=E7=9B=91=E6=8E=A7?=
=?UTF-8?q?=E6=94=B6=E9=9B=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/views/equipmentList/list.vue | 174 ++++++++++++++++++++++++++++++-
1 file changed, 171 insertions(+), 3 deletions(-)
diff --git a/src/views/equipmentList/list.vue b/src/views/equipmentList/list.vue
index 480563e..7499755 100644
--- a/src/views/equipmentList/list.vue
+++ b/src/views/equipmentList/list.vue
@@ -104,11 +104,16 @@
实时监控-北门出入门监控
- 绘制区域
+ 绘制区域
-
+
+
+
+
+
当前报警区域
+
@@ -145,6 +150,31 @@ const dialog1 = reactive({
const monitorVideoUrl = 'http://192.168.0.214:8088/5a0db047943df198aff98b650ac9e001.mp4';
const monitorVideoRef = ref();
+type MonitorPoint = { x: number; y: number };
+const monitorDrawCollecting = ref(false);
+const monitorDrawPoints = ref([]);
+const monitorRectPx = ref<{ left: number; top: number; width: number; height: number } | null>(null);
+
+const drawRectStyle = computed>(() => {
+ const r = monitorRectPx.value;
+ if (!r) return {};
+ return {
+ left: `${r.left}px`,
+ top: `${r.top}px`,
+ width: `${Math.max(r.width, 2)}px`,
+ height: `${Math.max(r.height, 2)}px`
+ };
+});
+
+const drawLabelStyle = computed>(() => {
+ const r = monitorRectPx.value;
+ if (!r) return {};
+ return {
+ left: `${r.left + r.width / 2}px`,
+ top: `${r.top}px`
+ };
+});
+
const initFormData = {
id: undefined,
code: undefined,
@@ -264,8 +294,15 @@ const handleActivate = async (row?: any) => {
};
/* 查看监控 */
+const resetMonitorDraw = () => {
+ monitorDrawCollecting.value = false;
+ monitorDrawPoints.value = [];
+ monitorRectPx.value = null;
+};
+
const handleSee = async (row?: any) => {
reset();
+ resetMonitorDraw();
const _id = row?.id || ids.value[0];
const res = await getDeviceInfo(_id);
Object.assign(form.value, res.data);
@@ -275,9 +312,102 @@ const handleSee = async (row?: any) => {
const handleMonitorDialogClose = (done: () => void) => {
monitorVideoRef.value?.pause();
monitorVideoRef.value && (monitorVideoRef.value.currentTime = 0);
+ resetMonitorDraw();
done();
};
+/** 显示坐标(相对 video 元素) → 视频原始像素坐标,含 object-fit: contain 留白换算 */
+const displayToVideoPixel = (px: number, py: number, video: HTMLVideoElement): { vx: number; vy: number } | null => {
+ const dw = video.clientWidth;
+ const dh = video.clientHeight;
+ const VW = video.videoWidth;
+ const VH = video.videoHeight;
+ if (dw <= 0 || dh <= 0 || VW <= 0 || VH <= 0) return null;
+
+ const scale = Math.min(dw / VW, dh / VH);
+ const contentW = VW * scale;
+ const contentH = VH * scale;
+ const offsetX = (dw - contentW) / 2;
+ const offsetY = (dh - contentH) / 2;
+
+ const vx = Math.min(VW, Math.max(0, (px - offsetX) / scale));
+ const vy = Math.min(VH, Math.max(0, (py - offsetY) / scale));
+ return { vx, vy };
+};
+
+/** 开始采集:视频上点击两个对角点 */
+const drawImage = () => {
+ resetMonitorDraw();
+ monitorDrawCollecting.value = true;
+ proxy?.$modal.msgSuccess('请在视频画面上连续点击两个点,生成报警区域');
+};
+
+const onMonitorDrawClick = (e: MouseEvent) => {
+ if (!monitorDrawCollecting.value) return;
+ const video = monitorVideoRef.value;
+ if (!video) {
+ proxy?.$modal.msgWarning('视频未就绪');
+ return;
+ }
+ const rect = video.getBoundingClientRect();
+ const dw = rect.width;
+ const dh = rect.height;
+ if (dw <= 0 || dh <= 0) {
+ proxy?.$modal.msgWarning('无法获取视频区域尺寸');
+ return;
+ }
+
+ const x = e.clientX - rect.left;
+ const y = e.clientY - rect.top;
+ const p: MonitorPoint = { x, y };
+ monitorDrawPoints.value = [...monitorDrawPoints.value, p];
+ if (monitorDrawPoints.value.length < 2) return;
+
+ const [p1, p2] = monitorDrawPoints.value;
+
+ const VW = video.videoWidth;
+ const VH = video.videoHeight;
+ const v1 = displayToVideoPixel(p1.x, p1.y, video);
+ const v2 = displayToVideoPixel(p2.x, p2.y, video);
+ if (!v1 || !v2 || !VW || !VH) {
+ monitorDrawPoints.value = [];
+ proxy?.$modal.msgWarning('请等待视频加载完成后再绘制(需能读取原始宽高)');
+ return;
+ }
+
+ const left = Math.min(p1.x, p2.x);
+ const top = Math.min(p1.y, p2.y);
+ const width = Math.max(Math.abs(p2.x - p1.x), 1);
+ const height = Math.max(Math.abs(p2.y - p1.y), 1);
+ monitorRectPx.value = { left, top, width, height };
+ monitorDrawCollecting.value = false;
+ monitorDrawPoints.value = [];
+
+ const minVx = Math.min(v1.vx, v2.vx);
+ const maxVx = Math.max(v1.vx, v2.vx);
+ const minVy = Math.min(v1.vy, v2.vy);
+ const maxVy = Math.max(v1.vy, v2.vy);
+ const rw = Math.max(maxVx - minVx, 1);
+ const rh = Math.max(maxVy - minVy, 1);
+
+ const firstTopPctOfHeight = (v1.vy / VH) * 100;
+ const firstRightPctOfWidth = ((VW - v1.vx) / VW) * 100;
+ const rectWidthPctOfWidth = (rw / VW) * 100;
+ const rectHeightPctOfHeight = (rh / VH) * 100;
+
+ const payload = {
+ videoIntrinsicSize: { width: VW, height: VH },
+ firstPointTopOffset_pctOfVideoHeight: Number(firstTopPctOfHeight.toFixed(4)),
+ firstPointDistanceToRight_pctOfVideoWidth: Number(firstRightPctOfWidth.toFixed(4)),
+ rectWidth_pctOfVideoWidth: Number(rectWidthPctOfWidth.toFixed(4)),
+ rectHeight_pctOfVideoHeight: Number(rectHeightPctOfHeight.toFixed(4))
+ };
+ console.log('[监控区域] 四点百分比(相对视频原始宽高 videoWidth/videoHeight):', payload);
+ proxy?.$modal.msgSuccess(
+ `已采集(相对原视频):顶距${payload.firstPointTopOffset_pctOfVideoHeight}% 右距${payload.firstPointDistanceToRight_pctOfVideoWidth}% 框宽${payload.rectWidth_pctOfVideoWidth}% 框高${payload.rectHeight_pctOfVideoHeight}%`
+ );
+};
+
/** 删除按钮操作 */
const handleDelete = async (row?: DemoVO) => {
const _ids = row?.id || ids.value;
@@ -333,12 +463,21 @@ onMounted(() => {
min-height: 0;
width: 100%;
display: flex;
- align-items: center;
+ align-items: stretch;
justify-content: center;
background: #000;
}
+.monitor-video-stack {
+ position: relative;
+ flex: 1;
+ min-height: 0;
+ width: 100%;
+}
+
.monitor-video {
+ position: absolute;
+ inset: 0;
width: 100%;
height: 100%;
max-width: 100%;
@@ -346,4 +485,33 @@ onMounted(() => {
object-fit: contain; //cover裁剪
background: #000;
}
+
+.monitor-draw-overlay {
+ position: absolute;
+ inset: 0;
+ z-index: 2;
+ cursor: crosshair;
+ background: transparent;
+}
+
+.monitor-draw-rect {
+ position: absolute;
+ z-index: 3;
+ pointer-events: none;
+ box-sizing: border-box;
+ border: 2px dashed #2563eb;
+ background: rgba(37, 99, 235, 0.1);
+}
+
+.monitor-draw-label {
+ position: absolute;
+ z-index: 4;
+ pointer-events: none;
+ transform: translate(-50%, -100%);
+ margin-top: -6px;
+ color: #2563eb;
+ font-size: 14px;
+ font-weight: 600;
+ white-space: nowrap;
+}