forked from SmartBench/CMVR-IOT-UI
refactor(log): 替换任务日志组件为通用执行日志组件
- 移除 inspection 和 intelligenceTest 模块中的重复日志显示代码 - 引入统一的 TaskExecutionLog 组件处理日志渲染逻辑 - 新组件支持多媒体内容展示包括图片、视频和音频 - 实现媒体资源自动解析和预览功能 - 优化滚动到底部逻辑确保日志实时显示 - 采用响应式布局适配不同屏幕尺寸
This commit is contained in:
parent
e88a81049b
commit
82642db70f
162
src/views/flow/components/TaskExecutionLog.vue
Normal file
162
src/views/flow/components/TaskExecutionLog.vue
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="messageContainer" class="task-log" :style="{ height: `${height}px` }">
|
||||||
|
<div v-for="(log, index) in list" :key="log.id || index" class="task-log__item">
|
||||||
|
<div class="task-log__time">{{ formatDate(log.logTime) }}</div>
|
||||||
|
<div class="task-log__body">
|
||||||
|
<div class="task-log__content">{{ log.logContent }}</div>
|
||||||
|
<div v-if="mediaOf(log).length" class="task-log__media">
|
||||||
|
<template v-for="media in mediaOf(log)" :key="`${media.type}:${media.url}`">
|
||||||
|
<el-image
|
||||||
|
v-if="media.type === 'image'"
|
||||||
|
class="task-log__image"
|
||||||
|
:src="media.url"
|
||||||
|
:preview-src-list="imageUrlsOf(log)"
|
||||||
|
preview-teleported
|
||||||
|
fit="cover"
|
||||||
|
/>
|
||||||
|
<video v-else-if="media.type === 'video'" class="task-log__video" :src="media.url" controls preload="metadata" />
|
||||||
|
<audio v-else class="task-log__audio" :src="media.url" controls preload="metadata" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { nextTick, onMounted, ref, watch } from 'vue'
|
||||||
|
import { formatDate } from '@/utils/index'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
list: { type: Array, default: () => [] },
|
||||||
|
height: { type: Number, default: 150 },
|
||||||
|
})
|
||||||
|
|
||||||
|
const messageContainer = ref(null)
|
||||||
|
const mediaCache = new WeakMap()
|
||||||
|
|
||||||
|
const parseObject = (value) => {
|
||||||
|
if (!value) return {}
|
||||||
|
if (typeof value === 'object') return value
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value)
|
||||||
|
return parsed && typeof parsed === 'object' ? parsed : {}
|
||||||
|
} catch {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addMedia = (result, seen, type, value) => {
|
||||||
|
const values = Array.isArray(value) ? value : [value]
|
||||||
|
values.forEach((item) => {
|
||||||
|
const url = typeof item === 'string' ? item.trim() : ''
|
||||||
|
const key = `${type}:${url}`
|
||||||
|
if (url && !seen.has(key)) {
|
||||||
|
seen.add(key)
|
||||||
|
result.push({ type, url })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const mediaOf = (log) => {
|
||||||
|
if (!log || typeof log !== 'object') return []
|
||||||
|
if (mediaCache.has(log)) return mediaCache.get(log)
|
||||||
|
const result = []
|
||||||
|
const seen = new Set()
|
||||||
|
const output = {
|
||||||
|
...parseObject(log.actualResult),
|
||||||
|
...parseObject(log.extraInfo),
|
||||||
|
}
|
||||||
|
addMedia(result, seen, 'image', output.imageUrl)
|
||||||
|
addMedia(result, seen, 'video', output.videoUrl)
|
||||||
|
addMedia(result, seen, 'audio', output.audioUrl)
|
||||||
|
addMedia(result, seen, 'image', log.screenshotUrl)
|
||||||
|
addMedia(result, seen, 'video', log.videoUrl)
|
||||||
|
const legacyType = Number(log.logType) === 2 ? 'image'
|
||||||
|
: Number(log.logType) === 3 ? 'video'
|
||||||
|
: Number(log.logType) === 4 ? 'audio' : ''
|
||||||
|
if (legacyType) addMedia(result, seen, legacyType, log.mediaUrl)
|
||||||
|
mediaCache.set(log, result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageUrlsOf = (log) => mediaOf(log)
|
||||||
|
.filter((item) => item.type === 'image')
|
||||||
|
.map((item) => item.url)
|
||||||
|
|
||||||
|
const scrollToBottom = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
if (messageContainer.value) messageContainer.value.scrollTop = messageContainer.value.scrollHeight
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.list, scrollToBottom, { deep: true })
|
||||||
|
onMounted(scrollToBottom)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.task-log {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 145px minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
align-items: start;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__time {
|
||||||
|
color: #999;
|
||||||
|
font-size: 13px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__body {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__content {
|
||||||
|
color: #333;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__media {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__image {
|
||||||
|
width: 96px;
|
||||||
|
height: 72px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__video {
|
||||||
|
width: min(320px, 100%);
|
||||||
|
max-height: 220px;
|
||||||
|
background: #111;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__audio {
|
||||||
|
width: min(340px, 100%);
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.task-log__item {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,73 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="log-container" :style="{ 'height': height + 'px' }" ref="messageContainer">
|
<TaskExecutionLog :list="list" :height="height" />
|
||||||
<div class="log" v-for="log, index in list">
|
|
||||||
<div class="time">{{ formatDate(log.logTime) }}</div>
|
|
||||||
<div class="context">{{ log.logContent }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { formatDate } from "@/utils/index";
|
import TaskExecutionLog from '@/views/flow/components/TaskExecutionLog.vue'
|
||||||
import { watch } from "vue";
|
|
||||||
const props = defineProps({
|
defineProps({
|
||||||
list: {
|
list: { type: Array, default: () => [] },
|
||||||
type: Array,
|
height: { type: Number, default: 150 },
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: Number,
|
|
||||||
default: 150
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const messageContainer = ref(null)
|
|
||||||
|
|
||||||
const SCROLL_DELAY = 100
|
|
||||||
const scrollToBottom = () => {
|
|
||||||
console.log(123, messageContainer.value)
|
|
||||||
nextTick(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (messageContainer.value) {
|
|
||||||
console.log('messageContainer.value.scrollHeight', messageContainer.value.scrollHeight)
|
|
||||||
messageContainer.value.scrollTop = messageContainer.value.scrollHeight
|
|
||||||
}
|
|
||||||
}, SCROLL_DELAY)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => props.list, () => {
|
|
||||||
scrollToBottom()
|
|
||||||
}, { deep: true })
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
scrollToBottom()
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
|
||||||
.log-container {
|
|
||||||
margin-top: 12px;
|
|
||||||
overflow-y: auto;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
|
|
||||||
.log {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
|
|
||||||
.time {
|
|
||||||
color: #999;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context {
|
|
||||||
color: #333;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,71 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="log-container" :style="{ 'height': height + 'px' }" ref="messageContainer">
|
<TaskExecutionLog :list="list" :height="height" />
|
||||||
<div class="log" v-for="log, index in list">
|
|
||||||
<div class="time">{{ formatDate(log.logTime) }}</div>
|
|
||||||
<div class="context">{{ log.logContent }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { formatDate } from "@/utils/index";
|
import TaskExecutionLog from '@/views/flow/components/TaskExecutionLog.vue'
|
||||||
import { watch } from "vue";
|
|
||||||
const props = defineProps({
|
defineProps({
|
||||||
list: {
|
list: { type: Array, default: () => [] },
|
||||||
type: Array,
|
height: { type: Number, default: 150 },
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: Number,
|
|
||||||
default: 150
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const messageContainer = ref(null)
|
|
||||||
|
|
||||||
const SCROLL_DELAY = 100
|
|
||||||
const scrollToBottom = () => {
|
|
||||||
nextTick(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (messageContainer.value) {
|
|
||||||
messageContainer.value.scrollTop = messageContainer.value.scrollHeight
|
|
||||||
}
|
|
||||||
}, SCROLL_DELAY)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => props.list, () => {
|
|
||||||
scrollToBottom()
|
|
||||||
}, { deep: true })
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
scrollToBottom()
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
|
||||||
.log-container {
|
|
||||||
margin-top: 12px;
|
|
||||||
overflow-y: auto;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
|
|
||||||
.log {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
|
|
||||||
.time {
|
|
||||||
color: #999;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context {
|
|
||||||
color: #333;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Loading…
Reference in New Issue
Block a user