feat: 拆分主模块

master
donghao 2 days ago
parent 66da196362
commit 348133a600

@ -137,8 +137,7 @@ html, body, #app {
/* 大屏页面特殊处理 */
.router-view-container {
background-color: #08104C; /* 深色背景 */
color: #fff;
background: linear-gradient( 160deg, #E2EAFF 0%, rgba(255,255,255,0) 100%);
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 713 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 915 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 711 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 922 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 955 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 468 B

@ -1,26 +0,0 @@
<svg width="42" height="34" viewBox="0 0 42 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_ii_9562_48037)">
<path d="M38.582 32.8229H3.93728L1.25977 30.6782V24.4139L2.7611 23.2867L2.78615 10.4887L1.25977 9.26178V3.0673L3.93728 0.932617H38.582L41.2595 3.0673V30.6782L38.582 32.8229Z" fill="#009DFF" fill-opacity="0.1"/>
</g>
<path opacity="0.6" d="M38.3222 33H3.67751L1 30.848V24.562L2.50134 23.431L2.52638 10.589L1 9.35782V3.14202L3.67751 1H38.3222L40.9997 3.14202V30.848L38.3222 33Z" stroke="#009DFF" stroke-width="0.58" stroke-miterlimit="10"/>
<path d="M21 15C22.933 15 24.5 13.433 24.5 11.5C24.5 9.56701 22.933 8 21 8C19.067 8 17.5 9.56701 17.5 11.5C17.5 13.433 19.067 15 21 15Z" fill="#009DFF" stroke="#009DFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 25.4V26H30V25.4C30 23.1598 30 22.0397 29.5641 21.184C29.1806 20.4314 28.5686 19.8195 27.816 19.436C26.9603 19 25.8402 19 23.6 19H18.4C16.1598 19 15.0397 19 14.1841 19.436C13.4314 19.8195 12.8195 20.4314 12.436 21.184C12 22.0397 12 23.1598 12 25.4Z" fill="#009DFF" stroke="#009DFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<defs>
<filter id="filter0_ii_9562_48037" x="1.25977" y="-1.06738" width="39.9995" height="35.8904" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.0361115 0 0 0 0 0.4795 0 0 0 0 1 0 0 0 0.4 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_9562_48037"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="-2"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.0352941 0 0 0 0 0.478431 0 0 0 0 1 0 0 0 0.4 0"/>
<feBlend mode="normal" in2="effect1_innerShadow_9562_48037" result="effect2_innerShadow_9562_48037"/>
</filter>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Before

Width:  |  Height:  |  Size: 800 KiB

After

Width:  |  Height:  |  Size: 800 KiB

@ -1,95 +0,0 @@
<!--
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-06 15:00:26
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-06-11 09:51:40
* @FilePath: \vite-ai\data-dashboard\src\views\dashboard\components\footer.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
<div class="footer-box">
<ul class="nav-list">
<li v-for="(item, index) in navList" :key="item.path" :class="{ active: isActive(item.fullPath) }">
<router-link :to="item.fullPath" class="nav-item">
<div :class="`nav-icon-${index}`">
</div>
<div class="mt-[12px]">{{ item?.meta?.title }}</div>
</router-link>
</li>
</ul>
</div>
</template>
<script lang="ts" setup>
import { useRoute } from 'vue-router';
import { dataViewRoutes } from '@/router/dataView';
//
const navList = computed(() => dataViewRoutes?.children)
const route = useRoute();
//
const isActive = (path: string) => {
console.log(route.path, "isActive", path);
return route.path.startsWith(path) || route.path === path;
};
</script>
<style scoped lang="scss">
.footer-box {
margin-top: 30px;
position: fixed;
bottom: 0;
width: 100%;
padding-bottom: 20px;
font-size: 18px;
.nav-list {
display: flex;
justify-content: center;
gap: 64px;
padding: 0;
.nav-item {
text-align: center;
cursor: pointer;
transition: color 0.3s;
// 1 4
@for $i from 0 through 4 {
// .menu-icon-1.menu-icon-2
.nav-icon-#{$i} {
//
background-image: url('@/assets/footer/menu#{$i}.png');
//
background-repeat: no-repeat;
background-size: cover;
//
width: 96px;
height: 96px;
}
}
.nav-icon {
width: 40px;
height: 40px;
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"/></svg>') no-repeat center;
background-size: 80%;
margin-bottom: 5px;
}
}
&>li {
color: #fff;
opacity: 0.6;
}
.active {
opacity: 1;
}
}
}
</style>

@ -1,171 +0,0 @@
<!--
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-06 15:42:11
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-03-13 10:49:12
* @FilePath: \vite-ai\data-dashboard\src\components\Navbar.vue
* @Description: 标题栏
-->
<script setup lang="ts">
// import { useNav } from "@/layout/hooks/useNav";
// import router, { resetRouter } from "@/router";
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const logout = () => {
userStore.logout()
}
defineOptions({
name: "DsNavbar"
});
// const { logout } = useNav();
//
const currTime = ref({
date: "",
time: "",
week: ""
});
function _formatNum(value) {
if (value <= 9) {
return "0" + value;
}
return value;
}
function getTime() {
const nowDate = new Date();
const date =
nowDate.getFullYear() +
"/" +
_formatNum(nowDate.getMonth() + 1) +
"/" +
_formatNum(nowDate.getDate());
const time =
_formatNum(nowDate.getHours()) +
":" +
_formatNum(nowDate.getMinutes())
// +
// ":" +
// _formatNum(nowDate.getSeconds());
let week = "";
switch (nowDate.getDay()) {
case 0:
week = "星期天";
break;
case 1:
week = "星期一";
break;
case 2:
week = "星期二";
break;
case 3:
week = "星期三";
break;
case 4:
week = "星期四";
break;
case 5:
week = "星期五";
break;
case 6:
week = "星期六";
break;
default:
break;
}
return {
date,
time,
week
};
}
const getDataTime = () => {
currTime.value = getTime();
setTimeout(getDataTime, 1000);
};
onMounted(() => {
getDataTime();
});
</script>
<template>
<div class="flex justify-between h-full align-middle Navbar_wrap">
<div class="flex items-center left">
<div class="bg_logo_left"></div>
</div>
<div class="flex">
<!-- <div class="center_title" /> -->
<h1 class="center_title"></h1>
</div>
<div class="flex items-center justify-end right">
<div class="date_box ff1">
<span>{{ currTime.date }}</span><span>{{ currTime.week }}</span><span>{{ currTime.time }}</span>
</div>
<div class="bg_user_icon" @click="logout" />
</div>
</div>
</template>
<style lang="scss" scoped>
.Navbar_wrap {
// bgNav
background: url("@/assets/common/bg_nav.png") no-repeat;
background-size: cover;
background-repeat: no-repeat;
width: 100%;
height: 64px;
line-height: 64px;
.left {
width: 30vw;
.bg_logo_left {
margin-left: 22px;
width: 88px;
height: 40px;
background-image: url("@/assets/common/logo_left.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
}
.center_title {
// margin-top: 19px;
background: url("@/assets/common/nav_title.png") no-repeat;
background-size: contain;
background-position: center;
width: 547px;
text-align: center;
font-size: 32px;
}
.right {
width: 30vw;
.date_box {
color: #009dff;
font-size: 16px;
margin-right: 24px;
font-family: DingTalk JinBuTi;
&>span {
padding-left: 5px;
}
}
.bg_user_icon {
background: url("@/assets/common/userIcon.svg") no-repeat;
width: 40px;
height: 32px;
// border: 1px solid #009dff;
opacity: 0.6;
margin-right: 24px;
}
}
}
</style>

@ -2,42 +2,34 @@
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-06 13:56:12
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-03-12 16:46:39
* @LastEditTime: 2025-06-12 10:53:33
* @FilePath: \vite-ai\data-dashboard\src\views\dashboard\home.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
<div class="data-screen">
<!-- 顶部标题栏 -->
<header>
<NavbarComp />
</header>
<main class="app-main px-[32px]">
<main class="app-main px-[240px] py-[48px]">
<router-view v-slot="{ Component, route }">
<transition name="fade">
<component :is="Component" :key="route.path" />
</transition>
</router-view>
</main>
<footer>
<FooterComp />
</footer>
</div>
</template>
<script setup>
import NavbarComp from '@/components/Navbar.vue'
import FooterComp from '@/components/Footer.vue'
</script>
<style scoped lang="scss">
.data-screen {
background: #002a5c;
color: white;
background: linear-gradient( 160deg, #E2EAFF 0%, rgba(255,255,255,0) 100%);
font-family: 'Arial', sans-serif;
background-image: url("@/assets/common/dashboardBg.png");
/* TODO 设置主页背景 */
// background-image: url("@/assets/common/dashboardBg.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
.app-main{
height: 100%;
width: 100%;
}
}
</style>

@ -2,7 +2,7 @@
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-06-11 09:13:47
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-06-11 11:02:17
* @LastEditTime: 2025-06-12 10:18:39
* @FilePath: \Web-Traffic-Police\src\router\home.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
@ -23,34 +23,6 @@ export const dataViewRoutes = {
name: "DataOverviews",
component: () => import("@/views/dataView/DataOverview.vue"),
meta: { title: "数据总览" },
},
{
path: "monitor",
name: "AppearanceMonitor",
fullPath: "/dataView/monitor",
component: () => import("@/views/dataView/AppearanceMonitor.vue"),
meta: { title: "外观监测" },
},
{
path: "pole",
name: "PoleMonitor",
fullPath: "/dataView/pole",
component: () => import("@/views/dataView/PoleMonitor.vue"),
meta: { title: "撑杆监测" },
},
{
path: "device",
name: "DeviceStatus",
fullPath: "/dataView/device",
component: () => import("@/views/dataView/DeviceStatus.vue"),
meta: { title: "设备状态" },
},
{
path: "vehicle",
name: "VehiclManagement",
fullPath: "/dataView/vehicle",
component: () => import("@/views/dataView/VehiclManagement.vue"),
meta: { title: "车辆管理" },
}
],
};

@ -18,5 +18,5 @@
}
.bg_basic_content{
background: linear-gradient( 180deg, rgba(7,16,19,0) 0%, #081417 100%);;
// background: linear-gradient( 180deg, rgba(7,16,19,0) 0%, #081417 100%);;
}

@ -29,7 +29,7 @@
}
.el-button:hover {
color: white;
// color: white;
background-color: transparent;
outline: none;
}

@ -4,9 +4,6 @@
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
// background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
@ -24,8 +21,6 @@ body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
line-height: 1.5;
color: rgba(255, 255, 255, 0.8);
background-color: #002a5c;
}
h1,

