优化路由结构,删除多余页面

This commit is contained in:
yueliuli 2026-04-23 17:13:02 +08:00
parent d8a8b1c94f
commit 659c462d91
6 changed files with 173 additions and 607 deletions

View File

@ -1,17 +1,174 @@
<script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus'
import { onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { runApi } from '~/composables/api'
const router = useRouter()
const runCheckBtnRef = ref()
const isRunningCheck = ref(false)
const vidPaths = ref<string[]>([])
interface RuleForm {
vidPath: string
function: string[]
}
const ruleFormRef = ref<FormInstance>()
const ruleForm = reactive<RuleForm>({
vidPath: '',
function: [
// 'runSam3',
// 'runHazardCheck',
// 'runGenerateReport',
// 'runAudioRecognition',
],
})
const rules = reactive<FormRules<RuleForm>>({
vidPath: [
{
type: 'string',
required: true,
message: '请选择视频',
trigger: 'change',
},
],
})
async function runCheck(formEl: FormInstance | undefined) {
if (!formEl)
return
const valid = await formEl.validate((valid, fields) => {
if (valid) {
// console.log('submit!', ruleForm)
}
else {
console.error('error submit!', fields)
}
})
if (valid) {
isRunningCheck.value = true
await runApi('/run', {
vid_file: vidPaths.value[vidPaths.value.indexOf(ruleForm.vidPath)],
run_sam3: ruleForm.function.includes('runSam3'),
run_inspection: ruleForm.function.includes('runHazardCheck'),
gen_report: ruleForm.function.includes('runGenerateReport'),
}).then((res) => {
isRunningCheck.value = false
//
if (res !== 'error') {
router.push({
path: '/nav/hazardCheckResult',
query: { vid_file: vidPaths.value[vidPaths.value.indexOf(ruleForm.vidPath)] },
})
}
})
}
}
async function reloadFiles() {
await runApi('/reload_files', {}).then((res) => {
for (const item of (res as any)[0].choices) {
vidPaths.value.push(item[0])
}
})
}
function resetForm(formEl: FormInstance | undefined) {
if (!formEl)
return
formEl.resetFields()
}
async function goToResult(formEl: FormInstance | undefined) {
if (!formEl)
return
await formEl.validate((valid, fields) => {
if (valid) {
// console.log('')
router.push({
path: '/nav/hazardCheckResult',
query: { vid_file: 'santai5.mp4' },
})
}
else {
console.error('error submit!', fields)
}
})
}
onMounted(() => {
reloadFiles()
})
</script>
<template> <template>
<!-- <Logos my="4" /> <!-- 最外层容器占满整个视口 -->
<HelloWorld msg="Hello Vue 3 + Element Plus + Vite" /> --> <el-container style="margin: 0; height: 100%; flex-direction: column;">
<div class="container mx-auto"> <!-- 内层容器自动填充剩余高度 -->
<div class="py-8 text-center text-4xl font-bold"> <el-main class="main" style="flex: 1; min-height: 0;">
安责险隐患检查 <el-form
</div> ref="ruleFormRef"
<div class="grid grid-cols-3 gap-4"> style="max-width: 600px"
<el-button class="bg-gray-200 p-4" @click="$router.push('/nav/hazardCheck')"> :model="ruleForm"
本地视频隐患检查 :rules="rules"
label-width="auto"
>
<el-form-item label="视频" prop="vidPath">
<el-select v-model="ruleForm.vidPath" placeholder="视频">
<el-option v-for="item in vidPaths" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="功能选择" prop="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="runGenerateReport" name="function">
生成报告
</el-checkbox>
<el-checkbox value="runAudioRecognition" name="function">
音频识别
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item>
<el-button @click="resetForm(ruleFormRef)">
重置表单
</el-button> </el-button>
<el-button class="bg-gray-200 p-4" @click="$router.push('/nav/VideoTrackDemo')"> <el-button @click="goToResult(ruleFormRef)">
物体跟踪 demo 查看结果
</el-button> </el-button>
</div> <el-button
</div> ref="runCheckBtnRef"
type="primary"
:loading="isRunningCheck"
@click="runCheck(ruleFormRef)"
>
运行检查
</el-button>
</el-form-item>
</el-form>
</el-main>
</el-container>
</template> </template>
<style scoped lang="scss">
.main {
display: flex;
justify-content: center;
align-items: start;
}
.checkbox-group {
display: flex;
flex-wrap: wrap;
gap: 16px;
justify-content: flex-start;
}
</style>

View File

@ -312,9 +312,6 @@ onMounted(() => {
<el-breadcrumb-item :to="{ path: '/' }"> <el-breadcrumb-item :to="{ path: '/' }">
主页 主页
</el-breadcrumb-item> </el-breadcrumb-item>
<el-breadcrumb-item :to="{ path: '/nav/hazardCheck/' }">
隐患检查
</el-breadcrumb-item>
<el-breadcrumb-item>隐患检查结果</el-breadcrumb-item> <el-breadcrumb-item>隐患检查结果</el-breadcrumb-item>
</el-breadcrumb> </el-breadcrumb>
</el-header> </el-header>

View File

@ -1,156 +0,0 @@
<script setup>
import { ref } from 'vue'
const videoRef = ref(null)
const currentTime = ref(0)
const currentBoxes = ref([])
const selectedBox = ref(null)
//
const trackData = ref([
{
id: 1,
label: '人物',
color: '#FF4D4F',
frames: [
{ time: 0, x: 60, y: 40, width: 160, height: 280 },
{ time: 1, x: 80, y: 40, width: 160, height: 280 },
{ time: 2, x: 100, y: 40, width: 160, height: 280 },
{ time: 3, x: 120, y: 40, width: 160, height: 280 },
{ time: 4, x: 140, y: 40, width: 160, height: 280 },
],
},
{
id: 2,
label: '车辆',
color: '#1890FF',
frames: [
{ time: 0, x: 300, y: 150, width: 240, height: 140 },
{ time: 1, x: 320, y: 150, width: 240, height: 140 },
{ time: 2, x: 340, y: 150, width: 240, height: 140 },
],
},
])
//
function onTimeUpdate() {
const t = videoRef.value.currentTime
currentTime.value = t
const boxes = []
trackData.value.forEach((item) => {
const frame = item.frames.findLast(f => f.time <= t)
if (frame) {
boxes.push({
id: item.id,
label: item.label,
color: item.color,
...frame,
})
}
})
currentBoxes.value = boxes
}
//
function handleBoxClick(box) {
selectedBox.value = box
}
</script>
<template>
<div class="demo-container" style="max-width: 1200px; margin: 20px auto">
<el-card header="视频实时物体跟踪(支持点击标注框)">
<div class="video-wrapper">
<video
ref="videoRef"
src="https://www.w3school.com.cn/i/movie.mp4"
controls
class="video"
@timeupdate="onTimeUpdate"
/>
<!-- 标注层 -->
<div class="annotations-layer">
<div
v-for="box in currentBoxes"
:key="box.id"
class="box"
:class="{ active: selectedBox?.id === box.id }"
:style="{
left: `${box.x}px`,
top: `${box.y}px`,
width: `${box.width}px`,
height: `${box.height}px`,
borderColor: box.color,
}"
@click.stop="handleBoxClick(box)"
>
<span class="label">{{ box.label }}</span>
</div>
</div>
</div>
<div style="margin-top: 16px; display: flex; gap: 10px">
<el-tag type="primary">
当前时间{{ currentTime.toFixed(2) }}s
</el-tag>
<el-tag v-if="selectedBox" type="warning">
已选中 ID{{ selectedBox.id }} | 标签{{ selectedBox.label }}
</el-tag>
</div>
</el-card>
</div>
</template>
<style scoped>
.video-wrapper {
position: relative;
width: 100%;
background: #000;
border-radius: 8px;
overflow: hidden;
}
.video {
display: block;
width: 100%;
height: auto;
}
.annotations-layer {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.box {
position: absolute;
border: 2px solid;
background: rgba(0, 0, 0, 0.1);
pointer-events: auto;
cursor: pointer;
box-sizing: border-box;
transform-origin: center center;
}
.box.active {
border-width: 4px;
background: rgba(255, 255, 0, 0.1);
transform: translate(-1px, -1px); /* 完美抵消边框变粗偏移 */
}
.label {
position: absolute;
top: -22px;
left: 0;
background: rgba(0, 0, 0, 0.7);
color: #fff;
font-size: 13px;
padding: 2px 6px;
border-radius: 3px;
}
</style>

View File

@ -1,241 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
// =============== 1. TS ===============
/** 单个帧位置信息 */
interface TrackFrame {
time: number
x: number
y: number
width: number
height: number
}
/** 单个跟踪目标 */
interface TrackItem {
id: number
label: string
color: string
frames: TrackFrame[]
}
/** 渲染到页面的标注框 */
interface RenderBox extends TrackFrame {
id: number
label: string
color: string
}
// =============== 2. ===============
const 隐患列表 = ref<string[]>(['隐患1', '隐患2', '隐患3', '隐患4', '隐患5'])
const 物体列表 = ref<string[]>(['物体1', '物体2', '物体3', '物体4', '物体5'])
const videoRef = ref<HTMLVideoElement | null>(null)
const currentTime = ref<number>(0)
const currentBoxes = ref<RenderBox[]>([])
const selectedBox = ref<RenderBox | null>(null)
//
const trackData = ref<TrackItem[]>([
{
id: 1,
label: '人物',
color: '#FF4D4F',
frames: [
{ time: 0, x: 60, y: 40, width: 160, height: 280 },
{ time: 1, x: 80, y: 40, width: 160, height: 280 },
{ time: 2, x: 100, y: 40, width: 160, height: 280 },
{ time: 3, x: 120, y: 40, width: 160, height: 280 },
{ time: 4, x: 140, y: 40, width: 160, height: 280 },
],
},
{
id: 2,
label: '车辆',
color: '#1890FF',
frames: [
{ time: 0, x: 300, y: 150, width: 240, height: 140 },
{ time: 1, x: 320, y: 150, width: 240, height: 140 },
{ time: 2, x: 340, y: 150, width: 240, height: 140 },
],
},
])
// =============== 3. ===============
/** 视频时间更新 */
function onTimeUpdate(): void {
if (!videoRef.value)
return
const t = videoRef.value.currentTime
currentTime.value = t
const boxes: RenderBox[] = []
trackData.value.forEach((item) => {
const frame = item.frames.findLast(f => f.time <= t)
if (frame) {
boxes.push({
id: item.id,
label: item.label,
color: item.color,
...frame,
})
}
})
currentBoxes.value = boxes
}
/** 点击标注框 */
function handleBoxClick(box: RenderBox): void {
selectedBox.value = box
}
</script>
<template>
<!-- 最外层容器占满整个视口 -->
<el-container style="margin: 0; height: 100%; flex-direction: column;">
<el-header style="height: 60px; display: flex; align-items: center; border-bottom: 1px solid var(--ep-border-color);">
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/' }">
主页
</el-breadcrumb-item>
<el-breadcrumb-item>隐患检查</el-breadcrumb-item>
</el-breadcrumb>
</el-header>
<!-- 内层容器自动填充剩余高度 -->
<el-container style="flex: 1; min-height: 0;">
<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 style="min-height: 20px;">
隐患列表
</el-row>
<el-row style="flex: 1; overflow-y: auto;">
<el-row v-for="item in 隐患列表" :key="item">
<el-button>
{{ item }}
</el-button>
</el-row>
</el-row>
</el-row>
<el-row>物体列表</el-row>
<el-row style="flex: 1; overflow-y: auto;">
<el-row v-for="item in 物体列表" :key="item">
<el-button>
{{ item }}
</el-button>
</el-row>
</el-row>
</el-aside>
<el-main style="border-right: 1px solid var(--ep-border-color); padding: 0;">
<div class="vid_box" style="border-bottom: 1px solid var(--ep-border-color);">
<div class="video-wrapper">
<video
ref="videoRef"
src="https://www.w3school.com.cn/i/movie.mp4"
controls
class="video"
@timeupdate="onTimeUpdate"
/>
<!-- 标注层 -->
<div class="annotations-layer">
<div
v-for="box in currentBoxes"
:key="box.id"
class="box"
:class="{ active: selectedBox?.id === box.id }"
:style="{
left: `${box.x}px`,
top: `${box.y}px`,
width: `${box.width}px`,
height: `${box.height}px`,
borderColor: box.color,
}"
@click.stop="handleBoxClick(box)"
>
<span class="label">{{ box.label }}</span>
</div>
</div>
</div>
</div>
<div class="vid_track">
轨道
</div>
</el-main>
<el-aside width="300px">
<el-row style="border-bottom: 1px solid var(--ep-border-color);">
<el-col>
<el-row class="text-left">
隐患描述
</el-row>
<el-row class="text-left">
依据
</el-row>
<el-row class="text-left">
整改建议
</el-row>
</el-col>
</el-row>
<el-row>
<el-button>
查看报告
</el-button>
</el-row>
</el-aside>
</el-container>
</el-container>
</template>
<style scoped lang="scss">
.video-wrapper {
position: relative;
width: calc(100% - 8px);
background: #000;
border-radius: 8px;
overflow: hidden;
margin: 4px;
}
.video {
display: block;
width: 100%;
height: auto;
}
.annotations-layer {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.box {
position: absolute;
border: 2px solid;
background: rgba(0, 0, 0, 0.1);
pointer-events: auto;
cursor: pointer;
box-sizing: border-box;
transform-origin: center center;
}
.box.active {
border-width: 4px;
background: rgba(255, 255, 0, 0.1);
transform: translate(-1px, -1px); /* 完美抵消边框变粗偏移 */
}
.label {
position: absolute;
top: -22px;
left: 0;
background: rgba(0, 0, 0, 0.7);
color: #fff;
font-size: 13px;
padding: 2px 6px;
border-radius: 3px;
}
</style>

View File

@ -1,188 +0,0 @@
<script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus'
import { onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { runApi } from '~/composables/api'
const router = useRouter()
const runCheckBtnRef = ref()
const isRunningCheck = ref(false)
const vidPaths = ref<string[]>([])
interface RuleForm {
vidPath: string
function: string[]
}
const ruleFormRef = ref<FormInstance>()
const ruleForm = reactive<RuleForm>({
vidPath: '',
function: [
// 'runSam3',
// 'runHazardCheck',
// 'runGenerateReport',
// 'runAudioRecognition',
],
})
const rules = reactive<FormRules<RuleForm>>({
vidPath: [
{
type: 'string',
required: true,
message: '请选择视频',
trigger: 'change',
},
],
})
async function runCheck(formEl: FormInstance | undefined) {
if (!formEl)
return
const valid = await formEl.validate((valid, fields) => {
if (valid) {
// console.log('submit!', ruleForm)
}
else {
console.error('error submit!', fields)
}
})
if (valid) {
isRunningCheck.value = true
await runApi('/run', {
vid_file: vidPaths.value[vidPaths.value.indexOf(ruleForm.vidPath)],
run_sam3: ruleForm.function.includes('runSam3'),
run_inspection: ruleForm.function.includes('runHazardCheck'),
gen_report: ruleForm.function.includes('runGenerateReport'),
}).then((res) => {
isRunningCheck.value = false
//
if (res !== 'error') {
router.push({
path: '/nav/hazardCheck/hazardCheckResult',
query: { vid_file: vidPaths.value[vidPaths.value.indexOf(ruleForm.vidPath)] },
})
}
})
}
}
async function reloadFiles() {
await runApi('/reload_files', {}).then((res) => {
for (const item of (res as any)[0].choices) {
vidPaths.value.push(item[0])
}
})
}
function resetForm(formEl: FormInstance | undefined) {
if (!formEl)
return
formEl.resetFields()
}
async function goToResult(formEl: FormInstance | undefined) {
if (!formEl)
return
const valid = await formEl.validate((valid, fields) => {
if (valid) {
// console.log('')
router.push('/nav/hazardCheck/hazardCheckResult')
}
else {
console.error('error submit!', fields)
}
})
if (valid) {
isRunningCheck.value = true
router.push({
path: '/nav/hazardCheck/hazardCheckResult',
query: { vid_file: 'santai5.mp4' },
})
// isRunningCheck.value = false
}
}
onMounted(() => {
reloadFiles()
})
</script>
<template>
<!-- 最外层容器占满整个视口 -->
<el-container style="margin: 0; height: 100%; flex-direction: column;">
<el-header style="height: 30px; display: flex; align-items: center; border-bottom: 1px solid var(--ep-border-color);">
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/' }">
主页
</el-breadcrumb-item>
<el-breadcrumb-item>隐患检查</el-breadcrumb-item>
</el-breadcrumb>
</el-header>
<!-- 内层容器自动填充剩余高度 -->
<el-main class="main" style="flex: 1; min-height: 0;">
<el-form
ref="ruleFormRef"
style="max-width: 600px"
:model="ruleForm"
:rules="rules"
label-width="auto"
>
<el-form-item label="视频" prop="vidPath">
<el-select v-model="ruleForm.vidPath" placeholder="视频">
<el-option v-for="item in vidPaths" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="功能选择" prop="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="runGenerateReport" name="function">
生成报告
</el-checkbox>
<el-checkbox value="runAudioRecognition" name="function">
音频识别
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item>
<el-button @click="resetForm(ruleFormRef)">
重置表单
</el-button>
<el-button @click="goToResult(ruleFormRef)">
查看结果
</el-button>
<el-button
ref="runCheckBtnRef"
type="primary"
:loading="isRunningCheck"
@click="runCheck(ruleFormRef)"
>
运行检查
</el-button>
</el-form-item>
</el-form>
</el-main>
</el-container>
</template>
<style scoped lang="scss">
.main {
display: flex;
justify-content: center;
align-items: start;
}
.checkbox-group {
display: flex;
flex-wrap: wrap;
gap: 16px;
justify-content: flex-start;
}
</style>

View File

@ -19,9 +19,6 @@ declare module 'vue-router/auto-routes' {
*/ */
export interface RouteNamedMap { export interface RouteNamedMap {
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>, '/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
'/nav/hazardCheck/': RouteRecordInfo<'/nav/hazardCheck/', '/nav/hazardCheck', Record<never, never>, Record<never, never>>, '/nav/HazardCheckResult/': RouteRecordInfo<'/nav/HazardCheckResult/', '/nav/HazardCheckResult', Record<never, never>, Record<never, never>>,
'/nav/hazardCheck/1_隐患检查 copy': RouteRecordInfo<'/nav/hazardCheck/1_隐患检查 copy', '/nav/hazardCheck/1_隐患检查 copy', Record<never, never>, Record<never, never>>,
'/nav/hazardCheck/HazardCheckResult': RouteRecordInfo<'/nav/hazardCheck/HazardCheckResult', '/nav/hazardCheck/HazardCheckResult', Record<never, never>, Record<never, never>>,
'/nav/VideoTrackDemo': RouteRecordInfo<'/nav/VideoTrackDemo', '/nav/VideoTrackDemo', Record<never, never>, Record<never, never>>,
} }
} }