fix 实时监控收集
This commit is contained in:
@@ -104,11 +104,16 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex w-full items-center justify-between gap-2 min-w-0 pr-8">
|
<div class="flex w-full items-center justify-between gap-2 min-w-0 pr-8">
|
||||||
<span class="truncate">实时监控-北门出入门监控</span>
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="monitor-video-wrap">
|
<div class="monitor-video-wrap">
|
||||||
<video ref="monitorVideoRef" class="monitor-video" controls preload="metadata" :src="monitorVideoUrl">您的浏览器不支持视频播放</video>
|
<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>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
@@ -145,6 +150,31 @@ const dialog1 = reactive<DialogOption>({
|
|||||||
const monitorVideoUrl = 'http://192.168.0.214:8088/5a0db047943df198aff98b650ac9e001.mp4';
|
const monitorVideoUrl = 'http://192.168.0.214:8088/5a0db047943df198aff98b650ac9e001.mp4';
|
||||||
const monitorVideoRef = ref<HTMLVideoElement>();
|
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 = {
|
const initFormData = {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
code: 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) => {
|
const handleSee = async (row?: any) => {
|
||||||
reset();
|
reset();
|
||||||
|
resetMonitorDraw();
|
||||||
const _id = row?.id || ids.value[0];
|
const _id = row?.id || ids.value[0];
|
||||||
const res = await getDeviceInfo(_id);
|
const res = await getDeviceInfo(_id);
|
||||||
Object.assign(form.value, res.data);
|
Object.assign(form.value, res.data);
|
||||||
@@ -275,9 +312,102 @@ const handleSee = async (row?: any) => {
|
|||||||
const handleMonitorDialogClose = (done: () => void) => {
|
const handleMonitorDialogClose = (done: () => void) => {
|
||||||
monitorVideoRef.value?.pause();
|
monitorVideoRef.value?.pause();
|
||||||
monitorVideoRef.value && (monitorVideoRef.value.currentTime = 0);
|
monitorVideoRef.value && (monitorVideoRef.value.currentTime = 0);
|
||||||
|
resetMonitorDraw();
|
||||||
done();
|
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 handleDelete = async (row?: DemoVO) => {
|
||||||
const _ids = row?.id || ids.value;
|
const _ids = row?.id || ids.value;
|
||||||
@@ -333,12 +463,21 @@ onMounted(() => {
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: #000;
|
background: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.monitor-video-stack {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.monitor-video {
|
.monitor-video {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
@@ -346,4 +485,33 @@ onMounted(() => {
|
|||||||
object-fit: contain; //cover裁剪
|
object-fit: contain; //cover裁剪
|
||||||
background: #000;
|
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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user