fix 实时监控收集
This commit is contained in:
@@ -104,11 +104,16 @@
|
||||
<template #header>
|
||||
<div class="flex w-full items-center justify-between gap-2 min-w-0 pr-8">
|
||||
<span class="truncate">实时监控-北门出入门监控</span>
|
||||
<el-button type="primary" class="shrink-0">绘制区域</el-button>
|
||||
<el-button type="primary" class="shrink-0" @click="drawImage()">绘制区域</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div class="monitor-video-wrap">
|
||||
<div class="monitor-video-stack">
|
||||
<video ref="monitorVideoRef" class="monitor-video" controls preload="metadata" :src="monitorVideoUrl">您的浏览器不支持视频播放</video>
|
||||
<div v-show="monitorDrawCollecting" class="monitor-draw-overlay" @click.stop="onMonitorDrawClick" />
|
||||
<div v-if="monitorRectPx" class="monitor-draw-rect" :style="drawRectStyle" />
|
||||
<div v-if="monitorRectPx" class="monitor-draw-label" :style="drawLabelStyle">当前报警区域</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -145,6 +150,31 @@ const dialog1 = reactive<DialogOption>({
|
||||
const monitorVideoUrl = 'http://192.168.0.214:8088/5a0db047943df198aff98b650ac9e001.mp4';
|
||||
const monitorVideoRef = ref<HTMLVideoElement>();
|
||||
|
||||
type MonitorPoint = { x: number; y: number };
|
||||
const monitorDrawCollecting = ref(false);
|
||||
const monitorDrawPoints = ref<MonitorPoint[]>([]);
|
||||
const monitorRectPx = ref<{ left: number; top: number; width: number; height: number } | null>(null);
|
||||
|
||||
const drawRectStyle = computed<Record<string, string>>(() => {
|
||||
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<Record<string, string>>(() => {
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user