diff --git a/src/components.d.ts b/src/components.d.ts index 70b49c8..8419878 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -48,6 +48,7 @@ declare module 'vue' { ElTag: typeof import('element-plus/es')['ElTag'] ElText: typeof import('element-plus/es')['ElText'] ElTimePicker: typeof import('element-plus/es')['ElTimePicker'] + ElTooltip: typeof import('element-plus/es')['ElTooltip'] HelloWorld: typeof import('./components/HelloWorld.vue')['default'] ItemList: typeof import('./components/hazard_inspect/ItemList.vue')['default'] Left_bar: typeof import('./components/hazard_inspect/left_bar.vue')['default'] diff --git a/src/components/hazard_inspect/ItemList.vue b/src/components/hazard_inspect/ItemList.vue index a45bc8c..ac2be16 100644 --- a/src/components/hazard_inspect/ItemList.vue +++ b/src/components/hazard_inspect/ItemList.vue @@ -41,8 +41,15 @@ function handleItemClick(item: string, index: number) { {{ index + 1 }}
+ + {{ (item as number[])[1] }} + + + {{ (item as number[])[1] }} + +
- {{ item }} + {{ (item as number[])[2] }} diff --git a/src/components/hazard_inspect/timeline/timeline.css b/src/components/hazard_inspect/timeline/timeline.css index f6b55fb..0e95490 100644 --- a/src/components/hazard_inspect/timeline/timeline.css +++ b/src/components/hazard_inspect/timeline/timeline.css @@ -15,9 +15,15 @@ padding: 0 8px; } +.timeline-title-container { + display: flex; + align-items: center; + gap: 4px; + color: var(--ep-text-color-secondary); +} + .timeline-title { font-size: 12px; - color: var(--ep-text-color-secondary); } .timeline-zoom-controls { @@ -168,17 +174,29 @@ align-items: center; justify-content: center; border-radius: 4px; - background-color: var(--ep-color-primary); + /* background-color: var(--ep-color-primary); */ color: #fff; font-size: 12px; cursor: pointer; transition: background-color 0.2s; } -.hazard-block:hover { +.hazard-block.primary { + background-color: var(--ep-color-primary); +} + +.hazard-block.primary:hover { background-color: color-mix(in srgb, var(--ep-color-primary) 80%, transparent); } +.hazard-block.danger { + background-color: var(--ep-color-danger); +} + +.hazard-block.danger:hover { + background-color: color-mix(in srgb, var(--ep-color-danger) 80%, transparent); +} + .timeline-container::-webkit-scrollbar { width: 6px; height: 6px; diff --git a/src/components/hazard_inspect/timeline/timeline.ts b/src/components/hazard_inspect/timeline/timeline.ts index 17b959a..58720fb 100644 --- a/src/components/hazard_inspect/timeline/timeline.ts +++ b/src/components/hazard_inspect/timeline/timeline.ts @@ -1,10 +1,17 @@ +import type { Action } from 'element-plus' import type { ComputedRef, Ref } from 'vue' +import { ElMessage, ElMessageBox } from 'element-plus' import { computed, nextTick, ref, watch } from 'vue' +export interface HazardData { + ranges: number[] + level: number +} + export interface TimelineProps { currentFrame: number totalFrames: number - hazardRanges: Record + hazardRanges: Record } export interface TimelineEmits { @@ -23,14 +30,15 @@ export interface HazardItem { id: string start: number end: number - ranges: number[][] + ranges: number[] } export interface HazardRow { id: string start: number end: number - ranges: number[][] + ranges: number[] + level: number } export function useTimeline( @@ -152,36 +160,44 @@ export function useTimeline( const trackRows: ComputedRef = computed(() => { const ranges = props.hazardRanges const sortedHazards = Object.entries(ranges) - .map(([id, frames]) => ({ - id, - ranges: frames.reduce((acc, _, i) => { - if (i % 2 === 0) { - acc.push([frames[i], frames[i + 1]]) + .map(([id, frames]) => { + // 处理旧数据结构:[start, end, level] + if (Array.isArray(frames)) { + return { + id, + level: frames[2] || 0, // 从第三个元素获取level + ranges: [[frames[0], frames[1]]], // 前两个元素是范围 } - return acc - }, []), - })) + } + // 处理新数据结构:{ ranges: number[][], level: number } + return { + id, + level: frames.level || 0, + ranges: frames.ranges, + } + }) .sort((a, b) => { - const aStart = a.ranges[0]?.[0] || 0 - const bStart = b.ranges[0]?.[0] || 0 - return aStart - bStart + const aStart = a.ranges[0] || 0 + const bStart = b.ranges[0] || 0 + return (aStart as number) - (bStart as number) }) const rows: HazardRow[][] = [] sortedHazards.forEach((hazard) => { let assigned = false for (let i = 0; i < rows.length; i++) { - const canPlace = hazard.ranges.every(([start, end]) => { + const canPlace = hazard.ranges.every((start) => { return rows[i].every((existing) => { - return end <= existing.start || start >= existing.end + return (start as number) <= (existing.start as number) || (start as number) >= (existing.end as number) }) }) if (canPlace) { rows[i].push({ id: hazard.id, - start: hazard.ranges[0][0], - end: hazard.ranges[0][1], - ranges: hazard.ranges, + start: hazard.ranges[0] as number, + end: hazard.ranges[1] as number, + ranges: hazard.ranges.map(range => range as number), + level: hazard.level || 0, }) assigned = true break @@ -190,9 +206,10 @@ export function useTimeline( if (!assigned) { rows.push([{ id: hazard.id, - start: hazard.ranges[0][0], - end: hazard.ranges[0][1], - ranges: hazard.ranges, + start: hazard.ranges[0] as number, + end: hazard.ranges[1] as number, + ranges: hazard.ranges.map(range => range as number), + level: hazard.level || 0, }]) } }) @@ -364,6 +381,20 @@ export function useTimeline( window.removeEventListener('mouseup', handleMouseUp) } + const openMessageBox = (message: string, title: string) => { + ElMessageBox.alert(message, title, { + // if you want to disable its autofocus + // autofocus: false, + confirmButtonText: '确认', + // callback: (action: Action) => { + // ElMessage({ + // type: 'info', + // message: `action: ${action}`, + // }) + // }, + }) + } + return { pxPerFrame, minPxPerFrame, @@ -388,5 +419,6 @@ export function useTimeline( handlePlayheadDrag, initMounted, initUnmounted, + openMessageBox, } } diff --git a/src/components/hazard_inspect/timeline/timeline.vue b/src/components/hazard_inspect/timeline/timeline.vue index 196b630..4df7ba3 100644 --- a/src/components/hazard_inspect/timeline/timeline.vue +++ b/src/components/hazard_inspect/timeline/timeline.vue @@ -1,8 +1,9 @@ - - - - - -
-
-
- - - -
-

- ProVision - | 视频标注编辑器 -

-
-
-
- - 系统就绪 -
- -
-
- - -
- - - - -
- -
- - - - -
- -
-
-
-
- - -
-
- -
- 00:00 / - 00:10 -
-
-
- - - - -
-
-
-
- - -
- -
- -
- 轨道视图 -
- - -
- -
-
-
- - -
- - -
- -
-
-
-
- - - -
- - - - diff --git a/src/pages/nav/hazardCheck/HazardCheckResult.vue b/src/pages/nav/hazardCheck/HazardCheckResult.vue index fc92bfb..aef2f4b 100644 --- a/src/pages/nav/hazardCheck/HazardCheckResult.vue +++ b/src/pages/nav/hazardCheck/HazardCheckResult.vue @@ -8,6 +8,8 @@ interface HazardItem { 隐患编号: string 物体类型: string 隐患名称: string + 隐患等级: string + 置信度: string 时间点: string 跳转时间点: number 隐患描述: string @@ -16,22 +18,24 @@ interface HazardItem { } interface DataFormat { - 隐患列表: string[] - 隐患范围字典: Record + 隐患列表: [number, string, string][] + 隐患范围字典: Record 隐患数据: HazardItem[] } interface ResultObject { - tag_id: number - base_id: number - track_id: string - hazard_track_id: number - class_id: number - level: number - start_frame: number - end_frame: number - start_sec: number - location: string + tag_id: number // 隐患类型id + base_id: number // 依据id + track_id: string // 物体追踪id + hazard_track_id: number // 隐患追踪id + class_id: number // 物体类型id + conf: number // 置信度 0: 疑似 1: 确信 + level: number // 隐患等级 0: 一般隐患 1: 重大隐患 + start_frame: number // 开始帧 + end_frame: number // 结束帧 + start_sec: number // 开始时间点 + location: string // 位置 + recommend: string // 整改建议 } interface ResultData { @@ -127,15 +131,19 @@ function getData() { const { tag, base, objects } = resultData.value data.value.隐患列表 = (objects || []).map((obj: any) => { - return `(${resultData.value?.class_list?.[obj.class_id] || ''}) ${tag?.[obj.tag_id] || ''}` + return [ + obj.level, + resultData.value?.class_list?.[obj.class_id] || '', + tag?.[obj.tag_id] || '', + ] }) data.value.隐患范围字典 = {} ;(objects || []).forEach((obj: any) => { const 编号 = String(obj.hazard_track_id) if (!data.value.隐患范围字典[编号]) - data.value.隐患范围字典[编号] = [] - data.value.隐患范围字典[编号].push(obj.start_frame, obj.end_frame) + data.value.隐患范围字典[编号] = { ranges: [], level: obj.level } + data.value.隐患范围字典[编号].ranges = [obj.start_frame, obj.end_frame] }) // data.value.物体列表 = (objects || []).map((_: any, i: number) => `物体${i + 1}`) @@ -149,11 +157,13 @@ function getData() { 隐患编号: obj.hazard_track_id || '', 物体类型: resultData.value.class_list?.[obj.class_id] || '', 隐患名称: tag?.[obj.tag_id] || '', + 隐患等级: obj.level === 0 ? '一般隐患' : '重大隐患', + 置信度: obj.conf === 0 ? '疑似' : '确信', 时间点: `${hh}:${mm}:${ss}`, 跳转时间点: jumpPoint, 隐患描述: obj.location || '', 依据: base?.[obj.base_id] || '', - 整改建议: '', + 整改建议: obj.recommend || '', } }) } @@ -194,11 +204,13 @@ function handleJumpToTimePoint(index: number) { const hazardFields = [ { label: '隐患编号', key: '隐患编号', group: 1, transform: (val: any) => val !== undefined ? val + 1 : '无' }, { label: '物体类型', key: '物体类型', group: 1 }, - { label: '隐患名称', key: '隐患名称', group: 2 }, - { label: '时间点', key: '时间点', group: 2 }, - { label: '隐患描述', key: '隐患描述', group: 3 }, - { label: '依据', key: '依据', group: 4 }, - { label: '整改建议', key: '整改建议', group: 5 }, + { label: '隐患等级', key: '隐患等级', group: 2 }, + { label: '置信度', key: '置信度', group: 2 }, + { label: '隐患名称', key: '隐患名称', group: 3 }, + { label: '时间点', key: '时间点', group: 3 }, + { label: '隐患描述', key: '隐患描述', group: 4 }, + { label: '依据', key: '依据', group: 5 }, + { label: '整改建议', key: '整改建议', group: 6 }, ] const groupedFields = computed(() => { @@ -271,11 +283,13 @@ onMounted(() => { track_id: '18', hazard_track_id: 0, class_id: 0, - level: 1, + conf: 1, + level: 0, start_frame: 551, end_frame: 581, start_sec: 18.4, location: '画面中央偏右的墙壁上', + recommend: '', }, { tag_id: 1, @@ -283,11 +297,13 @@ onMounted(() => { track_id: '115', hazard_track_id: 1, class_id: 1, - level: 2, + conf: 2, + level: 1, start_frame: 3618, end_frame: 3648, start_sec: 120.6, location: '画面右侧墙壁上的白色插座面板', + recommend: '', }, { tag_id: 2, @@ -295,11 +311,13 @@ onMounted(() => { track_id: '125', hazard_track_id: 2, class_id: 2, + conf: 1, level: 1, start_frame: 3733, end_frame: 3763, start_sec: 124.4, location: '画面中央偏左墙壁上的配电箱正下方紧贴放置有一台不锈钢设备,导致配电箱前1米内被占用。', + recommend: '', }, { tag_id: 1, @@ -307,11 +325,13 @@ onMounted(() => { track_id: '127', hazard_track_id: 3, class_id: 1, + conf: 1, level: 1, start_frame: 3767, end_frame: 3900, start_sec: 125.6, location: '画面左侧墙壁上,操作台上方区域', + recommend: '', }, { tag_id: 1, @@ -319,11 +339,13 @@ onMounted(() => { track_id: '130', hazard_track_id: 4, class_id: 1, + conf: 1, level: 1, start_frame: 3812, end_frame: 3842, start_sec: 127.1, location: '画面中央偏右墙壁上,配电箱下方区域', + recommend: '', }, { tag_id: 2, @@ -331,11 +353,13 @@ onMounted(() => { track_id: '196', hazard_track_id: 5, class_id: 2, + conf: 1, level: 1, start_frame: 5199, end_frame: 5229, start_sec: 173.3, location: '画面中央偏右的蓝色配电箱正下方及周围区域', + recommend: '', }, { tag_id: 3, @@ -343,11 +367,13 @@ onMounted(() => { track_id: '206', hazard_track_id: 6, class_id: 2, + conf: 1, level: 1, start_frame: 5409, end_frame: 5439, start_sec: 180.3, location: '画面左侧墙壁上的灰色配电箱箱门缺失,内部元器件裸露', + recommend: '', }, { tag_id: 4, @@ -355,11 +381,13 @@ onMounted(() => { track_id: '221', hazard_track_id: 7, class_id: 0, + conf: 1, level: 1, start_frame: 5654, end_frame: 5684, start_sec: 188.5, location: '画面右侧的蓝色消火栓箱内部', + recommend: '', }, ], } @@ -418,8 +446,8 @@ onMounted(() => { style="height: 100%;" :current-frame="currentFrame" :total-frames="totalFrames" - :hazard-ranges="data.隐患范围字典" - @hazard-click="handleTimelineHazardClick" + :hazard-ranges="data.隐患范围字典 as Record" + @hazard-click="(id: number) => handleTimelineHazardClick(String(id))" @frame-change="handleFrameChange" /> diff --git a/src/pages/nav/hazardCheck/index.vue b/src/pages/nav/hazardCheck/index.vue index cc3326e..d0921c7 100644 --- a/src/pages/nav/hazardCheck/index.vue +++ b/src/pages/nav/hazardCheck/index.vue @@ -20,7 +20,7 @@ const ruleForm = reactive({ function: [ 'runSam3', 'runHazardCheck', - 'runGenerateReport', + // 'runGenerateReport', // 'runAudioRecognition', ], })