@ -1,121 +0,0 @@
.appearance-monitor-warp {
box-sizing: border-box;
padding-top: 32px;
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
gap: 20px;
// align-items: center;
.appearance-monitor-right {
box-sizing: border-box;
width:970px;
// display: flex;
background-image: url("@/assets/common/carbtmBg.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
.appearance-monitor-search-box {
display: flex;
align-items: center;
gap: 12px;
margin: 16px 0;
}
.right-panel{
.el-scrollbar__view {
background: transparent !important;
height: 600px;
}
.fixed_pagination{
padding: 12px 20px 15px;
}
}
.appearance-monitor-left {
width: 49%;
background-image: url("@/assets/common/boderBg.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
.monitor-left-top {
box-sizing: border-box;
padding: 32px 16px 20px;
min-height: 600px;
.file-preview-screen {
width: 100%;
height: 590px;
display: flex;
justify-content: center;
align-items: center;
img {
max-width: 100%;
max-height: 460px;
object-fit: cover
}
video {
width: 100%;
max-height: calc(100%);
}
}
}
.monitor-left-bottom {
width: 100%;
padding: 0 16px;
margin-bottom: 29px;
overflow: visible;
.swiper {
width: 100%;
height: 100%;
.swiper-slide {
width: 20%;
border-radius:4px;
height: 144px;
img {
width: 100%;
height: 144px;
border-radius:4px;
object-fit: cover
}
}
.active-slide img,
.active-slide video {
border-radius:4px;
border: 2px solid #2ECCE0;
}
.swiper-button-prev,
.swiper-button-next {
background-color: rgba(0, 0, 0, 0.5);
color: white;
width: 32px;
height: 32px;
border-radius: 50%;
}
.swiper-button-prev::after,
.swiper-button-next::after {
font-size: 12px ;
color: #FFF;
}
/* 修改按钮悬停样式 */
.swiper-button-prev:hover,
.swiper-button-next:hover {
background-color: rgba(0, 0, 0, 0.8);
}
}
}
.empty-bg {
box-sizing: border-box;
width: 892px;
height: 815px;
background-image: url("@/assets/common/emptyBg.png");
background-size: 156px 102px;
background-position: center;
background-repeat: no-repeat;
}
}
}

@ -1,424 +0,0 @@
<template>
<div class="appearance-monitor-warp">
<div class="appearance-monitor-left h-[100%]">
<template v-if="currFileList?.length">
<div class="monitor-left-top">
<div class="file-preview-screen">
<Player :src="currFile?.video_url" :is-playing="isPlaying" v-if="currFile?.video_url"
@play="isPlaying = true" @pause="isPlaying = false" />
<img :src="currFile?.image_url" v-else-if="currFile?.image_url" >
<div v-else>
<!-- //TODO -->
</div>
</div>
</div>
<div class="monitor-left-bottom">
<swiper ref="swiperRef" :modules="modules" :slides-per-view="4" :space-between="10" navigation
:scrollbar="{ draggable: false }" :centered-slides="false" :observer="true" :observeParents="true"
@swiper="onSwiper" @slideChange="onSlideChange">
<swiper-slide v-for="(file, index) in currFileList" :key="index" @click="handleSlideClick(index)"
:class="{ 'active-slide': activeIndex === index }">
<img :src="file?.image_url" v-if="file?.image_url" class="cursor-pointer" />
<SwiperPlayer class="cursor-pointer" :videoUrl="file?.video_url" v-else-if="file?.video_url"
:isPlaying="isPlaying && (activeIndex === index)" />
<div v-else>
<!-- //TODO -->
</div>
</swiper-slide>
</swiper>
</div>
</template>
<div class="empty-bg" v-else></div>
</div>
<div class="appearance-monitor-right h-[100%]">
<div class="module-header">
<ContentHeader bgLayout="918">
<template #title>
<div class="w-[200px] bg_title bg_title_5"></div>
</template>
<template #extra>
<div></div>
</template>
</ContentHeader>
</div>
<!-- 表格区域 -->
<!-- 搜索区域 -->
<div class="px-[16px]">
<div class="appearance-monitor-search-box">
<el-select v-model="searchForm.station" placeholder="站点" class="custom-select">
<el-option label="小觉站" value="小觉站"></el-option>
<el-option label="东西站" value="东西站"></el-option>
<el-option label="立杆区" value="立杆区"></el-option>
</el-select>
<el-input v-model="searchForm.train_number" placeholder="请输入列车号" class="custom-input" clearable />
<el-input v-model="searchForm.train_carriage_number" placeholder="请输入车厢号" class="custom-input" clearable />
<el-select v-model="searchForm.fault_type" placeholder="故障类型" class="custom-select">
<el-option label="下侧门板缺失" value="下侧门板缺失"></el-option>
<el-option label="门折页座脱落" value="门折页座脱落"></el-option>
<el-option label="小门塔扣丢失" value="小门塔扣丢失"></el-option>
<el-option label="小窗裂纹" value="小窗裂纹"></el-option>
<el-option label="搭扣未搭" value="搭扣未搭"></el-option>
<el-option label="小门外胀" value="小门外胀"></el-option>
</el-select>
<el-button type="primary" @click="handleQuery" class="basic-btn query-btn">
<span class="icon"></span> 查询
</el-button>
<el-button @click="handleReset" class="basic-btn reset-btn">
<span class="icon"></span> 重置
</el-button>
</div>
<!-- 右侧表格区域 -->
<div class="w-full right-panel">
<div class="bg-transparent baseTable_wrap">
<template v-if="pagination.total > 0">
<BaseTable class="bg-transparent baseTable_box" :total="pagination.total" :pageSize="pagination.pageSize"
:dataSource="listData" :isFixedPagination="true" :columns="columns" :page="pagination.currentPage"
@change="handleTableChange" :row-class-name="handleRowClassName" @row-click="handleRowClick">
<template #created_at="{ row }">
<div>{{row}}</div>
</template>
</BaseTable>
</template>
</div>
</div>
</div>
</div>
<AppearanceAlarmModal v-model:value="isAlarmOpen" :info="currentRow" :image="currFileList" :beforeImage="currBeforeFileList" @close="isAlarmOpen = false" />
<DeleteModal v-model:value="isDeleteOpen" @delete-success="getList()" :info="currentRow" @close="isDeleteOpen = false" />
</div>
</template>
<script lang="ts" setup>
import Player from '@/components/videoPlayer/Player.vue'
import SwiperPlayer from './components/SwiperPlayer.vue'
import ContentHeader from "@/components/ContentHeader.vue";
import { BaseTable } from "@/components/CustomTable";
import { Swiper, SwiperSlide } from "swiper/vue";
import { Navigation, Scrollbar } from "swiper/modules";
import { getAppearanceMonitorApi, getAppearanceMonitorDetailApi, getBeforeMonitorDetailApi } from '@/api/dashboard';
import { isSuccessApi } from "@/utils/forApi";
import AppearanceAlarmModal from './components/AppearanceAlarmModal.vue'
import DeleteModal from './components/DeleteModal.vue'
import { useWebSocketStore } from '@/stores/websocketStore';
import { onBeforeRouteLeave } from 'vue-router';
import "swiper/css";
import 'swiper/scss';
import 'swiper/scss/navigation';
import { color } from 'echarts';
const modules = [Navigation, Scrollbar];
const activeIndex = ref(-1);
const swiperRef = ref(null);
const isPointOpen = ref<Boolean>(false); //
const isAlarmOpen = ref<Boolean>(false); //
const isDeleteOpen = ref<Boolean>(false); //
const websocketStore = useWebSocketStore();
// messages
watch(() => websocketStore.messages, (newMessages: string[], oldMessages: string[]) => {
// console.log(':', newMessages[newMessages?.length - 1].content);
// console.log(':', oldMessages);
// if (newMessages?.length > oldMessages?.length) {
// //
// const newMessage = newMessages[newMessages?.length - 1];
// // console.log(' WebSocket :', newMessage);
// //
// }
if(newMessages?.length > 0 && !isAlarmOpen.value) {
console.log(newMessages[newMessages?.length - 1],'newMessages[newMessages?.length - 1]');
currentRow.value = newMessages[newMessages?.length - 1];
currFileList.value = newMessages[newMessages?.length - 1]?.images;
isAlarmOpen.value = true;
}
}, { deep: true, immediate: true });
const columns = [
{
label: "站点",
property: "station",
width: 75,
},
{
label: "车号",
property: "train_number",
width: 155,
},
{
label: "车型",
property: "train_model",
width: 70,
},
{
label: "车厢号",
property: "train_carriage_number",
width: 95,
},
{
label: "告警类型",
property: "alarm_type",
width: 90,
},
{
label: "故障类型",
property: "fault_type",
width: 90,
},
{
label: "等级",
property: "level",
width: 60,
},
// {
// label: "",
// property: "is_reviewed",
// formatter: ({ is_reviewed }) => {
// return is_reviewed === true
// ? ""
// : "";
// },
// width: 60,
// },
{
label: "时间",
property: "created_at",
},
{
slot: "operation",
label: "操作",
width: 140,
formatter: (row) => {
return h(
"div",
{
style: {
fontSize: "14px",
color:"#37EBFF"
}
},
[
// h("i", {
// class: `iconfont icon-zishebeizu pr-[8px]`
// }),
h(
"span",
{
fontSize: "14px",
class: "pf-1",
},
[
h("i",
{
style: {
fontSize: "14px",
letterSpacing: "2px",
marginRight: "4px",
color:"#37EBFF"
},
onClick: () => {
// console.log(row.id);
//
isPointOpen.value = true;
}
},
"点云"
),
h("i",
{
style: {
fontSize: "14px",
letterSpacing: "2px",
marginRight: "4px",
color:"#009DFF"
},
onClick: (row) => {
console.log('row.id');
//
isAlarmOpen.value = true;
console.log(isAlarmOpen.value);
currentRow.value = row;
}
},
"详情"
),
h("i",
{
style: {
fontSize: "14px",
letterSpacing: "2px",
marginRight: "4px",
color:"#FF2727"
},
onClick: (row) => {
// console.log(row.id);
//
isDeleteOpen.value = true;
currentRow.value = row;
}
},
"删除"
),
]
)
]
);
}
}
]
const pagination = ref({ currentPage: 1, pageSize: 10, total: 0 });
const listData = ref([]); //
const currentRow = ref<Record<string, any>>({}); //
const currFileList = ref<Record<string, any>[]>([]); //
const currBeforeFileList = ref<Record<string, any>[]>([]); //
const currFile = ref<Record<string, any>>({}); //
const isPlaying = ref<boolean>(false); //
//
const searchForm = reactive({
train_number: "",
train_carriage_number: "",
fault_type: "",
station:"",
type: "appearance"
});
const dataLoading = ref(true);
const togglePlay = () => {
isPlaying.value = !isPlaying.value;
};
const handleSlideClick = (index) => {
if (activeIndex.value === index) {
togglePlay() //
} else {
activeIndex.value = index;
currFile.value = currFileList.value[index];
isPlaying.value = false;
}
};
const onSwiper = (swiper) => {
swiperRef.value = swiper;
console.log('Swiper 实例已获取:', swiper);
};
const onSlideChange = () => {
console.log("slide change");
};
//
const getFileList = async () => {
try {
const res = await getAppearanceMonitorDetailApi({ id: currentRow.value?.id, current: 1, pageSize: 1000 })
console.log(res.data, 'getDetailList_data')
if (isSuccessApi(res)) {
currFileList.value = res.data.data;
currFile.value = res.data.data[0];
activeIndex.value = 0;
}
} catch (error) {
console.log(error, 'getDetailList_error')
}
}
//
const getBeforeFileList = async () => {
try {
const res = await getBeforeMonitorDetailApi({ id: currentRow.value?.id, current: 1, pageSize: 1000 })
console.log(res.data, 'getDetailList_data')
if (isSuccessApi(res)) {
currBeforeFileList.value = res.data.data;
}
} catch (error) {
console.log(error, 'getDetailList_error')
}
}
// TODO mock
// const getFileList = async () => {
// try {
// const resAll = await fetch('/api/v1/record/record_detail_list/', {
// method: 'POST'
// })
// const res = await resAll.json()
// if (isSuccessApi(res)) {
// currFileList.value = res.data.data;
// currFile.value = res.data.data[0];
// activeIndex.value = 0;
// }
// } catch (error) {
// console.error(':', error)
// }
// }
function loadDetail() {
currentRow.value = listData.value[0]
getFileList()
getBeforeFileList()
}
//
const getList = async () => {
try {
const { currentPage, pageSize } = pagination.value;
const res = await getAppearanceMonitorApi({ ...searchForm, current: currentPage, pageSize })
console.log(res.data, 'getList_data')
if (isSuccessApi(res)) {
listData.value = res.data.data;
loadDetail()
pagination.value = {
...pagination.value,
total: res.data.total
};
}
} catch (error) {
console.error('获取数据失败:', error)
}
};
//
const handleQuery = () => {
getList()
};
//
const handleReset = () => {
searchForm.train_number = '';
searchForm.station = '';
searchForm.train_carriage_number = '';
searchForm.fault_type = '';
getList()
};
function handleTableChange(record) {
console.log("handleTableChange_record", record);
pagination.value = {
...pagination.value,
currentPage: record.page,
pageSize: record.pageSize
};
getList();
}
//
const handleRowClassName = ({ row }) => {
return row.id === currentRow.value.id ? 'selected-row' : '';
};
//
const handleRowClick = (row, event, rowIndex) => {
currentRow.value = row;
getFileList()
getBeforeFileList()
};
onBeforeRouteLeave(() => {
isAlarmOpen.value = false;
currentRow.value = {};
currFileList.value = [];
});
onMounted(() => {
getList();
});
</script>
<style lang="scss">
@import url('./AppearanceMonitor.scss');
</style>

@ -1,6 +1,10 @@
<!-- src/components/PollingComponent.vue -->
<script setup lang="ts">
import { config } from '@/config';
import Type1ObjectDetect from "./components/Type1ObjectDetect.vue";
import Type2LicensePlateRecog from "./components/Type2LicensePlateRecog.vue";
import Type3CigaretteOffer from "./components/Type3CigaretteOffer.vue";
import Type4AudioDetection from "./components/Type4AudioDetection.vue";
//
interface ListItem {
@ -11,6 +15,7 @@ interface ListItem {
//
const dataList = ref<ListItem[]>([]);
const detailInfo = ref<ListItem | null>(); //
const isLoading = ref(false);
let pollingTimer: number | null = null;
let abortController: AbortController | null = null;
@ -69,10 +74,25 @@ onUnmounted(() => {
</script>
<template>
<div>
<h2>轮询数据列表</h2>
<p v-if="isLoading">...</p>
<ul>
<div class="data-overview-wrap">
<div class="menu-wrap">
<!-- 递钱 递烟 -->
</div>
<div class="content-box">
<!-- 标题栏 -->
<div class="tilte-bar">
</div>
<ul class="content-list-box">
<li class="content-detail-box">
<Type1ObjectDetect typeKey="目标检测" :info="detailInfo" />
<Type2LicensePlateRecog typeKey="车牌识别" :info="detailInfo" />
<Type3CigaretteOffer typeKey="递烟" :info="detailInfo" />
<Type4AudioDetection typeKey="音频检测" :info="detailInfo" />
</li>
</ul>
</div>
<!-- <ul>
<li v-for="item in dataList" :key="item.id">
{{ item.titles[0] }} 状态:
@ -82,7 +102,7 @@ onUnmounted(() => {
{{ timeItem }}
</span>
</li>
</ul>
</ul> -->
</div>
</template>

@ -1,21 +0,0 @@
.device-status-wrap{
height: 813px;
background-image: url("@/assets/common/device_status_bg_line.png");
background-size: 100% 100%;
background-position: bottom;
background-repeat: no-repeat;
}
.device-status-content-box{
.el-scrollbar__view {
background: transparent !important;
height: 600px;
}
.el-table__inner-wrapper{
background-color: transparent !important;
}
.el-table__body-wrapper, .el-scrollbar__wrap, .el-scrollbar{
background: transparent !important;
}
}

@ -1,270 +0,0 @@
<template>
<div class="bg_basic_content">
<div class="device-status-wrap">
<div class="device-status-header mt-[32px]">
<ContentHeader bgLayout="1855">
<template #title>
<div class="w-[200px] bg_title bg_title_6">
</div>
</template>
</ContentHeader>
</div>
<div class="px-[16px] device-status-content-box">
<div class="mt-[16px] bg-transparent baseTable_wrap full_table">
<BaseTable class="bg-transparent baseTable_box" :total="pagination.total"
:pageSize="pagination.pageSize" :dataSource="listData" :isFixedPagination="true"
:columns="columns" :page="pagination.currentPage" @change="handleTableChange">
<template v-slot:actionBar="{ row }">
<ul class="flex table_action_box">
<li class="flex items-center mr-[16px]" @click="openCurrent(row)">
<el-button text>
<span :style="{
fontSize: '14px',
color: '#37DBFF'
}">
即时视频
</span>
</el-button>
</li>
<li class="flex items-center" @click="openHistory(row)">
<el-button text>
<span :style="{
fontSize: '14px',
color: '#37DBFF'
}">
历史视频
</span>
</el-button>
</li>
</ul>
</template>
</BaseTable>
</div>
</div>
<RealVideoModal v-model:value="isRealOpen" :info="currentRow" @close="isRealOpen = false" />
<HistoryVideoModal ref="historyModalRef" v-model:value="isHistoryOpen" :info="currentRow"
:historyVideos="historyVideos" @close="isHistoryOpen = false" />
<AlarmModal v-model:value="isAlarmOpen" :info="currentDetailRow" :image="currFileList" @close="isAlarmOpen = false" />
</div>
</div>
</template>
<script setup lang="ts">
import { BaseTable } from "@/components/CustomTable";
import ContentHeader from '@/components/ContentHeader.vue';
import HistoryVideoModal from './components/HistoryVideoModal.vue';
import RealVideoModal from './components/RealVideoModal.vue';
import onLineIcon from '@/assets/common/online_icon.png';
import outLineIcon from '@/assets/common/outline_icon.png';
import errorIcon from '@/assets/common/error_icon.png';
import { getDeviceStatusApi } from '@/api/dashboard';
import { isSuccessApi } from "@/utils/forApi";
import AlarmModal from './components/AlarmModal.vue'
import { useWebSocketStore } from '@/stores/websocketStore';
import { onBeforeRouteLeave } from 'vue-router';
defineOptions({
name: "DeviceStatusIndex"
});
const alarmLevelStatusEnum = {
'online': {
color: "#52C41A",
value: "1",
label: "在线",
isDelete: true,
icon: onLineIcon,
id: "1"
},
'offline': {
color: "#999999",
value: "2",
label: "离线",
isDelete: false,
icon: outLineIcon,
id: "2"
},
'error': {
color: "#E80D0D",
value: "3",
label: "故障",
isDelete: false,
icon: errorIcon,
id: "3"
},
'notFound': { //
color: "#E80D0D",
value: "4444",
label: "未知",
isDelete: false,
icon: errorIcon,
id: "4444"
}
}
const currentRow = ref<Record<string, any>>({});
const isRealOpen = ref<Boolean>(false); //
const isHistoryOpen = ref<Boolean>(false); //
const historyVideos = ref<Record<string, any>[]>([]); //
const historyModalRef = ref(null);
const isAlarmOpen = ref<Boolean>(false); //
const currentDetailRow = ref<Record<string, any>>({}); //
const currFileList = ref<Record<string, any>[]>([]); //
const websocketStore = useWebSocketStore();
// messages
watch(() => websocketStore.messages, (newMessages: string[], oldMessages: string[]) => {
if(newMessages?.length > 0 && !isAlarmOpen.value) {
currentDetailRow.value = newMessages[newMessages?.length - 1];
currFileList.value = newMessages[newMessages?.length - 1]?.images;
isAlarmOpen.value = true;
}
}, { deep: true, immediate: true });
const columns = [
{
label: "设备名称",
property: "device_name"
},
{
label: "设备ID",
property: "device_number"
},
{
label: "设备位置",
property: "device_position"
},
{
label: "设备状态",
property: "device_status",
formatter: val => {
console.log(val);
const currentLevelObj =
alarmLevelStatusEnum[val?.device_status] || alarmLevelStatusEnum.notFound;
return h(
"div",
{
style: {
fontSize: "14px",
display: "flex",
alignItems: "center",
lineHeight: "20px",
color: currentLevelObj?.color
}
},
[
h('img', {
src: currentLevelObj?.icon,
style: {
width: '20px',
height: '20px',
marginRight: "12px"
}
}),
h(
"span",
{
fontSize: "14px",
},
currentLevelObj?.label
)
]
);
}
},
{
type: "action",
label: "操作"
}
];
const pagination = ref({ currentPage: 1, pageSize: 10, total: 0 });
const listData = ref([]);
const getList = async () => {
try {
const { currentPage, pageSize } = pagination.value;
const res = await getDeviceStatusApi({ current: currentPage, pageSize })
console.log(res.data, 'getList_data')
if (isSuccessApi(res)) {
listData.value = res.data.data;
pagination.value = {
...pagination.value,
total: res.data.total
};
}
} catch (error) {
console.error('获取数据失败:', error)
}
}
function handleTableChange(record) {
console.log("handleTableChange_record", record);
pagination.value = {
...pagination.value,
currentPage: record.page,
pageSize: record.pageSize
};
getList();
}
/**打开实时视频 */
function openCurrent(row) {
console.log(row, "openCurrent");
currentRow.value = row;
isRealOpen.value = true;
}
/**打开历史视频 */
// getDeviceHistoryDetailApi
// const fetchHistoryList = async () => {
// try {
// const res = await getDeviceStatusApi({ device_id: currentRow.value?.id })
// if (isSuccessApi(res)) {
// historyVideos.value = res.data.data;
// isHistoryOpen.value = true;
// nextTick(() => {
// historyModalRef.value.loadData()
// })
// console.log(res, 'fetchHistoryList_data')
// }
// } catch (error) {
// console.error(':', error)
// }
// }
// TODO mock
const fetchHistoryList = async () => {
try {
const resAll = await fetch('/api/v1/device/device_history/', {
method: 'POST'
})
const res = await resAll.json()
if (isSuccessApi(res)) {
historyVideos.value = res.data;
isHistoryOpen.value = true;
nextTick(() => {
historyModalRef.value.loadData()
})
console.log(res, 'fetchHistoryList_data')
}
} catch (error) {
console.error('获取数据失败:', error)
}
}
function openHistory(row) {
console.log(row, "openHistory");
currentRow.value = row;
fetchHistoryList();
}
onBeforeRouteLeave(() => {
isAlarmOpen.value = false;
currentDetailRow.value = {};
currFileList.value = [];
});
onMounted(() => {
getList();
});
</script>
<style lang="scss">
@import url('./DeviceStatus.scss');
</style>

@ -1,117 +0,0 @@
.pole-monitor-wrap {
background-image: url("@/assets/common/bg_banner_1.png");
background-size: cover;
background-position: bottom;
background-repeat: no-repeat;
height: 823px;
.search-section {
padding: 16px 0;
}
.pole-main-content {
width: 100%;
}
.pole-monitor-search-box {
display: flex;
align-items: center;
gap: 12px;
margin: 16px 0;
}
.right-panel {
.el-scrollbar__view {
background: transparent !important;
height: 600px;
}
}
.pole-monitor-main {
.left-panel {
width: 870px;
margin-right: 16px;
&.empty-bg {
height: 680px;
background-image: url("@/assets/common/emptyBg.png");
background-size: 312px 204px;
background-position: center;
background-repeat: no-repeat;
}
.main-image {
box-sizing: border-box;
height: 511px;
position: relative;
background-color: #090f48;
border-radius: 4px;
.file-preview-screen {
height: calc(100%);
display: flex;
justify-content: center;
align-items: center;
img {
max-width: 100%;
max-height: 460px;
object-fit: cover;
}
}
video {
width: 100%;
max-height: calc(100%);
}
.image-info {
position: absolute;
height: 52px;
line-height: 52px;
bottom: 0;
font-size: 14px;
padding: 0 16px;
& > span {
margin-right: 10px;
}
}
}
.thumbnail-container {
width: 100%;
overflow: visible;
.swiper {
width: 100%;
height: 100%;
.swiper-slide {
width: 20%;
border-radius: 4px;
height: 144px;
img {
width: 100%;
height: 144px;
border-radius: 4px;
object-fit: cover;
}
}
.active-slide img,
.active-slide video {
border-radius: 4px;
border: 2px solid #2ecce0;
}
.swiper-button-prev,
.swiper-button-next {
background-color: rgba(0, 0, 0, 0.5);
color: white;
width: 32px;
height: 32px;
border-radius: 50%;
}
.swiper-button-prev::after,
.swiper-button-next::after {
font-size: 12px;
color: #fff;
}
/* 修改按钮悬停样式 */
.swiper-button-prev:hover,
.swiper-button-next:hover {
background-color: rgba(0, 0, 0, 0.8);
}
}
}
}
}
}

@ -1,390 +0,0 @@
<template>
<div class="pole-monitor-wrap mt-[32px]">
<div class="module-header">
<ContentHeader bgLayout="1855">
<template #title>
<div class="w-[200px] bg_title bg_title_3">
</div>
</template>
<template #extra>
<div></div>
</template>
</ContentHeader>
</div>
<div class="pole-main-content px-[16px]">
<!-- 搜索区域 -->
<div class="pole-monitor-search-box">
<el-select v-model="searchForm.station" placeholder="站点" class="custom-select">
<el-option label="小觉站" value="小觉站"></el-option>
<el-option label="东西站" value="东西站"></el-option>
<el-option label="立杆区" value="立杆区"></el-option>
</el-select>
<el-input v-model="searchForm.train_number" placeholder="请输入列车号" class="custom-input" clearable />
<el-input v-model="searchForm.train_carriage_number" placeholder="请输入车厢号" class="custom-input"
clearable />
<el-select v-model="searchForm.fault_type" placeholder="故障类型" class="custom-select" clearable>
<el-option label="撑杆弯曲" value="撑杆弯曲"></el-option>
<el-option label="撑杆断折" value="撑杆断折"></el-option>
</el-select>
<el-button type="primary" @click="handleQuery" class="basic-btn query-btn">
<span class="icon"></span> 查询
</el-button>
<el-button @click="handleReset" class="basic-btn reset-btn">
<span class="icon"></span> 重置
</el-button>
</div>
<!-- 主体内容区域 -->
<div class="flex justify-between pole-monitor-main">
<!-- 左侧视频与缩略图区域 -->
<div class="left-panel" v-if="currFileList?.length">
<!-- 主图显示 -->
<div class="main-image">
<!-- <img src="https://picsum.phfotos/300/200?random=1" alt="监控画面"> -->
<!-- <video ref="refVideo" controls muted :src="currFile?.video_url" width="100%" height="100%" style="object-fit: fill;"></video> -->
<div class="file-preview-screen">
<Player :src="currFile?.video_url" :is-playing="isPlaying" v-if="currFile?.video_url"
@play="isPlaying = true" @pause="isPlaying = false" />
<img :src="currFile?.image_url" v-else-if="currFile?.image_url">
<div v-else>
<!-- //TODO -->
</div>
</div>
<div class="image-info" v-if="currFile?.image_url">
<!-- //TODO -->
<span>: {{ currFile?.length }}</span>
<span>: {{ currFile?.width }}</span>
<span>: {{ currFile?.height }}</span>
<span>体积: {{ currFile?.volume }}</span>
<span>重量: {{ currFile?.weight }}</span>
</div>
</div>
<!-- 缩略图区域 -->
<div class="thumbnail-container mt-[16px] w-[870px]">
<swiper ref="swiperRef" :modules="modules" :slides-per-view="3" :space-between="10" navigation
:scrollbar="{ draggable: false }" :centered-slides="false" :observer="true"
:observeParents="true" @swiper="onSwiper" @slideChange="onSlideChange">
<swiper-slide v-for="(file, index) in currFileList" :key="index"
@click="handleSlideClick(index)" :class="{ 'active-slide': activeIndex === index }">
<img :src="file?.image_url" v-if="file?.image_url" class="cursor-pointer" />
<SwiperPlayer class="cursor-pointer" :videoUrl="file?.video_url"
v-else-if="file?.video_url" :isPlaying="isPlaying && (activeIndex === index)" />
<div v-else>
<!-- //TODO -->
</div>
</swiper-slide>
</swiper>
</div>
</div>
<div class="left-panel empty-bg" v-else></div>
<!-- 右侧表格区域 -->
<div class="flex-1 right-panel">
<div class="bg-transparent baseTable_wrap">
<BaseTable class="bg-transparent baseTable_box" :total="pagination.total"
:pageSize="pagination.pageSize" :dataSource="listData" :isFixedPagination="true"
:columns="columns" :page="pagination.currentPage" @change="handleTableChange"
:row-class-name="handleRowClassName" @row-click="handleRowClick">
</BaseTable>
</div>
</div>
</div>
</div>
<AlarmModal v-model:value="isAlarmOpen" :info="currentRow" :image="currFileList" @close="isAlarmOpen = false" />
<DeleteModal v-model:value="isDeleteOpen" @delete-success="getList()" :info="currentRow" @close="isDeleteOpen = false" />
<!-- <div class="bg_footer_desp">
</div> -->
</div>
</template>
<script lang="ts" setup>
import { onBeforeRouteLeave } from 'vue-router';
import Player from '@/components/videoPlayer/Player.vue'
import ContentHeader from '@/components/ContentHeader.vue';
import { BaseTable } from "@/components/CustomTable";
import SwiperPlayer from './components/SwiperPlayer.vue'
import { Swiper, SwiperSlide } from "swiper/vue";
import { Navigation, Scrollbar } from "swiper/modules";
import { getAppearanceMonitorApi, getAppearanceMonitorDetailApi } from '@/api/dashboard';
import { isSuccessApi } from "@/utils/forApi";
import AlarmModal from './components/AlarmModal.vue'
import DeleteModal from './components/DeleteModal.vue'
import { useWebSocketStore } from '@/stores/websocketStore';
import "swiper/css";
import 'swiper/scss';
import 'swiper/scss/navigation';
defineOptions({
name: "PoleMonitorIndex"
});
const modules = [Navigation, Scrollbar];
const activeIndex = ref(-1);
const swiperRef = ref(null);
const columns = [
{
label: "站点",
property: "station",
width: 80,
},
{
label: "车号",
property: "train_number",
width: 155,
},
{
label: "车型",
property: "train_model",
width: 70,
},
{
label: "车厢号",
property: "train_carriage_number",
width: 95,
},
{
label: "告警类型",
property: "alarm_type",
width: 90,
},
{
label: "故障类型",
property: "fault_type",
width: 100,
},
{
label: "等级",
property: "level",
width: 60,
},
// {
// label: "",
// property: "is_reviewed",
// formatter: ({ is_reviewed }) => {
// return is_reviewed === true
// ? ""
// : "";
// },
// width: 80,
// },
{
label: "时间",
property: "created_at"
},
{
slot: "operation",
label: "操作",
width: 120,
formatter: (row) => {
return h(
"div",
{
style: {
fontSize: "14px",
color:"#37EBFF"
}
},
[
// h("i", {
// class: `iconfont icon-zishebeizu pr-[8px]`
// }),
h(
"span",
{
fontSize: "14px",
class: "pf-1",
},
[
h("i",
{
style: {
fontSize: "14px",
letterSpacing: "2px",
marginRight: "4px",
color:"#009DFF"
},
onClick: (row) => {
// console.log(row.id);
//
isAlarmOpen.value = true;
currentRow.value = row;
}
},
"详情"
),
h("i",
{
style: {
fontSize: "14px",
letterSpacing: "2px",
marginRight: "4px",
color:"#FF2727"
},
onClick: (row) => {
// console.log(row.id);
//
isDeleteOpen.value = true;
currentRow.value = row;
}
},
"删除"
),
]
)
]
);
}
}
]
const pagination = ref({ currentPage: 1, pageSize: 10, total: 0 });
const listData = ref<Record<string, any>[]>([]); //
const currentRow = ref<Record<string, any>>({}); //
const currFileList = ref<Record<string, any>[]>([]); //
const currFile = ref<Record<string, any>>({}); //
const isPlaying = ref<boolean>(false); //
const searchForm = reactive({
train_number: "",
train_carriage_number: "",
fault_type: "",
station: "",
type: "pole"
});
const dataLoading = ref(true);
const isAlarmOpen = ref<Boolean>(false); //
const isDeleteOpen = ref<Boolean>(false); //
const websocketStore = useWebSocketStore();
// messages
watch(() => websocketStore.messages, (newMessages: string[], oldMessages: string[]) => {
if(newMessages?.length > 0 && !isAlarmOpen.value) {
currentRow.value = newMessages[newMessages?.length - 1];
currFileList.value = newMessages[newMessages?.length - 1]?.images;
isAlarmOpen.value = true;
}
}, { deep: true, immediate: true });
// const isCurrPlaying = computed(() => {
// return (index) => {
// return isPlaying.value && (activeIndex === index)
// }
// })
const togglePlay = () => {
isPlaying.value = !isPlaying.value;
};
const handleSlideClick = (index) => {
if (activeIndex.value === index) {
togglePlay() //
} else {
isPlaying.value = false;
activeIndex.value = index;
currFile.value = currFileList.value[index]
}
};
const onSwiper = (swiper) => {
swiperRef.value = swiper;
console.log('Swiper 实例已获取:', swiper);
};
const onSlideChange = () => {
console.log("slide change");
};
//
const getFileList = async () => {
try {
const res = await getAppearanceMonitorDetailApi({ id: currentRow.value?.id, current: 1, pageSize: 1000 })
console.log(res.data, 'getDetailList_data')
if (isSuccessApi(res)) {
currFileList.value = res.data.data;
currFile.value = res.data.data[0];
activeIndex.value = 0;
}
} catch (error) {
console.log(error, 'getDetailList_error')
}
}
// TODO mock
// const getFileList = async () => {
// try {
// const resAll = await fetch('/api/v1/record/record_detail_list/', {
// method: 'POST'
// })
// const res = await resAll.json()
// if (isSuccessApi(res)) {
// currFileList.value = res.data.data;
// currFile.value = res.data.data[0];
// activeIndex.value = 0;
// }
// } catch (error) {
// console.error(':', error)
// }
// }
function loadDetail() {
currentRow.value = listData.value[0]
getFileList()
}
//
const getList = async () => {
try {
const { currentPage, pageSize } = pagination.value;
const res = await getAppearanceMonitorApi({ ...searchForm, current: currentPage, pageSize })
console.log(res.data, 'getList_data')
if (isSuccessApi(res)) {
listData.value = res.data.data;
if (listData.value?.length > 0) {
loadDetail()
pagination.value = {
...pagination.value,
total: res.data.total
};
}
}
} catch (error) {
console.error('获取数据失败:', error)
}
};
//
const handleQuery = () => {
getList()
};
//
const handleReset = () => {
searchForm.train_number = '';
searchForm.station = '';
searchForm.train_carriage_number = '';
searchForm.fault_type = '';
getList()
};
//
function handleTableChange(record) {
console.log("handleTableChange_record", record);
pagination.value = {
...pagination.value,
currentPage: record.page,
pageSize: record.pageSize
};
getList();
}
//
const handleRowClassName = ({ row }) => {
return row.id === currentRow.value.id ? 'selected-row' : '';
};
//
const handleRowClick = (row, event, rowIndex) => {
currentRow.value = row;
getFileList()
};
onBeforeRouteLeave(() => {
isAlarmOpen.value = false;
currentRow.value = {};
currFileList.value = [];
});
onMounted(() => {
getList();
});
</script>
<style lang="scss">
@import url('./PoleMonitor.scss');
</style>

@ -1,21 +0,0 @@
.vehicl-management-wrap{
height: 813px;
background-image: url("@/assets/common/device_status_bg_line.png");
background-size: 100% 100%;
background-position: bottom;
background-repeat: no-repeat;
}
.vehicl-management-content-box{
.el-scrollbar__view {
background: transparent !important;
height: 600px;
}
.el-table__inner-wrapper{
background-color: transparent !important;
}
.el-table__body-wrapper, .el-scrollbar__wrap, .el-scrollbar{
background: transparent !important;
}
}

@ -1,145 +0,0 @@
<template>
<div class="bg_basic_content">
<div class="vehicl-management-wrap">
<div class="vehicl-management-header mt-[32px]">
<ContentHeader bgLayout="1855">
<template #title>
<div class="w-[200px] bg_title bg_title_7">
</div>
</template>
</ContentHeader>
</div>
<div class="px-[16px] vehicl-management-content-box">
<div class="mt-[16px] bg-transparent baseTable_wrap full_table">
<BaseTable class="bg-transparent baseTable_box" :total="pagination.total"
:pageSize="pagination.pageSize" :dataSource="listData" :isFixedPagination="true"
:columns="columns" :page="pagination.currentPage" @change="handleTableChange">
<template v-slot:actionBar="{ row }">
<ul class="flex table_action_box">
<li class="flex items-center mr-[16px]" @click="openCurrent(row)">
<el-button text>
<span :style="{
fontSize: '14px',
color: '#37DBFF'
}">
查看详情
</span>
</el-button>
</li>
</ul>
</template>
</BaseTable>
</div>
</div>
<VehiclModal v-model:value="isVehiclOpen" :info="currentRow" :image="currFileList" @close="isVehiclOpen = false" />
<AlarmModal v-model:value="isAlarmOpen" :info="currentDetailRow" :image="currFileList" @close="isAlarmOpen = false" />
</div>
</div>
</template>
<script setup lang="ts">
import { BaseTable } from "@/components/CustomTable";
import ContentHeader from '@/components/ContentHeader.vue';
import { getVehiclManagementApi } from '@/api/dashboard';
import { isSuccessApi } from "@/utils/forApi";
import AlarmModal from './components/AlarmModal.vue'
import { useWebSocketStore } from '@/stores/websocketStore';
import { onBeforeRouteLeave } from 'vue-router';
import VehiclModal from "./components/VehiclModal.vue";
defineOptions({
name: "VehiclManagementWrap"
});
const currentRow = ref<Record<string, any>>({});
const isAlarmOpen = ref<Boolean>(false); //
const isVehiclOpen = ref<Boolean>(false); //
const currentDetailRow = ref<Record<string, any>>({}); //
const currFileList = ref<Record<string, any>[]>([]); //
const websocketStore = useWebSocketStore();
// messages
watch(() => websocketStore.messages, (newMessages: string[], oldMessages: string[]) => {
if(newMessages?.length > 0 && !isAlarmOpen.value) {
currentDetailRow.value = newMessages[newMessages?.length - 1];
currFileList.value = newMessages[newMessages?.length - 1]?.images;
isAlarmOpen.value = true;
}
}, { deep: true, immediate: true });
const columns = [
{
label: "车辆ID",
property: "train_id"
},
{
label: "车厢数量",
property: "carriage_account"
},
{
label: "入场时间",
property: "arrive_at"
},
{
label: "出场时间",
property: "leave_at"
},
{
label: "停留时间",
property: "stay_duration"
},
{
type: "action",
label: "操作"
}
];
const pagination = ref({ currentPage: 1, pageSize: 10, total: 0 });
const listData = ref([]);
const getList = async () => {
try {
const { currentPage, pageSize } = pagination.value;
const res = await getVehiclManagementApi({ current: currentPage, pageSize })
console.log(res.data, 'getList_data')
if (isSuccessApi(res)) {
listData.value = res.data.data;
pagination.value = {
...pagination.value,
total: res.data.total
};
}
} catch (error) {
console.error('获取数据失败:', error)
}
}
function handleTableChange(record) {
console.log("handleTableChange_record", record);
pagination.value = {
...pagination.value,
currentPage: record.page,
pageSize: record.pageSize
};
getList();
}
/**查看详情 */
function openCurrent(row) {
console.log(row, "openCurrent");
currentRow.value = row;
isVehiclOpen.value = true;
}
onBeforeRouteLeave(() => {
isAlarmOpen.value = false;
currentDetailRow.value = {};
currFileList.value = [];
});
onMounted(() => {
getList();
});
</script>
<style lang="scss">
@import url('./VehiclManagement.scss');
</style>

@ -1,253 +0,0 @@
<template>
<el-dialog class="alarmModal-wrap" v-model="show" @close="handleClose">
<!-- 自定义标题栏 -->
<template #header="{ close, titleId, titleClass }">
<div class="flex items-center justify-between alarm-dialog-header">
<div class="flex items-center justify-center header-left">
<div class="header-icon mr-[12px]"></div>
<p class="overflow-hidden whitespace-nowrap text-ellipsis max-w-[650px]">{{
"故障提示" }}</p>
</div>
</div>
</template>
<!-- 图片区域 -->
<div class="alarm-content">
<div class="alarm-content-top">
<template v-for="(item,index) in image" :key="index">
<img :src="item.image_url" alt="" v-if="item.image_url && index < 4"></img>
</template>
</div>
<div class="alarm-content-bottom">
<span class="alarm-content-bottom-title">列车信息:</span>
<div class="alarm-content-bottom-info">
<span class="mr-8">列车编号: <i>{{ info.train_number }}</i></span>
<span class="mr-8">车型: <i>{{ info.train_model }}</i></span>
<span>发生时间: <i>{{ info.created_at }}</i></span>
</div>
<div class="alarm-content-bottom-info">
<span class="mr-8">告警类型: <i>{{ info.alarm_type }}</i></span>
<span>故障类型: <i>{{ info.fault_type }}</i></span>
</div>
<div class="alarm-content-bottom-btn">
<el-button type="primary" @click="exportToExcel" class="alarm-btn">
<span class="icon"></span> 导出
</el-button>
</div>
</div>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import ExcelJS from 'exceljs';
interface Props {
/** 弹窗显隐 */
value: boolean;
info: Record<string, any>;
image: any;
}
interface Emits {
(e: "update:value", val: boolean): void;
}
const props = withDefaults(defineProps<Props>(), {
value: false,
info: {},
image: []
});
const emit = defineEmits<Emits>();
//
const handleClose = () => {
// emits('close');
};
const show = computed({
get() {
return props.value;
},
set(val: boolean) {
emit("update:value", val);
}
});
// Excel
const exportToExcel = async () => {
const { info, image } = props;
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Sheet1');
//
const data = [
['列车编号', '车型', '发生时间', '告警类型', '故障类型'],
[info.train_number, info.train_model, info.created_at, info.alarm_type, info.fault_type]
];
worksheet.addRows(data);
//
if (image.length > 0) {
for (let i = 0; i < Math.min(image.length, 4); i++) {
const imgUrl = image[i].image_url;
try {
console.log(`开始加载图片: ${imgUrl}`);
const response = await fetch(imgUrl);
if (!response.ok) {
console.error(`图片加载失败,状态码: ${response.status}URL: ${imgUrl}`);
continue;
}
const blob = await response.blob();
const arrayBuffer = await blob.arrayBuffer();
const imageId = workbook.addImage({
// 使 arrayBuffer
buffer: arrayBuffer,
extension: 'png',
});
worksheet.addImage(imageId, {
tl: { col: 0, row: i + 2 },
ext: { width: 200, height: 200 },
});
console.log(`图片 ${imgUrl} 已成功添加到 Excel`);
} catch (error) {
console.error(`图片处理过程中出错URL: ${imgUrl},错误信息:`, error);
}
}
}
try {
const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'alarm_info.xlsx';
link.click();
URL.revokeObjectURL(url);
} catch (error) {
console.error('导出 Excel 文件时出错:', error);
}
};
</script>
<style lang="scss">
.alarmModal-wrap.el-dialog {
border: none;
overflow: hidden;
box-shadow: none;
background-color: transparent;
background-image: url("@/assets/common/bg_real_dialog.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
width: 805px;
height: 712px;
padding: 0;
// margin-top: calc(50vh - 316px);
.el-dialog__header.show-close {
padding: 0;
}
.el-dialog__close {
width: 56px;
height: 56px;
color: white;
font-size: 18px;
padding-top: 4px;
padding-right: 20px;
}
.alarm-dialog-header {
color: white;
padding: 0;
padding-top: 8px;
margin-bottom: 10px;
.header-left {
padding: 0 24px;
font-weight: bold;
font-size: 18px;
.header-icon {
margin-top: 4px;
width: 24px;
height: 48px;
background-image: url("@/assets/common/alarm_title.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
}
}
}
.alarm-content {
box-sizing: border-box;
padding: 0 24px;
width: 100%;
height: 100%;
.alarm-content-top {
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
justify-content: space-between; /* 水平方向均匀分布间距 */
align-content: space-between; /* 垂直方向均匀分布间距 */
width: 752px;
height: 448px;
img {
width: 368px;
height: 216px;
}
}
.alarm-content-bottom {
box-sizing: border-box;
margin-top: 12px;
.alarm-content-bottom-title {
box-sizing: border-box;
padding-left: 8px;
height: 22px;
font-weight: bold;
font-size: 14px;
color: #FFFFFF;
border-left: 3px solid #46A9ED;
line-height: 22px;
}
.alarm-content-bottom-info {
box-sizing: border-box;
font-size: 14px;
color: #FFFFFF;
i {
font-style: normal;
font-weight: bold;
}
}
.alarm-content-bottom-info:nth-of-type(1) {
margin: 4px 0;
}
.alarm-content-bottom-btn {
margin-top: 24px;
text-align: end;
.alarm-btn {
width: 96px;
height: 32px;
background: linear-gradient(180deg, #2589ff 0%, #46a9ed 100%);
border: 1px solid #42a5f5;
border-radius: 2px;
color: white;
margin-left: 0;
& .icon {
width: 14px;
height: 14px;
background-image: url("@/assets/common/export_icon.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
margin-right: 5px;
}
}
}
}
}
}
</style>

@ -1,447 +0,0 @@
<template>
<el-dialog class="appearanceAlarmModal" v-model="show" @close="handleClose">
<!-- 自定义标题栏 -->
<template #header="{ close, titleId, titleClass }">
<div class="flex items-center justify-between appearanceAlarm-dialog-header">
<div class="flex items-center justify-center header-left">
<div class="header-icon mr-[12px]"></div>
<p class="overflow-hidden whitespace-nowrap text-ellipsis max-w-[650px]">{{
"故障提示" }}</p>
</div>
</div>
</template>
<!-- 图片区域 -->
<div class="appearanceAlarm-content">
<div class="appearanceAlarm-content-top">
<!-- <template v-for="(item,index) in image" :key="index">
<img :src="item.image_url" alt="" v-if="index < 4"></img>
</template> -->
<div class="appearanceAlarm-content-top-left">
<span class="appearanceAlarm-content-top-title">故障前</span>
<div class="appearanceAlarm-content-top-left-img">
<div class="appearanceAlarm-content-top-img">
<img :src="imageBefore" alt=""></img>
</div>
<div class="appearanceAlarm-content-top-img-slider">
<swiper ref="swiperModalRef" :modules="modules" :slides-per-view="3" :space-between="10" navigation
:scrollbar="{ draggable: false }" :centered-slides="false" :observer="true" :observeParents="true"
@swiper="onSwiper" @slideChange="onSlideChange">
<swiper-slide v-for="(file, index) in beforeImage" :key="index" @click="handleSlideClick(index, 'before')"
:class="{ 'active-slide': activeBeforeIndex === index }">
<img :src="file.image_url" class="cursor-pointer" v-if="file.image_url" />
</swiper-slide>
</swiper>
</div>
</div>
</div>
<div class="appearanceAlarm-content-top-right">
<span class="appearanceAlarm-content-top-title">故障后</span>
<div class="appearanceAlarm-content-top-right-img">
<div class="appearanceAlarm-content-top-img">
<img :src="imageAfter" alt=""></img>
</div>
<div class="appearanceAlarm-content-top-img-slider">
<swiper ref="swiperModalRef" :modules="modules" :slides-per-view="3" :space-between="10" navigation
:scrollbar="{ draggable: false }" :centered-slides="false" :observer="true" :observeParents="true"
@swiper="onSwiper" @slideChange="onSlideChange">
<swiper-slide v-for="(file, index) in image" :key="index" @click="handleSlideClick(index, 'after')"
:class="{ 'active-slide': activeAfterIndex === index }">
<img :src="file.image_url" class="cursor-pointer" v-if="file.image_url" />
</swiper-slide>
</swiper>
</div>
</div>
</div>
</div>
<div class="appearanceAlarm-content-bottom">
<span class="appearanceAlarm-content-bottom-title">列车信息:</span>
<div class="appearanceAlarm-content-bottom-info">
<span class="mr-8">列车编号: <i>{{ info.train_number }}</i></span>
<span class="mr-8">车型: <i>{{ info.train_model }}</i></span>
<span>发生时间: <i>{{ info.created_at }}</i></span>
</div>
<div class="appearanceAlarm-content-bottom-info">
<span class="mr-8">告警类型: <i>{{ info.alarm_type }}</i></span>
<span>故障类型: <i>{{ info.fault_type }}</i></span>
</div>
<div class="appearanceAlarm-content-bottom-btn">
<el-button type="primary" @click="exportToExcel" class="appearanceAlarm-btn">
<span class="icon"></span> 导出
</el-button>
</div>
</div>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import ExcelJS from 'exceljs';
import { Swiper, SwiperSlide } from "swiper/vue";
import { Navigation, Scrollbar } from "swiper/modules";
import "swiper/css";
import 'swiper/scss';
import 'swiper/scss/navigation';
interface Props {
/** 弹窗显隐 */
value: boolean;
info: Record<string, any>;
image: any;
beforeImage: any;
}
interface Emits {
(e: "update:value", val: boolean): void;
}
const props = withDefaults(defineProps<Props>(), {
value: false,
info: {},
image: [],
beforeImage: []
});
const emit = defineEmits<Emits>();
const modules = [Navigation, Scrollbar];
const activeBeforeIndex = ref(-1);
const activeAfterIndex = ref(-1);
const swiperModalRef = ref(null);
const imageBefore = ref('');
const imageAfter = ref('');
const loading = ref(true);
//
const handleClose = () => {
// emits('close');
activeBeforeIndex.value = -1;
activeAfterIndex.value = -1;
// imageBefore.value = '';
// imageAfter.value = '';
};
const show = computed({
get() {
return props.value;
},
set(val: boolean) {
emit("update:value", val);
}
});
const onSwiper = (swiper) => {
swiperModalRef.value = swiper;
console.log('Swiper 实例已获取:', swiper);
};
const onSlideChange = () => {
console.log("slide change");
};
const handleSlideClick = (index:any, type:any) => {
if(type === 'before') {
activeBeforeIndex.value = index;
imageBefore.value = props.beforeImage[index]?.image_url;
} else if(type === 'after') {
activeAfterIndex.value = index;
imageAfter.value = props.image[index]?.image_url;
}
};
// Excel
const exportToExcel = async () => {
const { info, image } = props;
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Sheet1');
//
const data = [
['列车编号', '车型', '发生时间', '告警类型', '故障类型'],
[info.train_number, info.train_model, info.created_at, info.alarm_type, info.fault_type]
];
worksheet.addRows(data);
//
if (image.length > 0) {
for (let i = 0; i < Math.min(image.length, 4); i++) {
const imgUrl = image[i].image_url;
try {
console.log(`开始加载图片: ${imgUrl}`);
const response = await fetch(imgUrl);
if (!response.ok) {
console.error(`图片加载失败,状态码: ${response.status}URL: ${imgUrl}`);
continue;
}
const blob = await response.blob();
const arrayBuffer = await blob.arrayBuffer();
const imageId = workbook.addImage({
// 使 arrayBuffer
buffer: arrayBuffer,
extension: 'png',
});
worksheet.addImage(imageId, {
tl: { col: 0, row: i + 2 },
ext: { width: 200, height: 200 },
});
console.log(`图片 ${imgUrl} 已成功添加到 Excel`);
} catch (error) {
console.error(`图片处理过程中出错URL: ${imgUrl},错误信息:`, error);
}
}
}
try {
const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'alarm_info.xlsx';
link.click();
URL.revokeObjectURL(url);
} catch (error) {
console.error('导出 Excel 文件时出错:', error);
}
};
onMounted(() => {
//
setTimeout(() => {
console.log(show.value);
imageBefore.value = props.beforeImage[0]?.image_url;
imageAfter.value = props.image[0]?.image_url;
loading.value = false;
}, 2000); // 2
});
</script>
<style lang="scss">
.appearanceAlarmModal.el-dialog {
border: none;
overflow: hidden;
box-shadow: none;
background-color: transparent;
background-image: url("@/assets/common/bg_real_dialog.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
width: 816px;
height:609px;
padding: 0;
// margin-top: calc(50vh - 316px);
.el-dialog__header.show-close {
padding: 0;
}
.el-dialog__close {
width: 56px;
height: 56px;
color: white;
font-size: 18px;
padding-top: 4px;
padding-right: 20px;
}
.appearanceAlarm-dialog-header {
color: white;
padding: 0;
padding-top: 8px;
margin-bottom: 10px;
.header-left {
padding: 0 24px;
font-weight: bold;
font-size: 18px;
.header-icon {
margin-top: 4px;
width: 24px;
height: 48px;
background-image: url("@/assets/common/alarm_title.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
}
}
}
.appearanceAlarm-content {
box-sizing: border-box;
padding: 0 24px;
width: 100%;
height: 100%;
.appearanceAlarm-content-top {
box-sizing: border-box;
display: flex;
// flex-wrap: wrap;
justify-content: space-between; /* 水平方向均匀分布间距 */
align-content: space-between; /* 垂直方向均匀分布间距 */
width: 766px;
height: 360px;
// img {
// width: 368px;
// height: 216px;
// }
.appearanceAlarm-content-top-title {
display: inline-block;
width: 42px;
height: 20px;
margin: 16px 0 12px;
font-weight: bold;
font-size: 14px;
color: #FFFFFF;
}
.appearanceAlarm-content-top-left {
.appearanceAlarm-content-top-left-img {
display: flex;
flex-direction: column;
.appearanceAlarm-content-top-img {
box-sizing: border-box;
width: 368px;
height: 216px;
margin-bottom: 16px;
border-radius:4px;
img {
width: 100%;
height: 100%;
border-radius:2px;
}
}
.appearanceAlarm-content-top-img-slider {
width: 368px;
img {
width: 118px;
height: 74px;
}
}
}
}
.appearanceAlarm-content-top-right {
.appearanceAlarm-content-top-right-img {
position: relative;
display: flex;
flex-direction: column;
.appearanceAlarm-content-top-img {
box-sizing: border-box;
width: 368px;
height: 216px;
margin-bottom: 16px;
border-radius:4px;
img {
width: 100%;
height: 100%;
border-radius:2px;
}
}
.appearanceAlarm-content-top-img-slider {
width: 368px;
img {
width: 118px;
height: 74px;
}
}
}
.appearanceAlarm-content-top-right-img::before {
content: "";
position: absolute;
top: 0;
left: -16px;
border-left: 2px dashed rgba(255,255,255,0.6);
// width: 2px; /* 线 */
height: 100%; /* 分割线高度,根据需要调整 */
// margin-right: 10px; /* 线 */
}
}
.swiper {
width: 100%;
height: 100%;
.swiper-slide {
width: 20%;
border-radius:2px;
height: 74px;
img {
width: 100%;
height: 74px;
border-radius:2px;
object-fit: cover
}
}
.active-slide img {
border-radius:2px;
border: 2px solid #2ECCE0;
}
.swiper-button-prev,
.swiper-button-next {
background-color: rgba(0, 0, 0, 0.5);
color: white;
width: 20px;
height: 20px;
border-radius: 50%;
top: 50px;
}
.swiper-button-prev::after,
.swiper-button-next::after {
font-size: 12px ;
color: #FFF;
}
/* 修改按钮悬停样式 */
.swiper-button-prev:hover,
.swiper-button-next:hover {
background-color: rgba(0, 0, 0, 0.8);
}
}
}
.appearanceAlarm-content-bottom {
box-sizing: border-box;
margin-top: 12px;
.appearanceAlarm-content-bottom-title {
box-sizing: border-box;
padding-left: 8px;
height: 22px;
font-weight: bold;
font-size: 14px;
color: #FFFFFF;
border-left: 3px solid #46A9ED;
line-height: 22px;
}
.appearanceAlarm-content-bottom-info {
box-sizing: border-box;
font-size: 14px;
color: #FFFFFF;
i {
font-style: normal;
font-weight: bold;
}
}
.appearanceAlarm-content-bottom-info:nth-of-type(1) {
margin: 4px 0;
}
.appearanceAlarm-content-bottom-btn {
margin-top: 24px;
text-align: end;
.appearanceAlarm-btn {
width: 96px;
height: 32px;
background: linear-gradient(180deg, #2589ff 0%, #46a9ed 100%);
border: 1px solid #42a5f5;
border-radius: 2px;
color: white;
margin-left: 0;
& .icon {
width: 14px;
height: 14px;
background-image: url("@/assets/common/export_icon.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
margin-right: 5px;
}
}
}
}
}
}
</style>

@ -1,320 +0,0 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch, nextTick } from "vue";
import * as echarts from "echarts";
// props
const props = defineProps({
xData: {
type: Array as PropType<Array<string>>,
default: () => [],
},
legendArr: {
type: Array as PropType<Array<string>>,
default: () => [],
},
datas: {
type: Array as PropType<Array<Array<number>>>,
default: () => [],
},
colorArr: {
type: Array as PropType<Array<Array<string>>>,
default: () => [],
},
});
const chartContainer = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
// 3D
const CubeLeft = echarts.graphic.extendShape({
shape: { x: 0, y: 0 },
buildPath: (ctx, shape) => {
const { xAxisPoint } = shape;
const [c0, c1, c2, c3] = [
[shape.x, shape.y],
[shape.x - 12, shape.y - 6],
[xAxisPoint[0] - 12, xAxisPoint[1] - 6],
[xAxisPoint[0], xAxisPoint[1]],
];
ctx
.moveTo(c0[0], c0[1])
.lineTo(c1[0], c1[1])
.lineTo(c2[0], c2[1])
.lineTo(c3[0], c3[1])
.closePath();
},
});
const CubeRight = echarts.graphic.extendShape({
shape: { x: 0, y: 0 },
buildPath: (ctx, shape) => {
const { xAxisPoint } = shape;
const [c1, c2, c3, c4] = [
[shape.x, shape.y],
[xAxisPoint[0], xAxisPoint[1]],
[xAxisPoint[0] + 12, xAxisPoint[1] - 6],
[shape.x + 12, shape.y - 6],
];
ctx
.moveTo(c1[0], c1[1])
.lineTo(c2[0], c2[1])
.lineTo(c3[0], c3[1])
.lineTo(c4[0], c4[1])
.closePath();
},
});
const CubeTop = echarts.graphic.extendShape({
shape: { x: 0, y: 0 },
buildPath: (ctx, shape) => {
const [c1, c2, c3, c4] = [
[shape.x, shape.y],
[shape.x + 12, shape.y - 6],
[shape.x, shape.y - 12],
[shape.x - 12, shape.y - 6],
];
ctx
.moveTo(c1[0], c1[1])
.lineTo(c2[0], c2[1])
.lineTo(c3[0], c3[1])
.lineTo(c4[0], c4[1])
.closePath();
},
});
//
echarts.graphic.registerShape("CubeLeft", CubeLeft);
echarts.graphic.registerShape("CubeRight", CubeRight);
echarts.graphic.registerShape("CubeTop", CubeTop);
//
const initChart = () => {
if (!chartContainer.value) return;
chartInstance = echarts.init(chartContainer.value);
setTimeout(() => {
chartInstance?.resize();
updateChart();
}, 500);
};
//
const updateChart = () => {
if (!chartInstance) return;
const series = props.datas
.map((item, index) => [
{
type: "custom",
name: props.legendArr[index],
renderItem: (params, api) => ({
type: "group",
x: (index - props.datas.length / 2) * 30 + 15,
children: [
{
type: "CubeLeft",
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: api.coord([api.value(0), api.value(1)])[0],
y: api.coord([api.value(0), api.value(1)])[1],
xAxisPoint: api.coord([api.value(0), 0]),
},
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: props.colorArr[index % props.colorArr.length][1],
},
{
offset: 1,
color: props.colorArr[index % props.colorArr.length][0],
},
]),
},
},
{
type: "CubeRight",
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: api.coord([api.value(0), api.value(1)])[0],
y: api.coord([api.value(0), api.value(1)])[1],
xAxisPoint: api.coord([api.value(0), 0]),
},
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: props.colorArr[index % props.colorArr.length][1],
},
{
offset: 1,
color: props.colorArr[index % props.colorArr.length][0],
},
]),
},
},
{
type: "CubeTop",
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: api.coord([api.value(0), api.value(1)])[0],
y: api.coord([api.value(0), api.value(1)])[1],
xAxisPoint: api.coord([api.value(0), 0]),
},
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: props.colorArr[index % props.colorArr.length][1],
},
{
offset: 1,
color: props.colorArr[index % props.colorArr.length][1],
},
]),
},
},
],
}),
data: item,
},
{
type: "bar",
name: props.legendArr[index],
barWidth: 25,
// label: {
// normal: {
// show: true,
// position: "top",
// fontSize: 16,
// color: "#fff",
// offset: [0, -10],
// },
// },
itemStyle: { color: "transparent" },
data: item,
yAxisIndex: 0, // 使y
},
{
type: "bar",
name: props.legendArr[index],
barWidth: 25,
itemStyle: { color: "transparent" },
data: item,
yAxisIndex: 1, // 使y
},
])
.flat();
chartInstance.setOption({
tooltip: {
trigger: "axis",
borderWidth: 0,
backgroundColor: "rgba(8,36,68,.9)",
color: "#fff",
textStyle: { color: "#fff" },
formatter: (params) => {
let str = params[0].name + "</br>";
params.forEach((item, index) => {
if (item.seriesType === "custom") {
str += `
<div style='display:flex;justify-content:space-between;align-items:center'>
<div style='margin-right:20px;'>
<span style="display:inline-block;width:10px;height:10px;border-radius:5px;background-color:${
props.colorArr[index % props.colorArr.length][0]
}"></span>
&nbsp;${item.seriesName}
</div>
<span>&nbsp;${item.value ? item.value : "-"}</span>
</div>`;
}
});
return str;
},
},
grid: {
left: "5%",
right: "5%",
top: "5%",
bottom: "10%",
containLabel: true,
},
legend: {
left: "center",
top: "90%",
itemWidth: 12, //
itemHeight: 8,
textStyle: { color: "#fff", fontSize: 12 },
data: props.legendArr.map((name, index) => ({
name,
textStyle: { color: "#fff", fontSize: 12 },
itemStyle: { color: props.colorArr[index % props.colorArr.length][1] },
})),
},
xAxis: {
type: "category",
data: props.xData,
axisLine: { lineStyle: { color: "rgba(239, 247, 253, .1)" } },
axisLabel: { fontSize: 12, color: "#fff", margin: 12 },
},
yAxis: [
{
// name: "kWh",
// nameTextStyle: { color: "#fff", fontSize: 12 },
splitLine: {
lineStyle: { type: "dashed", color: "rgba(80,112,242,0.3)" },
},
axisLabel: { textStyle: { color: "#8C8C8C" }, fontSize: 12 },
// axisLine: { lineStyle: { color: "#8C8C8C" } },
// scale: true, //
},
{
// name: "kWh",
// nameTextStyle: { color: "#fff", fontSize: 12 },
splitLine: { lineStyle: { color: "transparent" } },
axisLabel: { textStyle: { color: "#8C8C8C" }, fontSize: 12 },
// axisLine: { lineStyle: { color: "#8C8C8C" } },
position: "right",
// scale: true, //
},
],
series,
});
};
const handleResize = () => {
chartInstance?.resize();
};
//
onMounted(async () => {
await nextTick();
initChart();
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
chartInstance?.resize();
}
});
//
watch(
() => props,
async () => {
await nextTick();
updateChart();
// delay(600).then(() => resize());
},
{
deep: true,
immediate: true,
}
);
</script>
<template>
<div ref="chartContainer" style="width: 100%; height: 100%" />
</template>

@ -1,157 +0,0 @@
<template>
<el-dialog class="deleteModal-wrap" v-model="show" @close="handleClose">
<!-- 自定义标题栏 -->
<template #header="{ close, titleId, titleClass }">
<div class="flex items-center justify-between delete-dialog-header">
<div class="flex items-center justify-center header-left">
<div class="header-icon mr-[12px]"></div>
<p class="overflow-hidden whitespace-nowrap text-ellipsis max-w-[650px]">{{
"确定删除吗?" }}</p>
</div>
</div>
</template>
<!-- 图片区域 -->
<div class="delete-content">
确定删除 <span>{{ info.alarm_type}}-{{ info.fault_type }}</span> 删除后将找不到此记录请谨慎操作.
</div>
<template #footer>
<div class="delete-footer">
<el-button class="delete-cancel" @click="handleClose"></el-button>
<el-button class="delete-confirm" @click="deleteData(info.id)">
确认
</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { deleteAppearanceMonitorApi } from '@/api/dashboard';
interface Props {
/** 弹窗显隐 */
value: boolean;
info: Record<string, any>;
}
interface Emits {
(e: "update:value", val: boolean): void;
(e: "delete-success"): void;
}
const props = withDefaults(defineProps<Props>(), {
value: false,
info: {}
});
const emit = defineEmits<Emits>();
//
const handleClose = () => {
emit("update:value", false);
};
//
const deleteData = async (id:any) => {
console.log(emit('delete-success'));
try {
const res = await deleteAppearanceMonitorApi({id: id})
console.log(res, 'deleteData')
if(res.code === 200) {
handleClose()
emit('delete-success');
}
} catch (error) {
console.error('获取数据失败:', error)
}
};
const show = computed({
get() {
return props.value;
},
set(val: boolean) {
emit("update:value", val);
}
});
</script>
<style lang="scss">
.deleteModal-wrap.el-dialog {
border: none;
overflow: hidden;
box-shadow: none;
background-color: transparent;
background-image: url("@/assets/common/bg_delete_dialog.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
width: 560px;
height: 226px;
padding: 0;
margin-top: calc(50vh - 316px);
.el-dialog__header.show-close {
padding: 0;
}
.el-dialog__close {
width: 56px;
height: 56px;
color: white;
font-size: 18px;
padding-top: 4px;
padding-right: 20px;
}
.delete-dialog-header {
color: white;
padding: 0;
padding-top: 32px;
padding-left: 16px;
.header-left {
padding: 0 24px;
// font-weight: bold;
font-size: 18px;
.header-icon {
// margin-top: 4px;
width: 24px;
height: 48px;
background-image: url("@/assets/common/warn_icon.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
}
}
}
.delete-content {
box-sizing: border-box;
margin-bottom: 30px;
padding-left: 76px;
font-weight: 400;
font-size: 14px;
color: #FFFFFF;
span {
font-weight: bold;
}
}
.delete-footer {
box-sizing: border-box;
padding-right: 32px;
.delete-cancel,
.delete-confirm {
width: 64px;
height: 32px;
font-size: 16px;
color: #FFFFFF;
background: transparent;
border-radius: 4px 4px 4px 4px;
border: 1px solid #CCCCCC;
}
.delete-confirm {
border: none;
background: linear-gradient( 180deg, #2589FF 0%, #46A9ED 100%);
}
}
}
</style>

@ -1,106 +0,0 @@
<!--
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-10 18:00:44
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-03-18 11:31:05
* @FilePath: \5G-Loading-Bay-Web\src\views\dashboard\components\DeviceStatus.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<script setup lang="ts">
import { ref } from "vue";
defineOptions({
name: "DeviceStatus"
});
const props = defineProps({
deviceStatus: {
type: Object,
default: () => {}
}
});
const deviceStatusOptions = ref<Record<string, any>[]>([
{
label: "在线",
color: "#52C41A",
bgColor: "#52C41A",
valueKey: "onlineCount" // 线
},
{
label: "离线",
color: "#ccc",
bgColor: "#999999",
valueKey: "outlineCount" //
},
{
label: "故障",
color: "#E80D0D",
bgColor: "#E80D0D",
valueKey: "errorCount" //
}
]);
</script>
<template>
<ul class="w-full text-sm deviceStatus_box">
<li
class="flex items-center justify-between w-full"
:style="{
marginBottom: '16px'
}"
v-for="(v, k) in deviceStatusOptions"
:key="k"
>
<div
class="flex items-center justify-center"
:style="{
backgroundColor: v.bgColor,
width: '40px',
height: '40px',
borderRadius: '4px',
marginRight: '12px'
}"
>
<div v-if="v.valueKey === 'onlineCount'" class="deviceStatusOnline"></div>
<div v-if="v.valueKey === 'errorCount'" class="deviceStatusError"></div>
<div v-if="v.valueKey === 'outlineCount'" class="deviceStatusOutline"></div>
</div>
<div class="flex flex-col flex-1">
<div class="flex justify-between" style="margin-bottom: 4px">
<span>{{ v.label }}</span>
<span>{{ deviceStatus?.[v.valueKey] }}</span>
</div>
<div class="w-full">
<el-progress
:show-text="false"
:stroke-width="8"
:percentage="deviceStatus?.[v.valueKey]"
:color="v.color"
/>
</div>
</div>
</li>
</ul>
</template>
<style lang="scss" scoped>
.deviceStatus_box {
li {
div {
.deviceStatusOnline {
width: 20px;
height: 20px;
background: url('@/assets/svg/deviceStatus/online.svg') no-repeat center center;
}
.deviceStatusError {
width: 20px;
height: 20px;
background: url('@/assets/svg/deviceStatus/error.svg') no-repeat center center;
}
.deviceStatusOutline {
width: 20px;
height: 20px;
background: url('@/assets/svg/deviceStatus/outline.svg') no-repeat center center;
}
}
}
}
</style>

@ -1,92 +0,0 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from "vue";
import * as echarts from "echarts";
const props = defineProps({
data: {
type: Array as PropType<Array<{ value: number; name: string }>>,
required: true,
},
colors: { type: Array as PropType<Array<string>>, default: () => [] },
});
const chartContainer = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
const colorsArr = ['#FFCC4A','#028FF5','#06EA7C','#8500FF','#FF7D05','#00D1FF']
//
const initChart = () => {
if (!chartContainer.value) return;
chartInstance = echarts.init(chartContainer.value);
setTimeout(() => {
chartInstance?.resize();
updateChart();
}, 500);
};
//
const updateChart = () => {
if (!chartInstance) return;
chartInstance.setOption({
legend: {
type: "scroll",
orient: "vertical",
left: "70%",
align: "left",
top: "middle",
itemWidth: 16, //
itemHeight: 8,
textStyle: { color: "#FFF" },
},
series: [
{
type: "pie",
radius: ["30%", "80%"],
center: ["35%", "50%"],
label: { show: false },
itemStyle: {
color: (params) =>
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: props.colors[params.dataIndex] },
{ offset: 1, color: colorsArr[params.dataIndex] },
]),
},
data: props.data,
},
],
});
};
//
onMounted(() => {
initChart();
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
});
//
watch(
() => props.data,
async () => {
await nextTick();
updateChart();
}
);
//
// onMounted(() => {
// window.addEventListener("resize", () => chartInstance?.resize());
// });
// onUnmounted(() => {
// window.removeEventListener("resize", () => chartInstance?.resize());
// });
</script>
<template>
<div ref="chartContainer" style="width: 100%; height: 100%" />
</template>

@ -1,92 +0,0 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from "vue";
import * as echarts from "echarts";
const props = defineProps({
data: {
type: Array as PropType<Array<{ value: number; name: string }>>,
required: true,
},
colors: { type: Array as PropType<Array<string>>, default: () => [] },
});
const chartContainer = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
const colorsArr = ['#3FE3FA','#FF4D00']
//
const initChart = () => {
if (!chartContainer.value) return;
chartInstance = echarts.init(chartContainer.value);
setTimeout(() => {
chartInstance?.resize();
updateChart();
}, 500);
};
//
const updateChart = () => {
if (!chartInstance) return;
chartInstance.setOption({
legend: {
type: "scroll",
orient: "vertical",
left: "70%",
align: "left",
top: "middle",
itemWidth: 16, //
itemHeight: 8,
textStyle: { color: "#FFF" },
},
series: [
{
type: "pie",
radius: ["40%", "80%"],
center: ["35%", "50%"],
label: { show: false },
itemStyle: {
color: (params) =>
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: props.colors[params.dataIndex] },
{ offset: 1, color: colorsArr[params.dataIndex] },
]),
},
data: props.data,
},
],
});
};
//
onMounted(() => {
initChart();
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
});
//
watch(
() => props.data,
async () => {
await nextTick();
updateChart();
}
);
//
// onMounted(() => {
// window.addEventListener("resize", () => chartInstance?.resize());
// });
// onUnmounted(() => {
// window.removeEventListener("resize", () => chartInstance?.resize());
// });
</script>
<template>
<div ref="chartContainer" style="width: 100%; height: 100%" />
</template>

@ -1,49 +0,0 @@
<template>
<div class="flex items-center justify-center w-full h-full position-relative">
<video ref="videoRef" :controls="false" muted :src="videoUrl" width="100%" height="144"
style="object-fit: cover;" @error="handleVideoError" v-if="!isVideoError"></video>
<div class="bg_error_img" v-if="isVideoError">
</div>
<div :class="{ 'bg_icon': true, 'playing': isPlaying }" v-if="!isVideoError">
<!-- {{ isPlaying }} -->
</div>
</div>
</template>
<script lang="ts" setup>
const props = defineProps<{
videoUrl: string;
isPlaying: boolean;
}>();
const videoRef = ref<HTMLVideoElement | null>(null);
const isVideoError = ref<boolean>(false);
const handleVideoError = () => {
console.log('handleVideoError')
isVideoError.value = true;
};
</script>
<style lang="scss" scoped>
.bg_error_img {
width: 100%;
height: 100%;
background: url("@/assets/common/load_file_error.png") no-repeat center center;
background-size: 50%;
border: 1px dashed red;
}
.bg_icon {
position: absolute;
width: 100%;
height: 100%;
background: url("@/assets/common/player_icon_1.png") no-repeat center center;
background-size: 40px;
&.playing {
background: url("@/assets/common/player_icon_2.png") no-repeat center center;
background-size: 40px;
}
}
</style>

@ -0,0 +1,13 @@
<!--
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-06-12 10:26:59
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-06-12 10:38:19
* @FilePath: \Web-Traffic-Police\src\views\dataView\components\Type1ObjectDetect.vue
* @Description: 目标检测
-->
<template>
<div>
1
</div>
</template>

@ -0,0 +1,13 @@
<!--
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-06-12 10:27:06
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-06-12 10:38:27
* @FilePath: \Web-Traffic-Police\src\views\dataView\components\Type2LicensePlateRecog.vue
* @Description: 车牌识别
-->
<template>
<div>
</div>
</template>

@ -0,0 +1,13 @@
<!--
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-06-12 10:37:10
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-06-12 10:38:40
* @FilePath: \Web-Traffic-Police\src\views\dataView\components\Type4AudioDetection.vue
* @Description: 递烟
-->
<template>
<div>
</div>
</template>

@ -0,0 +1,13 @@
<!--
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-06-12 10:37:10
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-06-12 10:38:40
* @FilePath: \Web-Traffic-Police\src\views\dataView\components\Type4AudioDetection.vue
* @Description: 音频检测
-->
<template>
<div>
</div>
</template>

@ -1,230 +0,0 @@
<template>
<el-dialog class="vehiclModal-wrap" v-model="show" @close="handleClose">
<!-- 自定义标题栏 -->
<template #header="{ close, titleId, titleClass }">
<div class="flex items-center justify-between vehicl-dialog-header">
<div class="flex items-center justify-center header-left">
<div class="header-icon mr-[12px]"></div>
<p class="overflow-hidden whitespace-nowrap text-ellipsis max-w-[650px]">{{
"列车图片" }}</p>
</div>
</div>
</template>
<!-- 图片区域 -->
<div class="vehicl-content">
<div class="vehicl-content-top">
<span class="vehicl-content-bottom-title">列车ID:</span>
<div class="vehicl-content-top-img">
<div class="vehicl-content-top-img-box">
<img :src="info.arrive_img_url" alt=""></img>
</div>
<div class="vehicl-content-top-img-box">
<img :src="info.leave_img_url" alt=""></img>
</div>
</div>
</div>
<div class="vehicl-content-bottom">
<span class="vehicl-content-bottom-title">列车与车厢号</span>
<div class="vehicl-content-bottom-vehicl">
<div class="vehicl-content-bottom-vehicl-header"></div>
<div :class="['vehicl-content-bottom-vehicl-body-box', { 'high-height': info?.data?.train_data?.length > 15 }]">
<div class="vehicl-content-bottom-vehicl-body" v-for="(item, index) in info?.data?.train_data" :key="index">
<div>{{ item.model }}</div>
<div>
<!-- <span class="mr-3">04</span> -->
<span>{{ item.carriage_number }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
interface Props {
/** 弹窗显隐 */
value: boolean;
info: Record<string, any>;
image: any;
}
interface Emits {
(e: "update:value", val: boolean): void;
}
const props = withDefaults(defineProps<Props>(), {
value: false,
info: {},
image: []
});
const emit = defineEmits<Emits>();
//
const handleClose = () => {
// emits('close');
};
const show = computed({
get() {
return props.value;
},
set(val: boolean) {
emit("update:value", val);
}
});
//
const itemCount = 16; //
</script>
<style lang="scss">
.vehiclModal-wrap.el-dialog {
border: none;
overflow: hidden;
box-shadow: none;
background-color: transparent;
background-image: url("@/assets/common/bg_real_dialog.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
width: 1200px;
height: 630px;
padding: 0;
.el-dialog__header.show-close {
padding: 0;
}
.el-dialog__close {
width: 56px;
height: 56px;
color: white;
font-size: 18px;
padding-top: 4px;
padding-right: 20px;
}
.vehicl-dialog-header {
color: white;
padding: 0;
padding-top: 8px;
margin-bottom: 20px;
.header-left {
padding: 0 24px;
font-weight: bold;
font-size: 18px;
.header-icon {
margin-top: 4px;
width: 24px;
height: 48px;
background-image: url("@/assets/common/alarm_title.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
}
}
}
.vehicl-content {
box-sizing: border-box;
padding: 0 24px;
width: 100%;
height: 100%;
.vehicl-content-top {
box-sizing: border-box;
width: 100%;
height: 378px;
.vehicl-content-top-img {
box-sizing: border-box;
padding-top: 16px;
display: flex;
flex-wrap: wrap;
justify-content: space-between; /* 水平方向均匀分布间距 */
align-content: space-between; /* 垂直方向均匀分布间距 */
}
img {
width: 568px;
height: 334px;
}
}
.vehicl-content-bottom {
box-sizing: border-box;
margin-top: 12px;
.vehicl-content-bottom-vehicl {
box-sizing: border-box;
padding-top: 16px;
display: flex;
// align-items: flex-end;
width: 100%;
height: 98px;
.vehicl-content-bottom-vehicl-header {
margin-right: 8px;
width: 82px;
height: 82px;
background-image: url("@/assets/common/vehicl_header.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
.vehicl-content-bottom-vehicl-body-box,
.high-height {
box-sizing: border-box;
width: 100%;
display: flex;
align-items: flex-end;
overflow-x: auto; /* 开启横向滚动 */
white-space: nowrap; /* 防止车厢内容换行 */
// background-color: #003366; /* */
padding: 0; /* 去除默认内边距 */
margin: 0; /* 去除默认外边距 */
/* 自定义滚动条样式 */
&::-webkit-scrollbar {
height: 8px; /* 滚动条高度 */
background-color: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: #003366; /* 滚动条滑块颜色 */
border-radius: 4px; /* 滑块圆角 */
}
.vehicl-content-bottom-vehicl-body {
box-sizing: border-box;
margin-right: 8px;
padding: 12px 8px;
width: 80px;
height: 66px;
background-image: url("@/assets/common/vehicl_body.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
font-weight: 500;
font-size: 12px;
color: #154DDD;
}
}
.high-height {
height: 90px;
}
}
}
.vehicl-content-bottom-title {
width: 100%;
box-sizing: border-box;
padding-left: 8px;
height: 22px;
font-weight: bold;
font-size: 14px;
color: #FFFFFF;
border-left: 3px solid #46A9ED;
line-height: 22px;
}
}
}
</style>

@ -1,21 +0,0 @@
self.onmessage = function (e) {
console.log('Received message:', e.data);
const { positions, gridSize, grid } = e.data;
const numPoints = positions.length / 3;
console.log('Number of points:', numPoints);
// 计算每个点的密度
const densities = [];
for (let i = 0; i < numPoints; i++) {
const x = positions[i * 3];
const y = positions[i * 3 + 1];
const z = positions[i * 3 + 2];
const gridX = Math.floor(x / gridSize);
const gridY = Math.floor(y / gridSize);
const gridZ = Math.floor(z / gridSize);
const key = `${gridX},${gridY},${gridZ}`;
densities.push(grid[key] || 0);
}
self.postMessage(densities);
};

@ -0,0 +1,104 @@
{
"titles": ["\u76ee\u6807\u68c0\u6d4b"],
"video_url": "http://110.40.131.100:8106/media/test_video/217.mp4",
"\u4eba\u673a\u5206\u79bb": false,
"\u4eba\u673a\u5206\u79bb\u65f6\u95f4": [],
"\u4eba\u8138\u56fe\u7247": [],
"\u544a\u77e5\u5904\u7f5a\u5185\u5bb9": false,
"\u544a\u77e5\u590d\u8bae\u8bc9\u8bbc\u7b49\u6551\u6d4e\u9014\u5f84": false,
"\u544a\u77e5\u6267\u6cd5\u4f9d\u636e": false,
"\u544a\u77e5\u6267\u6cd5\u5168\u7a0b\u88ab\u8bb0\u5f55": false,
"\u544a\u77e5\u6c11\u8b66\u8eab\u4efd": false,
"\u544a\u77e5\u7533\u8fa9\u6743\u529b": false,
"\u544a\u77e5\u8fdd\u6cd5\u4e8b\u5b9e": false,
"\u591a\u6b21\u5439\u6c14": false,
"\u591a\u6b21\u5439\u6c14\u65f6\u95f4": [],
"\u6838\u67e5\u5f53\u4e8b\u4eba\u8eab\u4efd\u4fe1\u606f": false,
"\u76ee\u6807\u68c0\u6d4b": {
"\u4eba": [
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/17.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/5.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/18.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/27.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/29.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/11.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/22.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/30.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/23.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/14.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/31.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/4.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/28.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/26.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/9.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/7.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/24.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/20.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/2.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/25.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/6.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/19.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/12.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/13.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/10.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/1.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/16.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/3.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/8.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/21.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4eba/15.jpg"
],
"\u4fe1\u53f7\u706f": [
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u4fe1\u53f7\u706f/1.jpg"
],
"\u8f66": [
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/17.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/5.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/18.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/27.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/11.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/22.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/23.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/14.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/4.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/28.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/26.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/9.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/7.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/24.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/20.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/2.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/25.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/6.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/19.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/12.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/13.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/10.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/1.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/16.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/3.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/8.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/21.jpg",
"http://110.40.131.100:8106/images/\u76ee\u6807\u68c0\u6d4b/object_det/\u8f66/15.jpg"
]
},
"\u89c6\u9891\u635f\u574f": false,
"\u8bed\u901f": 0,
"\u8bed\u97f3\u89d2\u8272": {},
"\u8f66\u724c\u53f7": [],
"\u8f66\u724c\u56fe\u7247": [],
"\u8f66\u724c\u989c\u8272": [],
"\u8fc7\u6fc0\u884c\u4e3a": false,
"\u8fc7\u6fc0\u884c\u4e3a\u65f6\u95f4": [],
"\u9003\u907f\u9152\u7cbe\u68c0\u6d4b": false,
"\u9003\u907f\u9152\u7cbe\u68c0\u6d4b\u65f6\u95f4": [],
"\u9012\u70df": false,
"\u9012\u70df\u65f6\u95f4": [],
"\u9012\u94b1": false,
"\u9012\u94b1\u65f6\u95f4": [],
"\u906e\u6321": false,
"\u906e\u6321\u65f6\u95f4": [],
"\u906e\u6321\u8f66\u724c": false,
"\u906e\u6321\u8f66\u724c\u65f6\u95f4": [],
"\u9152\u7cbe\u5ea6\u6570": 0
}

@ -0,0 +1,79 @@
{
"titles": ["\u8f66\u724c\u8bc6\u522b"],
"video_url": "http://110.40.131.100:8106/media/test_video/213.mp4",
"\u4eba\u673a\u5206\u79bb": false,
"\u4eba\u673a\u5206\u79bb\u65f6\u95f4": [],
"\u4eba\u8138\u56fe\u7247": [],
"\u544a\u77e5\u5904\u7f5a\u5185\u5bb9": false,
"\u544a\u77e5\u590d\u8bae\u8bc9\u8bbc\u7b49\u6551\u6d4e\u9014\u5f84": false,
"\u544a\u77e5\u6267\u6cd5\u4f9d\u636e": false,
"\u544a\u77e5\u6267\u6cd5\u5168\u7a0b\u88ab\u8bb0\u5f55": false,
"\u544a\u77e5\u6c11\u8b66\u8eab\u4efd": false,
"\u544a\u77e5\u7533\u8fa9\u6743\u529b": false,
"\u544a\u77e5\u8fdd\u6cd5\u4e8b\u5b9e": false,
"\u591a\u6b21\u5439\u6c14": false,
"\u591a\u6b21\u5439\u6c14\u65f6\u95f4": [],
"\u6838\u67e5\u5f53\u4e8b\u4eba\u8eab\u4efd\u4fe1\u606f": false,
"\u76ee\u6807\u68c0\u6d4b": {},
"\u89c6\u9891\u635f\u574f": false,
"\u8bed\u901f": 0,
"\u8bed\u97f3\u89d2\u8272": {},
"\u8f66\u724c\u53f7": [
"\u9655E\u00b7J3333",
"\u9655U\u00b7666B6",
"\u9655E\u00b7N2222",
"\u9655U\u00b7CR032",
"\u9655U\u00b7D9999",
"\u9655A\u00b700026",
"\u9655A\u00b788D88",
"\u9655E\u00b7Y7777",
"\u9655A\u00b7S0325",
"\u9655E\u00b739999",
"\u9655K\u00b744444",
"\u9655E\u00b7K1111",
"\u7518M\u00b733333"
],
"\u8f66\u724c\u56fe\u7247": [
"http://110.40.131.100:8106/images/\u8f66\u724c_2/license_plate/\u9655E\u00b7J3333.jpg",
"http://110.40.131.100:8106/images/\u8f66\u724c_2/license_plate/\u9655U\u00b7666B6.jpg",
"http://110.40.131.100:8106/images/\u8f66\u724c_2/license_plate/\u9655E\u00b7N2222.jpg",
"http://110.40.131.100:8106/images/\u8f66\u724c_2/license_plate/\u9655U\u00b7CR032.jpg",
"http://110.40.131.100:8106/images/\u8f66\u724c_2/license_plate/\u9655U\u00b7D9999.jpg",
"http://110.40.131.100:8106/images/\u8f66\u724c_2/license_plate/\u9655A\u00b700026.jpg",
"http://110.40.131.100:8106/images/\u8f66\u724c_2/license_plate/\u9655A\u00b788D88.jpg",
"http://110.40.131.100:8106/images/\u8f66\u724c_2/license_plate/\u9655E\u00b7Y7777.jpg",
"http://110.40.131.100:8106/images/\u8f66\u724c_2/license_plate/\u9655A\u00b7S0325.jpg",
"http://110.40.131.100:8106/images/\u8f66\u724c_2/license_plate/\u9655E\u00b739999.jpg",
"http://110.40.131.100:8106/images/\u8f66\u724c_2/license_plate/\u9655K\u00b744444.jpg",
"http://110.40.131.100:8106/images/\u8f66\u724c_2/license_plate/\u9655E\u00b7K1111.jpg",
"http://110.40.131.100:8106/images/\u8f66\u724c_2/license_plate/\u7518M\u00b733333.jpg"
],
"\u8f66\u724c\u989c\u8272": [
"\u9ec4",
"\u9ec4",
"\u9ec4",
"\u9ec4",
"\u9ec4",
"\u9ec4",
"\u9ec4",
"\u9ec4",
"\u9ec4",
"\u9ec4",
"\u9ec4",
"\u9ec4",
"\u9ec4"
],
"\u8fc7\u6fc0\u884c\u4e3a": false,
"\u8fc7\u6fc0\u884c\u4e3a\u65f6\u95f4": [],
"\u9003\u907f\u9152\u7cbe\u68c0\u6d4b": false,
"\u9003\u907f\u9152\u7cbe\u68c0\u6d4b\u65f6\u95f4": [],
"\u9012\u70df": false,
"\u9012\u70df\u65f6\u95f4": [],
"\u9012\u94b1": false,
"\u9012\u94b1\u65f6\u95f4": [],
"\u906e\u6321": false,
"\u906e\u6321\u65f6\u95f4": [],
"\u906e\u6321\u8f66\u724c": false,
"\u906e\u6321\u8f66\u724c\u65f6\u95f4": [],
"\u9152\u7cbe\u5ea6\u6570": 0
}

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save