新增:对话列表新增关键词高亮
- 新增:对话列表新增关键词高亮 - 新增:对话列表中含有关键词的对话使用淡红色背景 - 修改:优化次级信息配色 - 修改:优化隐患结果页右侧边栏滚动条效果 - 修改:优化隐患结果页左侧边栏滚动条效果 - 修改:视频背景改为黑色
This commit is contained in:
parent
14bc8ffbf7
commit
b477db4bd2
|
|
@ -0,0 +1,2 @@
|
||||||
|
拍一下,
|
||||||
|
现场整改,
|
||||||
|
|
|
@ -41,6 +41,7 @@ declare module 'vue' {
|
||||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||||
ElRow: typeof import('element-plus/es')['ElRow']
|
ElRow: typeof import('element-plus/es')['ElRow']
|
||||||
|
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||||
ElSegmented: typeof import('element-plus/es')['ElSegmented']
|
ElSegmented: typeof import('element-plus/es')['ElSegmented']
|
||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
ElSelectV2: typeof import('element-plus/es')['ElSelectV2']
|
ElSelectV2: typeof import('element-plus/es')['ElSelectV2']
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CaretRight, Location } from '@element-plus/icons-vue'
|
import { CaretRight, Location } from '@element-plus/icons-vue'
|
||||||
import { ref, shallowRef, watch } from 'vue'
|
import { computed, ref, shallowRef, watch } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
title: {
|
title: {
|
||||||
|
|
@ -15,6 +15,10 @@ const props = defineProps({
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
keywords: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['click', 'play'])
|
const emit = defineEmits(['click', 'play'])
|
||||||
|
|
@ -28,6 +32,22 @@ function handlePlayClick(e: Event, item: any[], index: number) {
|
||||||
emit('play', item, index)
|
emit('play', item, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const keywordList = computed(() => props.keywords as string[])
|
||||||
|
|
||||||
|
function highlightText(text: string): string {
|
||||||
|
const kws = keywordList.value
|
||||||
|
if (!kws.length || !text)
|
||||||
|
return text
|
||||||
|
let result = text
|
||||||
|
for (const kw of kws) {
|
||||||
|
if (!kw)
|
||||||
|
continue
|
||||||
|
const regex = new RegExp(`(${kw})`, 'gi')
|
||||||
|
result = result.replace(regex, '<span class="keyword-highlight">$1</span>')
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
const dataCache = shallowRef<[number, string, string, string][]>([])
|
const dataCache = shallowRef<[number, string, string, string][]>([])
|
||||||
const indexMap = ref(new Map<number, number[]>())
|
const indexMap = ref(new Map<number, number[]>())
|
||||||
const sortedTimes = ref<number[]>([])
|
const sortedTimes = ref<number[]>([])
|
||||||
|
|
@ -37,6 +57,7 @@ watch(() => props.data, (newData) => {
|
||||||
dataCache.value = arr
|
dataCache.value = arr
|
||||||
const map = new Map<number, number[]>()
|
const map = new Map<number, number[]>()
|
||||||
const times: number[] = []
|
const times: number[] = []
|
||||||
|
|
||||||
for (let i = 0; i < arr.length; i++) {
|
for (let i = 0; i < arr.length; i++) {
|
||||||
const t = (arr[i][0] * 10) | 0
|
const t = (arr[i][0] * 10) | 0
|
||||||
if (!map.has(t)) {
|
if (!map.has(t)) {
|
||||||
|
|
@ -51,6 +72,30 @@ watch(() => props.data, (newData) => {
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
const currentHighlight = ref(-1)
|
const currentHighlight = ref(-1)
|
||||||
|
const keywordItemSet = ref(new Set<number>())
|
||||||
|
|
||||||
|
watch(keywordList, () => {
|
||||||
|
const arr = dataCache.value
|
||||||
|
const kws = keywordList.value
|
||||||
|
keywordItemSet.value = new Set<number>()
|
||||||
|
|
||||||
|
if (!kws.length || !arr.length)
|
||||||
|
return
|
||||||
|
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
if (arr[i][3]) {
|
||||||
|
for (const kw of kws) {
|
||||||
|
if (!kw)
|
||||||
|
continue
|
||||||
|
const regex = new RegExp(kw, 'i')
|
||||||
|
if (regex.test(arr[i][3])) {
|
||||||
|
keywordItemSet.value.add(i)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
watch(() => props.currentTime, (t) => {
|
watch(() => props.currentTime, (t) => {
|
||||||
const time = (t * 10) | 0
|
const time = (t * 10) | 0
|
||||||
|
|
@ -85,20 +130,24 @@ watch(() => props.currentTime, (t) => {
|
||||||
<!-- 滚动列表区域 -->
|
<!-- 滚动列表区域 -->
|
||||||
<el-row style="flex: 1; overflow-y: auto;">
|
<el-row style="flex: 1; overflow-y: auto;">
|
||||||
<el-col>
|
<el-col>
|
||||||
<el-row v-for="(item, index) in props.data" :key="index" class="message-item" :class="{ highlighted: currentHighlight === index }">
|
<el-row v-for="(item, index) in props.data" :key="index" class="message-item" :class="{ 'highlighted': currentHighlight === index, 'keyword-highlighted': keywordItemSet.has(index) }">
|
||||||
<div class="message-content" @click="handleItemClick(item as any[], index)">
|
<div class="message-content" @click="handleItemClick(item as any[], index)">
|
||||||
<div class="flex flex-col items-start">
|
<div class="flex flex-col items-start">
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-2">
|
||||||
<el-text class="item-text" type="info" size="small">
|
<el-text class="item-text info-text" type="info" size="small">
|
||||||
{{ (item as any[])[1] }}
|
{{ (item as any[])[1] }}
|
||||||
</el-text>
|
</el-text>
|
||||||
<el-text class="item-text" type="info" size="small">
|
<el-text class="item-text info-text" type="info" size="small">
|
||||||
{{ (item as any[])[2] }}
|
{{ (item as any[])[2] }}
|
||||||
</el-text>
|
</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-text class="item-text">
|
<div class="item-text">
|
||||||
{{ (item as any[])[3] }}
|
<el-text>
|
||||||
</el-text>
|
<template #default>
|
||||||
|
<span v-html="highlightText((item as any[])[3])" />
|
||||||
|
</template>
|
||||||
|
</el-text>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hover-actions">
|
<div class="hover-actions">
|
||||||
|
|
@ -150,6 +199,10 @@ watch(() => props.currentTime, (t) => {
|
||||||
background-color: var(--ep-color-primary-light-8) !important;
|
background-color: var(--ep-color-primary-light-8) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-item.keyword-highlighted {
|
||||||
|
background-color: var(--ep-color-danger-light-9);
|
||||||
|
}
|
||||||
|
|
||||||
.message-content {
|
.message-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
@ -171,31 +224,16 @@ watch(() => props.currentTime, (t) => {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-btn {
|
:deep(.keyword-highlight) {
|
||||||
width: 100%;
|
color: #f56c6c;
|
||||||
padding: 4px 0.75rem;
|
font-weight: bold;
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
height: auto;
|
|
||||||
min-height: 28px;
|
|
||||||
line-height: normal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-text {
|
.item-text {
|
||||||
word-break: break-word;
|
|
||||||
white-space: normal;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-title {
|
.info-text {
|
||||||
display: flex;
|
color: var(--ep-color-info-light-3) !important;
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-text.ep-text {
|
|
||||||
align-self: start;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ const resultData = ref<ResultData>({
|
||||||
})
|
})
|
||||||
|
|
||||||
const videoRef = ref<HTMLVideoElement>()
|
const videoRef = ref<HTMLVideoElement>()
|
||||||
|
const keywords = ref<string[]>([])
|
||||||
const data = ref<DataFormat>({
|
const data = ref<DataFormat>({
|
||||||
隐患列表: [],
|
隐患列表: [],
|
||||||
隐患范围字典: {},
|
隐患范围字典: {},
|
||||||
|
|
@ -322,7 +323,16 @@ function getFieldValue(field: typeof hazardFields[0]) {
|
||||||
return field.transform ? field.transform(value) : (value || '无')
|
return field.transform ? field.transform(value) : (value || '无')
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/data/keyword.csv')
|
||||||
|
const text = await res.text()
|
||||||
|
keywords.value = text.split('\n').map(k => k.trim().replace(/,$/, '')).filter(k => k)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error('Failed to load keywords:', e)
|
||||||
|
}
|
||||||
|
|
||||||
// 从路由参数中获取数据
|
// 从路由参数中获取数据
|
||||||
const vidFile = router.currentRoute.value.query.vid_file as string
|
const vidFile = router.currentRoute.value.query.vid_file as string
|
||||||
if (vidFile) {
|
if (vidFile) {
|
||||||
|
|
@ -394,9 +404,11 @@ onMounted(() => {
|
||||||
<el-container style="flex: 1; min-height: 0;">
|
<el-container style="flex: 1; min-height: 0;">
|
||||||
<el-aside width="200px" style="border-right: 1px solid var(--ep-border-color);">
|
<el-aside width="200px" style="border-right: 1px solid var(--ep-border-color);">
|
||||||
<!-- <el-row class="flex flex-col" style="border-bottom: 1px solid var(--ep-border-color);"> -->
|
<!-- <el-row class="flex flex-col" style="border-bottom: 1px solid var(--ep-border-color);"> -->
|
||||||
<el-row class="flex flex-col">
|
<el-scrollbar>
|
||||||
<ItemList title="隐患" :data="data.隐患列表" @click="handleHazardClick" />
|
<el-row class="flex flex-col">
|
||||||
</el-row>
|
<ItemList title="隐患" :data="data.隐患列表" @click="handleHazardClick" />
|
||||||
|
</el-row>
|
||||||
|
</el-scrollbar>
|
||||||
<!-- <el-row style="flex: 1; overflow-y: auto;">
|
<!-- <el-row style="flex: 1; overflow-y: auto;">
|
||||||
<ItemList title="物体列表" :data="data.物体列表" />
|
<ItemList title="物体列表" :data="data.物体列表" />
|
||||||
</el-row> -->
|
</el-row> -->
|
||||||
|
|
@ -427,57 +439,60 @@ onMounted(() => {
|
||||||
</el-container>
|
</el-container>
|
||||||
<el-aside style="width: 300px;">
|
<el-aside style="width: 300px;">
|
||||||
<!-- <el-row style="border-bottom: 1px solid var(--ep-border-color);"> -->
|
<!-- <el-row style="border-bottom: 1px solid var(--ep-border-color);"> -->
|
||||||
<el-row class="px-3 py-2">
|
<el-scrollbar>
|
||||||
<el-col v-if="selectedHazard >= 0">
|
<el-row class="px-3 py-2">
|
||||||
<template v-for="group in groupedFields" :key="group[0].group">
|
<el-col v-if="selectedHazard >= 0">
|
||||||
<el-row :gutter="12">
|
<template v-for="group in groupedFields" :key="group[0].group">
|
||||||
<el-col v-for="field in group" :key="field.key" :span="24 / group.length">
|
<el-row :gutter="12">
|
||||||
<el-row class="result-title">
|
<el-col v-for="field in group" :key="field.key" :span="24 / group.length">
|
||||||
<el-text type="info" size="small">
|
<el-row class="result-title">
|
||||||
{{ field.label }}
|
<el-text type="info" size="small">
|
||||||
</el-text>
|
{{ field.label }}
|
||||||
</el-row>
|
</el-text>
|
||||||
<el-row class="result-content">
|
</el-row>
|
||||||
<el-text v-if="field.key === '隐患等级' && getFieldValue(field) === '重大隐患'" type="danger">
|
<el-row class="result-content">
|
||||||
{{ getFieldValue(field) }}
|
<el-text v-if="field.key === '隐患等级' && getFieldValue(field) === '重大隐患'" type="danger">
|
||||||
</el-text>
|
{{ getFieldValue(field) }}
|
||||||
<el-text v-else-if="field.key === '置信度' && getFieldValue(field) === '疑似'" type="warning">
|
</el-text>
|
||||||
{{ getFieldValue(field) }}
|
<el-text v-else-if="field.key === '置信度' && getFieldValue(field) === '疑似'" type="warning">
|
||||||
</el-text>
|
{{ getFieldValue(field) }}
|
||||||
<el-text v-else>
|
</el-text>
|
||||||
{{ getFieldValue(field) }}
|
<el-text v-else>
|
||||||
</el-text>
|
{{ getFieldValue(field) }}
|
||||||
</el-row>
|
</el-text>
|
||||||
</el-col>
|
</el-row>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<div class="py-1" />
|
||||||
|
</template>
|
||||||
|
<el-row>
|
||||||
|
<el-button style="width: 100%;" @click="handleJumpToHazard(selectedHazard)">
|
||||||
|
跳转到隐患时间点
|
||||||
|
</el-button>
|
||||||
</el-row>
|
</el-row>
|
||||||
<div class="py-1" />
|
</el-col>
|
||||||
</template>
|
<el-col v-else>
|
||||||
<el-row>
|
<el-row class="px-1 py-1">
|
||||||
<el-button style="width: 100%;" @click="handleJumpToHazard(selectedHazard)">
|
无选中隐患
|
||||||
跳转到隐患时间点
|
</el-row>
|
||||||
</el-button>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-col>
|
<el-row v-if="data.对话列表.length > 0" style="height: calc(100% - 400px); flex: auto; border-top: 1px solid var(--ep-border-color);">
|
||||||
<el-col v-else>
|
<SubtitleList
|
||||||
<el-row class="px-1 py-1">
|
title="对话"
|
||||||
无选中隐患
|
:data="data.对话列表"
|
||||||
</el-row>
|
:current-time="videoCurrentTime"
|
||||||
</el-col>
|
:keywords="keywords"
|
||||||
</el-row>
|
@click="(item: any[]) => handleJumpToTimePoint(Number(item[0]))"
|
||||||
<el-row v-if="data.对话列表.length > 0" style="flex: 1; overflow-y: auto; border-top: 1px solid var(--ep-border-color);">
|
@play="(item: any[]) => handlePlayAndSeek(item)"
|
||||||
<SubtitleList
|
/>
|
||||||
title="对话"
|
</el-row>
|
||||||
:data="data.对话列表"
|
|
||||||
:current-time="videoCurrentTime"
|
|
||||||
@click="(item: any[]) => handleJumpToTimePoint(Number(item[0]))"
|
|
||||||
@play="(item: any[]) => handlePlayAndSeek(item)"
|
|
||||||
/>
|
|
||||||
</el-row>
|
|
||||||
<!-- <el-row class="px-1 py-1">
|
<!-- <el-row class="px-1 py-1">
|
||||||
<el-button>
|
<el-button>
|
||||||
查看报告
|
查看报告
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-row> -->
|
</el-row> -->
|
||||||
|
</el-scrollbar>
|
||||||
</el-aside>
|
</el-aside>
|
||||||
</el-container>
|
</el-container>
|
||||||
</el-container>
|
</el-container>
|
||||||
|
|
@ -488,6 +503,7 @@ onMounted(() => {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
background-color: var(--ep-color-black);
|
||||||
}
|
}
|
||||||
.vid_box video {
|
.vid_box video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue