31
0
Fork 0

feat: 视频分析页面开发

develop
JINGYJ 2 years ago
parent 8330d1df8c
commit 237d5a60d8

@ -19,5 +19,5 @@
"CachingAsyncRoutes": false,
"TooltipEffect": "light",
"ResponsiveStorageNameSpace": "responsive-",
"AdminHostUrl": "http://192.168.10.81:8848/"
"AdminHostUrl": "http://192.168.10.13:8000"
}

@ -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>

@ -1,10 +1,20 @@
import { addDialog } from "@/components/ReDialog";
import { ref, h } from "vue";
// import { ref, h } from "vue";
import OurPlayer from "@/components/VideoPlayer/OurPlayer.vue";
// import { http } from "@/utils/http";
export function welcomeUtil() {
const currentPlayingIndex = ref(-1);
function openDialog(title = "视频", row, index: number) {
// const currentPlayingIndex = ref(-1);
function openDialog(title = "视频", row) {
const highlights = [
// 进度条时间点高亮
{
text: row.event_type,
time: row.relative_time
}
];
sessionStorage.setItem("video_url", row.video_dir);
sessionStorage.setItem("highlights", JSON.stringify(highlights));
console.log(row.video_dir, "row.video_dir");
addDialog({
title: `${title}用户`,
@ -12,28 +22,27 @@ export function welcomeUtil() {
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => {
return h(
"video",
{
controls: true,
id: "video-" + index,
onPlay: () => playVideo(row, index)
},
[h("source", { src: row.video_dir, type: "video/mp4" })]
);
}
contentRenderer: () => <OurPlayer></OurPlayer>
// return h(
// "video",
// {
// controls: true,
// id: "video-" + index,
// onPlay: () => playVideo(row, index)
// },
// [h("source", { src: row.video_dir, type: "video/mp4" })]
// );
});
}
function playVideo(row, index) {
if (currentPlayingIndex.value !== -1) {
const videoElement = document.getElementById(
"video-" + currentPlayingIndex.value
);
videoElement.pause(); // 暂停当前正在播放的视频
}
currentPlayingIndex.value = index;
}
// function playVideo(row, index) {
// if (currentPlayingIndex.value !== -1) {
// const videoElement = document.getElementById(
// "video-" + currentPlayingIndex.value
// );
// videoElement.pause(); // 暂停当前正在播放的视频
// }
// currentPlayingIndex.value = index;
// }
return {
openDialog

@ -44,19 +44,19 @@ const violationMap = ref({
});
// const currentPlayingIndex = ref(-1); //
// function playVideo(index) {
// // data = JSON.parse(JSON.stringify(data));
// // // console.log(AdminHostUrl)
// // dialogVisible.value = true;
// // const highlights = [
// // //
// // {
// // text: data.event_type,
// // time: data.relative_time
// // }
// // ];
// // sessionStorage.setItem("video_url", data.video_dir);
// // sessionStorage.setItem("highlights", JSON.stringify(highlights));
// function playVideo(data) {
// data = JSON.parse(JSON.stringify(data));
// // console.log(AdminHostUrl)
// dialogVisible.value = true;
// const highlights = [
// //
// {
// text: data.event_type,
// time: data.relative_time
// }
// ];
// sessionStorage.setItem("video_url", data.video_dir);
// sessionStorage.setItem("highlights", JSON.stringify(highlights));
// if (currentPlayingIndex.value !== -1) {
// const videoElement = document.getElementById(
// "video-" + currentPlayingIndex.value
@ -254,8 +254,8 @@ const pagination = reactive<PaginationProps>({
currentPage: currentPage.value,
background: true
});
function handleUpdate(row, index) {
openDialog("我的", row, index);
function handleUpdate(row) {
openDialog("我的", row);
console.log(row);
}
// function handleDelete(row) {
@ -398,13 +398,13 @@ onMounted(() => {
<source :src="row.video_dir" type="video/mp4" />
</video>
</template> -->
<template #operation="{ row, index }">
<template #operation="{ row }">
<el-button
class="reset-margin"
link
type="primary"
:size="size"
@click="handleUpdate(row, index)"
@click="handleUpdate(row)"
>
播放视频
</el-button>

Loading…
Cancel
Save