forked from kongfp/Tp_Web2.0
feat: 视频分析页面开发
parent
8330d1df8c
commit
237d5a60d8
@ -1,2 +1,4 @@
|
||||
const { VITE_APP_BASE_URL } = import.meta.env;
|
||||
export const baseUrlApi = (url: string) => `${VITE_APP_BASE_URL}/api/${url}`;
|
||||
import { getConfig } from "@/config";
|
||||
export const baseUrlApi = (url: string) =>
|
||||
`${getConfig().AdminHostUrl || VITE_APP_BASE_URL}/api/${url}`;
|
||||
|
@ -0,0 +1,24 @@
|
||||
import { http } from "@/utils/http";
|
||||
import { baseUrlApi } from "./utils";
|
||||
|
||||
type Result = {
|
||||
msg: any;
|
||||
success: boolean;
|
||||
data?: {
|
||||
person?: Array<any>;
|
||||
car?: Array<any>;
|
||||
};
|
||||
};
|
||||
type Data = {
|
||||
success: boolean;
|
||||
msg: any;
|
||||
count: number;
|
||||
results?: Array<any>;
|
||||
};
|
||||
|
||||
export const getPicList = (params?: object) => {
|
||||
return http.request<Result>("get", baseUrlApi("tps/pics"), { params });
|
||||
};
|
||||
export const getTPList = (params?: object) => {
|
||||
return http.request<Data>("get", baseUrlApi("tps"), { params });
|
||||
};
|
@ -0,0 +1,20 @@
|
||||
export default {
|
||||
path: "/videoParse",
|
||||
// redirect: "/error/403",
|
||||
meta: {
|
||||
icon: "lollipop",
|
||||
title: "视频分析",
|
||||
showLink: true,
|
||||
rank: 10
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/videoParse/index",
|
||||
name: "VideoParsePage",
|
||||
component: () => import("@/views/videoParse/index.vue"),
|
||||
meta: {
|
||||
title: "视频分析"
|
||||
}
|
||||
}
|
||||
]
|
||||
} as RouteConfigsTable;
|
@ -0,0 +1,77 @@
|
||||
import { addDialog } from "@/components/ReDialog";
|
||||
// import { ref, h } from "vue";
|
||||
import OurPlayer from "@/components/VideoPlayer/OurPlayer.vue";
|
||||
// import { http } from "@/utils/http";
|
||||
|
||||
export function videoUtil() {
|
||||
// const currentPlayingIndex = ref(-1);
|
||||
const columns: TableColumnList = [
|
||||
{
|
||||
label: "视频内时间",
|
||||
prop: "relative_time",
|
||||
minWidth: 100,
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: "异常类型",
|
||||
prop: "exception_type",
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
label: "异常ID",
|
||||
prop: "abnormal_id",
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
label: "异常行为",
|
||||
prop: "abnormal_behavior",
|
||||
minWidth: 100,
|
||||
slot: "violation"
|
||||
},
|
||||
{
|
||||
label: "异常关键帧",
|
||||
slot: "image"
|
||||
},
|
||||
{
|
||||
label: "异常视频定位",
|
||||
slot: "video"
|
||||
}
|
||||
// {
|
||||
// label: "视频",
|
||||
// slot: "video"
|
||||
// },
|
||||
];
|
||||
function openDialog(title = "视频", row) {
|
||||
const highlights = [
|
||||
// 进度条时间点高亮
|
||||
{
|
||||
text: row.event_type,
|
||||
time: row.relative_time
|
||||
}
|
||||
];
|
||||
sessionStorage.setItem("video_url", row.abnormal_video);
|
||||
sessionStorage.setItem("highlights", JSON.stringify(highlights));
|
||||
addDialog({
|
||||
title: `${title}用户`,
|
||||
width: "40%",
|
||||
draggable: true,
|
||||
fullscreenIcon: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => <OurPlayer></OurPlayer>
|
||||
// return h(
|
||||
// "video",
|
||||
// {
|
||||
// controls: true,
|
||||
// id: "video-" + index,
|
||||
// onPlay: () => playVideo(row, index)
|
||||
// },
|
||||
// [h("source", { src: row.video_dir, type: "video/mp4" })]
|
||||
// );
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
columns,
|
||||
openDialog
|
||||
};
|
||||
}
|
@ -0,0 +1,454 @@
|
||||
<script setup lang="ts">
|
||||
import { useWindowSize } from "@vueuse/core";
|
||||
import { Help } from "@element-plus/icons-vue";
|
||||
import { ref, reactive, onMounted } from "vue";
|
||||
import { PureTableBar } from "@/components/RePureTableBar";
|
||||
import { PureTable } from "@pureadmin/table";
|
||||
import { type PaginationProps } from "@pureadmin/table";
|
||||
import { getPicList, getTPList } from "@/api/videoParse";
|
||||
import { getToken } from "@/utils/auth";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { videoUtil } from "./hook";
|
||||
|
||||
import type { UploadProps, UploadUserFile } from "element-plus";
|
||||
|
||||
defineOptions({
|
||||
name: "videoParse"
|
||||
});
|
||||
const { columns, openDialog } = videoUtil();
|
||||
const fileList = ref<UploadUserFile[]>([
|
||||
{
|
||||
name: "element-plus-logo.svg",
|
||||
url: "https://element-plus.org/images/element-plus-logo.svg"
|
||||
}
|
||||
]);
|
||||
|
||||
const handleRemove: UploadProps["onRemove"] = (file, uploadFiles) => {
|
||||
console.log(file, uploadFiles);
|
||||
};
|
||||
|
||||
const handlePreview: UploadProps["onPreview"] = uploadFile => {
|
||||
console.log(uploadFile);
|
||||
};
|
||||
|
||||
const handleExceed: UploadProps["onExceed"] = (files, uploadFiles) => {
|
||||
ElMessage.warning(
|
||||
`The limit is 3, you selected ${files.length} files this time, add up to ${
|
||||
files.length + uploadFiles.length
|
||||
} totally`
|
||||
);
|
||||
};
|
||||
|
||||
const beforeRemove: UploadProps["beforeRemove"] = uploadFile => {
|
||||
return ElMessageBox.confirm(
|
||||
`Cancel the transfer of ${uploadFile.name} ?`
|
||||
).then(
|
||||
() => true,
|
||||
() => false
|
||||
);
|
||||
};
|
||||
const { height } = useWindowSize();
|
||||
|
||||
const videoData = ref([]);
|
||||
const loading = ref(true);
|
||||
const currentPage = ref(1);
|
||||
const totalNumber = ref(0);
|
||||
const pageSize = ref(10);
|
||||
// const isDisplay = true;
|
||||
const pagination = reactive<PaginationProps>({
|
||||
total: totalNumber.value,
|
||||
pageSize: pageSize.value,
|
||||
currentPage: currentPage.value,
|
||||
background: true
|
||||
});
|
||||
const personOddList = ref([]);
|
||||
const personEvenList = ref([]);
|
||||
const carOddList = ref([]);
|
||||
const carEvenList = ref([]);
|
||||
|
||||
function handleUpdate(row) {
|
||||
openDialog("我的", row);
|
||||
console.log(row);
|
||||
}
|
||||
function handleSizeChange(val) {
|
||||
pageSize.value = val;
|
||||
onSearch();
|
||||
}
|
||||
|
||||
function handleCurrentChange(val) {
|
||||
currentPage.value = val;
|
||||
onSearch();
|
||||
}
|
||||
function onSearch() {
|
||||
const params = {
|
||||
video_name: "test1",
|
||||
page: currentPage.value || undefined,
|
||||
page_size: pageSize.value || undefined
|
||||
};
|
||||
getTPList(params).then(response => {
|
||||
if (response.success) {
|
||||
const dataList = response;
|
||||
totalNumber.value = dataList.count;
|
||||
pagination.total = dataList.count;
|
||||
videoData.value = dataList.results;
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
getPicList(params).then(response => {
|
||||
if (response.success) {
|
||||
const dataList = response;
|
||||
personOddList.value = dataList.data.person.filter(
|
||||
item => item.id % 2 == 1
|
||||
);
|
||||
personEvenList.value = dataList.data.person.filter(
|
||||
item => item.id % 2 == 0
|
||||
);
|
||||
carOddList.value = dataList.data.car.filter(item => item.id % 2 == 1);
|
||||
carEvenList.value = dataList.data.car.filter(item => item.id % 2 == 0);
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function spanStyle(type) {
|
||||
switch (type) {
|
||||
case 1:
|
||||
return "span-first";
|
||||
case 2:
|
||||
return "span-second";
|
||||
case 3:
|
||||
return "span-third";
|
||||
default:
|
||||
"";
|
||||
break;
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
onSearch();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main">
|
||||
<el-row :gutter="24">
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<el-card
|
||||
class="box-card"
|
||||
:style="{ height: `calc(${height}px - 150px)` }"
|
||||
>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>视频分析</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="file-upload flex justify-around">
|
||||
<div class="upload-box">
|
||||
<el-upload
|
||||
v-model:file-list="fileList"
|
||||
class="upload"
|
||||
:headers="{ token: 'Bearer ' + getToken().accessToken }"
|
||||
action="http://192.168.10.13:8000/api/tps/upload_video"
|
||||
multiple
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="handleRemove"
|
||||
:before-remove="beforeRemove"
|
||||
:limit="1"
|
||||
:on-exceed="handleExceed"
|
||||
>
|
||||
<span>{{ "选择分析文件" }}</span
|
||||
><el-button type="primary">选择文件</el-button>
|
||||
<!-- <template #tip>
|
||||
<div class="el-upload__tip">
|
||||
jpg/png files with a size less than 500KB.
|
||||
</div>
|
||||
</template> -->
|
||||
</el-upload>
|
||||
</div>
|
||||
<el-button type="success" size="large">
|
||||
分析 <el-icon class="el-icon--right"><Help /></el-icon
|
||||
></el-button>
|
||||
</div>
|
||||
<el-divider border-style="dashed" />
|
||||
<!-- <el-empty description="暂无数据" v-show="videoData.length == 0" /> -->
|
||||
<div class="analysis-content flex justify-between">
|
||||
<div
|
||||
class="analysis-box"
|
||||
:style="{ height: `calc(${height}px - 60vh - 150px)` }"
|
||||
>
|
||||
<span class="analysis-box-label">被执法人员推定</span>
|
||||
<div
|
||||
class="analysis-table"
|
||||
:style="{ height: `calc(${height}px - 60vh - 240px)` }"
|
||||
>
|
||||
<div class="analysis-table-left">
|
||||
<div class="analysis-box-lable w-full flex justify-end">
|
||||
<span>目标清晰度</span>
|
||||
</div>
|
||||
<el-scrollbar :height="`calc(${height}px - 60vh - 220px)`">
|
||||
<div
|
||||
v-for="(item, index) in personOddList"
|
||||
:key="index"
|
||||
class="scrollbar-item"
|
||||
>
|
||||
<span :class="spanStyle(item.id)">{{ item.id }}</span>
|
||||
<el-image
|
||||
style="height: 100px"
|
||||
:src="item.pic_path"
|
||||
:zoom-rate="1.2"
|
||||
:preview-src-list="[item.pic_path]"
|
||||
fit="cover"
|
||||
/>
|
||||
<span>{{ item.pic_quality }}</span>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<el-divider
|
||||
direction="vertical"
|
||||
:style="{ height: `calc(${height}px - 60vh - 220px)` }"
|
||||
/>
|
||||
<div class="analysis-table-left">
|
||||
<div class="analysis-box-lable w-full flex justify-end">
|
||||
<span>目标清晰度</span>
|
||||
</div>
|
||||
<el-scrollbar :height="`calc(${height}px - 60vh - 220px)`">
|
||||
<div
|
||||
v-for="(item, index) in personEvenList"
|
||||
:key="index"
|
||||
class="scrollbar-item"
|
||||
>
|
||||
<span :class="spanStyle(item.id)">{{ item.id }}</span>
|
||||
<el-image
|
||||
style="height: 100px"
|
||||
:src="item.pic_path"
|
||||
:zoom-rate="1.2"
|
||||
:preview-src-list="[item.pic_path]"
|
||||
fit="cover"
|
||||
/>
|
||||
<span>{{ item.pic_quality }}</span>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="analysis-box"
|
||||
:style="{ height: `calc(${height}px - 60vh - 150px)` }"
|
||||
>
|
||||
<span class="analysis-box-label">被执法车辆推定</span>
|
||||
<div
|
||||
class="analysis-table"
|
||||
:style="{ height: `calc(${height}px - 60vh - 240px)` }"
|
||||
>
|
||||
<div class="analysis-table-left">
|
||||
<div class="analysis-box-lable w-full flex justify-end">
|
||||
<span>目标清晰度</span>
|
||||
</div>
|
||||
<el-scrollbar :height="`calc(${height}px - 60vh - 220px)`">
|
||||
<div
|
||||
v-for="item in carOddList"
|
||||
:key="item.id"
|
||||
class="scrollbar-item"
|
||||
>
|
||||
<span :class="spanStyle(item.id)">{{ item.id }}</span>
|
||||
<div class="defect-image">
|
||||
<el-image
|
||||
style="height: 100px"
|
||||
:src="item.pic_path"
|
||||
:zoom-rate="1.2"
|
||||
:preview-src-list="[item.pic_path]"
|
||||
fit="cover"
|
||||
/>
|
||||
<span>{{ item.car_number }}</span>
|
||||
</div>
|
||||
<span>{{ item.pic_quality }}</span>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<el-divider
|
||||
direction="vertical"
|
||||
:style="{ height: `calc(${height}px - 60vh - 240px)` }"
|
||||
/>
|
||||
<div class="analysis-table-left">
|
||||
<div class="analysis-box-lable w-full flex justify-end">
|
||||
<span>目标清晰度</span>
|
||||
</div>
|
||||
<el-scrollbar :height="`calc(${height}px - 60vh - 220px)`">
|
||||
<div
|
||||
v-for="item in carEvenList"
|
||||
:key="item.id"
|
||||
class="scrollbar-item"
|
||||
>
|
||||
<span :class="spanStyle(item.id)">{{ item.id }}</span>
|
||||
<div class="defect-image">
|
||||
<el-image
|
||||
style="height: 100px"
|
||||
:src="item.pic_path"
|
||||
:zoom-rate="1.2"
|
||||
:preview-src-list="[item.pic_path]"
|
||||
fit="cover"
|
||||
/>
|
||||
<span>{{ item.car_number }}</span>
|
||||
</div>
|
||||
<span>{{ item.pic_quality }}</span>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PureTableBar
|
||||
title="视频分析列表"
|
||||
:columns="columns"
|
||||
@refresh="onSearch"
|
||||
>
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<pure-table
|
||||
border
|
||||
adaptive
|
||||
align-whole="center"
|
||||
table-layout="auto"
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
:data="videoData"
|
||||
:columns="dynamicColumns"
|
||||
:pagination="pagination"
|
||||
:paginationSmall="size === 'small' ? true : false"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-table-row-hover-bg-color)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
}"
|
||||
@page-size-change="handleSizeChange"
|
||||
@page-current-change="handleCurrentChange"
|
||||
>
|
||||
<template #violation="{ row }">
|
||||
{{ row.is_violation ? "是" : "否" }}
|
||||
</template>
|
||||
<template #image="{ row, index }">
|
||||
<el-image
|
||||
preview-teleported
|
||||
loading="lazy"
|
||||
:src="row.abnormal_pic"
|
||||
:preview-src-list="[row.abnormal_pic]"
|
||||
:initial-index="index"
|
||||
fit="cover"
|
||||
class="w-[100px] h-[100px]"
|
||||
/>
|
||||
</template>
|
||||
<template #video="{ row }">
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
@click="handleUpdate(row)"
|
||||
>
|
||||
播放视频
|
||||
</el-button>
|
||||
</template>
|
||||
</pure-table>
|
||||
</template>
|
||||
</PureTableBar>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.card-header {
|
||||
span {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.file-upload {
|
||||
height: 60px;
|
||||
|
||||
.upload-box {
|
||||
width: 300px;
|
||||
|
||||
.upload {
|
||||
span {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.analysis-content {
|
||||
.analysis-box {
|
||||
width: 48%;
|
||||
padding: 20px;
|
||||
box-shadow: 5px 5px 5px rgb(0 0 0 / 2.5%), 5px -5px 5px rgb(0 0 0 / 2.5%),
|
||||
-5px 5px 5px rgb(0 0 0 / 2.5%), -5px -5px 5px rgb(0 0 0 / 2.5%);
|
||||
// background: skyblue;
|
||||
// height: 400px;
|
||||
.analysis-box-label {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.analysis-table {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
.analysis-table-left {
|
||||
width: 45%;
|
||||
|
||||
.analysis-box-lable {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.scrollbar-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.defect-image {
|
||||
position: relative;
|
||||
|
||||
span {
|
||||
position: absolute;
|
||||
bottom: 7px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
background-color: rgb(0 0 0 / 50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.analysis-box-line) {
|
||||
height: 320px;
|
||||
}
|
||||
|
||||
.span-first {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #e02828;
|
||||
}
|
||||
|
||||
.span-second {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #e58b03;
|
||||
}
|
||||
|
||||
.span-third {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #516ee0;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue