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; +}