parent
ec6508c262
commit
ee208d5b16
|
|
@ -37,6 +37,7 @@ declare module 'vue' {
|
||||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||||
ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
|
ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
|
||||||
ElOption: typeof import('element-plus/es')['ElOption']
|
ElOption: typeof import('element-plus/es')['ElOption']
|
||||||
|
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||||
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']
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
import type { Action } from 'element-plus'
|
|
||||||
import type { ComputedRef, Ref } from 'vue'
|
import type { ComputedRef, Ref } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessageBox } from 'element-plus'
|
||||||
import { computed, nextTick, ref, watch } from 'vue'
|
import { computed, nextTick, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
function debounce<T extends (...args: any[]) => void>(fn: T, delay: number): T {
|
||||||
|
let timeoutId: ReturnType<typeof setTimeout> | null = null
|
||||||
|
return ((...args: any[]) => {
|
||||||
|
if (timeoutId)
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
timeoutId = setTimeout(() => fn(...args), delay)
|
||||||
|
}) as T
|
||||||
|
}
|
||||||
|
|
||||||
export interface HazardData {
|
export interface HazardData {
|
||||||
ranges: number[]
|
ranges: number[]
|
||||||
level: number
|
level: number
|
||||||
|
|
@ -48,13 +56,14 @@ export function useTimeline(
|
||||||
emit: TimelineEmits,
|
emit: TimelineEmits,
|
||||||
) {
|
) {
|
||||||
const FPS = 30
|
const FPS = 30
|
||||||
const pxPerFrame = ref(2)
|
const pxPerFrame = ref(1)
|
||||||
const minPxPerFrame = ref(0.5)
|
const minPxPerFrame = ref(0.5)
|
||||||
const maxPxPerFrame = ref(3)
|
const maxPxPerFrame = ref(3)
|
||||||
const timelineContainer: Ref<HTMLElement | null> = ref(null)
|
const timelineContainer: Ref<HTMLElement | null> = ref(null)
|
||||||
const isDragging = ref(false)
|
const isDragging = ref(false)
|
||||||
const isPlayheadDragging = ref(false)
|
const isPlayheadDragging = ref(false)
|
||||||
const isRulerDragging = ref(false)
|
const isRulerDragging = ref(false)
|
||||||
|
const isMounted = ref(false)
|
||||||
const dragStartX = ref(0)
|
const dragStartX = ref(0)
|
||||||
const scrollStartLeft = ref(0)
|
const scrollStartLeft = ref(0)
|
||||||
const localCurrentFrame = ref(0)
|
const localCurrentFrame = ref(0)
|
||||||
|
|
@ -67,19 +76,27 @@ export function useTimeline(
|
||||||
return Math.ceil(props.totalFrames / FPS)
|
return Math.ceil(props.totalFrames / FPS)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新每帧像素值,根据容器宽度自动计算合适的缩放比例
|
||||||
|
* 确保时间线能够完整显示所有帧,同时设置合理的缩放范围
|
||||||
|
*/
|
||||||
function updatePxPerFrame(): void {
|
function updatePxPerFrame(): void {
|
||||||
if (!timelineContainer.value || props.totalFrames === 0)
|
if (!timelineContainer.value || props.totalFrames === 0)
|
||||||
return
|
return
|
||||||
const containerWidth = timelineContainer.value.clientWidth - 20
|
|
||||||
const calculatedPxPerFrame = containerWidth / props.totalFrames
|
const container = timelineContainer.value
|
||||||
// 计算最小缩放倍数,确保能看到视频全长
|
if (!container.isConnected)
|
||||||
|
return
|
||||||
|
|
||||||
|
const containerWidth = container.clientWidth - 20
|
||||||
|
if (containerWidth <= 0)
|
||||||
|
return
|
||||||
|
|
||||||
const newMinPxPerFrame = Math.max(0.1, containerWidth / props.totalFrames)
|
const newMinPxPerFrame = Math.max(0.1, containerWidth / props.totalFrames)
|
||||||
// 只在必要时更新,避免无限循环
|
|
||||||
if (Math.abs(newMinPxPerFrame - minPxPerFrame.value) > 0.01) {
|
if (Math.abs(newMinPxPerFrame - minPxPerFrame.value) > 0.01) {
|
||||||
minPxPerFrame.value = newMinPxPerFrame
|
minPxPerFrame.value = newMinPxPerFrame
|
||||||
}
|
}
|
||||||
// 最大值固定为3,不修改
|
|
||||||
pxPerFrame.value = Math.max(minPxPerFrame.value, Math.min(calculatedPxPerFrame, maxPxPerFrame.value))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setZoom(value: number): void {
|
function setZoom(value: number): void {
|
||||||
|
|
@ -336,16 +353,17 @@ export function useTimeline(
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件挂载时的初始化函数
|
||||||
|
* 设置时间线容器的初始状态并绑定事件监听器
|
||||||
|
*/
|
||||||
function initMounted(): void {
|
function initMounted(): void {
|
||||||
|
isMounted.value = true
|
||||||
|
|
||||||
const progressContainer = timelineContainer.value
|
const progressContainer = timelineContainer.value
|
||||||
if (!progressContainer)
|
if (!progressContainer)
|
||||||
return
|
return
|
||||||
|
|
||||||
// 使用 nextTick 确保容器已渲染
|
|
||||||
nextTick(() => {
|
|
||||||
updatePxPerFrame()
|
|
||||||
})
|
|
||||||
|
|
||||||
progressContainer.addEventListener('wheel', handleWheel, { passive: false })
|
progressContainer.addEventListener('wheel', handleWheel, { passive: false })
|
||||||
progressContainer.addEventListener('mousedown', handleMouseDown)
|
progressContainer.addEventListener('mousedown', handleMouseDown)
|
||||||
window.addEventListener('mousemove', handleMouseMove)
|
window.addEventListener('mousemove', handleMouseMove)
|
||||||
|
|
@ -353,34 +371,18 @@ export function useTimeline(
|
||||||
window.addEventListener('mouseup', handleMouseUp)
|
window.addEventListener('mouseup', handleMouseUp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const debouncedUpdatePxPerFrame = debounce(() => {
|
||||||
|
updatePxPerFrame()
|
||||||
|
}, 150)
|
||||||
|
|
||||||
watch(() => props.totalFrames, () => {
|
watch(() => props.totalFrames, () => {
|
||||||
// 使用 setTimeout 避免频繁更新
|
if (!isMounted.value)
|
||||||
setTimeout(() => {
|
return
|
||||||
updatePxPerFrame()
|
debouncedUpdatePxPerFrame()
|
||||||
}, 100)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 移除 resize 监听,避免无限循环
|
|
||||||
// 监听容器宽度变化
|
|
||||||
// let resizeObserver: ResizeObserver | null = null
|
|
||||||
|
|
||||||
// function handleResize(): void {
|
|
||||||
// updatePxPerFrame()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// watch(() => timelineContainer.value, (newVal, oldVal) => {
|
|
||||||
// if (newVal && !oldVal) {
|
|
||||||
// // 使用 ResizeObserver 代替 window resize 监听
|
|
||||||
// resizeObserver = new ResizeObserver(handleResize)
|
|
||||||
// resizeObserver.observe(newVal)
|
|
||||||
// }
|
|
||||||
// else if (!newVal && oldVal && resizeObserver) {
|
|
||||||
// resizeObserver.disconnect()
|
|
||||||
// resizeObserver = null
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
function initUnmounted(): void {
|
function initUnmounted(): void {
|
||||||
|
isMounted.value = false
|
||||||
window.removeEventListener('mousemove', handleMouseMove)
|
window.removeEventListener('mousemove', handleMouseMove)
|
||||||
window.removeEventListener('mousemove', handlePlayheadDrag)
|
window.removeEventListener('mousemove', handlePlayheadDrag)
|
||||||
window.removeEventListener('mouseup', handleMouseUp)
|
window.removeEventListener('mouseup', handleMouseUp)
|
||||||
|
|
@ -391,12 +393,6 @@ export function useTimeline(
|
||||||
// if you want to disable its autofocus
|
// if you want to disable its autofocus
|
||||||
// autofocus: false,
|
// autofocus: false,
|
||||||
confirmButtonText: '确认',
|
confirmButtonText: '确认',
|
||||||
// callback: (action: Action) => {
|
|
||||||
// ElMessage({
|
|
||||||
// type: 'info',
|
|
||||||
// message: `action: ${action}`,
|
|
||||||
// })
|
|
||||||
// },
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -138,21 +138,6 @@ onUnmounted(() => {
|
||||||
</div>
|
</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div
|
|
||||||
v-for="hazard in row"
|
|
||||||
:key="hazard.id"
|
|
||||||
>
|
|
||||||
<el-tooltip :content="hazard.tip" placement="top">
|
|
||||||
<div
|
|
||||||
class="hazard-block"
|
|
||||||
:class="{ primary: hazard.level === 0, danger: hazard.level === 1 }"
|
|
||||||
:style="getHazardStyle(hazard.start, hazard.end)"
|
|
||||||
@click="handleHazardClick(hazard.id)"
|
|
||||||
>
|
|
||||||
{{ parseInt(hazard.id) + 1 }}
|
|
||||||
</div>
|
|
||||||
</el-tooltip>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ElScrollbar>
|
</ElScrollbar>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,28 @@ const ruleForm = reactive<RuleForm>({
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const timerDisplay = ref('00:00:00')
|
||||||
|
let timerInterval: ReturnType<typeof setInterval> | null = null
|
||||||
|
let startTime: number = 0
|
||||||
|
|
||||||
|
function startTimer() {
|
||||||
|
startTime = Date.now()
|
||||||
|
timerInterval = setInterval(() => {
|
||||||
|
const elapsed = Date.now() - startTime
|
||||||
|
const hours = Math.floor(elapsed / 3600000)
|
||||||
|
const minutes = Math.floor((elapsed % 3600000) / 60000)
|
||||||
|
const seconds = Math.floor((elapsed % 60000) / 1000)
|
||||||
|
timerDisplay.value = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopTimer() {
|
||||||
|
if (timerInterval) {
|
||||||
|
clearInterval(timerInterval)
|
||||||
|
timerInterval = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const rules = reactive<FormRules<RuleForm>>({
|
const rules = reactive<FormRules<RuleForm>>({
|
||||||
vidPath: [
|
vidPath: [
|
||||||
{
|
{
|
||||||
|
|
@ -50,6 +72,7 @@ async function runCheck(formEl: FormInstance | undefined) {
|
||||||
})
|
})
|
||||||
if (valid) {
|
if (valid) {
|
||||||
isRunningCheck.value = true
|
isRunningCheck.value = true
|
||||||
|
startTimer()
|
||||||
await runApi('/run', {
|
await runApi('/run', {
|
||||||
vid_file: vidPaths.value[vidPaths.value.indexOf(ruleForm.vidPath)],
|
vid_file: vidPaths.value[vidPaths.value.indexOf(ruleForm.vidPath)],
|
||||||
run_sam3: ruleForm.function.includes('runSam3'),
|
run_sam3: ruleForm.function.includes('runSam3'),
|
||||||
|
|
@ -57,6 +80,7 @@ async function runCheck(formEl: FormInstance | undefined) {
|
||||||
gen_report: ruleForm.function.includes('runGenerateReport'),
|
gen_report: ruleForm.function.includes('runGenerateReport'),
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
isRunningCheck.value = false
|
isRunningCheck.value = false
|
||||||
|
stopTimer()
|
||||||
// 判定是否成功运行
|
// 判定是否成功运行
|
||||||
if (res !== 'error') {
|
if (res !== 'error') {
|
||||||
router.push({
|
router.push({
|
||||||
|
|
@ -109,51 +133,61 @@ onMounted(() => {
|
||||||
<el-container style="margin: 0; height: 100%; flex-direction: column;">
|
<el-container style="margin: 0; height: 100%; flex-direction: column;">
|
||||||
<!-- 内层容器:自动填充剩余高度 -->
|
<!-- 内层容器:自动填充剩余高度 -->
|
||||||
<el-main class="main" style="flex: 1; min-height: 0;">
|
<el-main class="main" style="flex: 1; min-height: 0;">
|
||||||
<el-form
|
<div class="flex flex-col items-center justify-center">
|
||||||
ref="ruleFormRef"
|
<el-form
|
||||||
style="max-width: 600px"
|
ref="ruleFormRef"
|
||||||
:model="ruleForm"
|
style="max-width: 600px"
|
||||||
:rules="rules"
|
:model="ruleForm"
|
||||||
label-width="auto"
|
:rules="rules"
|
||||||
>
|
label-width="auto"
|
||||||
<el-form-item label="视频" prop="vidPath">
|
>
|
||||||
<el-select v-model="ruleForm.vidPath" placeholder="视频">
|
<el-form-item label="视频" prop="vidPath">
|
||||||
<el-option v-for="item in vidPaths" :key="item" :label="item" :value="item" />
|
<el-select v-model="ruleForm.vidPath" placeholder="视频">
|
||||||
</el-select>
|
<el-option v-for="item in vidPaths" :key="item" :label="item" :value="item" />
|
||||||
</el-form-item>
|
</el-select>
|
||||||
<el-form-item label="功能选择" prop="function">
|
</el-form-item>
|
||||||
<el-checkbox-group v-model="ruleForm.function" class="checkbox-group">
|
<el-form-item label="功能选择" prop="function">
|
||||||
<el-checkbox value="runSam3" name="function">
|
<el-checkbox-group v-model="ruleForm.function" class="checkbox-group">
|
||||||
物体识别
|
<el-checkbox value="runSam3" name="function">
|
||||||
</el-checkbox>
|
物体识别
|
||||||
<el-checkbox value="runHazardCheck" name="function">
|
</el-checkbox>
|
||||||
隐患检查
|
<el-checkbox value="runHazardCheck" name="function">
|
||||||
</el-checkbox>
|
隐患检查
|
||||||
<el-checkbox value="runGenerateReport" name="function">
|
</el-checkbox>
|
||||||
生成报告
|
<el-checkbox value="runGenerateReport" name="function">
|
||||||
</el-checkbox>
|
生成报告
|
||||||
<el-checkbox value="runAudioRecognition" name="function">
|
</el-checkbox>
|
||||||
音频识别
|
<el-checkbox value="runAudioRecognition" name="function">
|
||||||
</el-checkbox>
|
音频识别
|
||||||
</el-checkbox-group>
|
</el-checkbox>
|
||||||
</el-form-item>
|
</el-checkbox-group>
|
||||||
<el-form-item>
|
</el-form-item>
|
||||||
<el-button @click="resetForm(ruleFormRef)">
|
<el-form-item v-if="!isRunningCheck">
|
||||||
重置表单
|
<el-button @click="resetForm(ruleFormRef)">
|
||||||
</el-button>
|
重置表单
|
||||||
<el-button @click="goToResult(ruleFormRef)">
|
</el-button>
|
||||||
查看结果
|
<el-button @click="goToResult(ruleFormRef)">
|
||||||
</el-button>
|
查看结果
|
||||||
<el-button
|
</el-button>
|
||||||
ref="runCheckBtnRef"
|
<el-button
|
||||||
type="primary"
|
ref="runCheckBtnRef"
|
||||||
:loading="isRunningCheck"
|
type="primary"
|
||||||
@click="runCheck(ruleFormRef)"
|
:loading="isRunningCheck"
|
||||||
>
|
@click="runCheck(ruleFormRef)"
|
||||||
运行检查
|
>
|
||||||
</el-button>
|
运行检查
|
||||||
</el-form-item>
|
</el-button>
|
||||||
</el-form>
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-progress
|
||||||
|
v-if="isRunningCheck"
|
||||||
|
:percentage="50"
|
||||||
|
:indeterminate="true"
|
||||||
|
style="width: 100%;"
|
||||||
|
>
|
||||||
|
<el-text>{{ timerDisplay }}</el-text>
|
||||||
|
</el-progress>
|
||||||
|
</div>
|
||||||
</el-main>
|
</el-main>
|
||||||
</el-container>
|
</el-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue