feat: 拆分主模块
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 342 KiB |
Before Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 225 KiB |
Before Width: | Height: | Size: 194 KiB |
Before Width: | Height: | Size: 400 KiB |
Before Width: | Height: | Size: 352 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 142 KiB |
Before Width: | Height: | Size: 317 KiB |
Before Width: | Height: | Size: 713 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 248 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 855 B |
Before Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 915 B |
Before Width: | Height: | Size: 711 B |
Before Width: | Height: | Size: 876 B |
Before Width: | Height: | Size: 922 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 955 B |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 420 B |
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 |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 563 B |
Before Width: | Height: | Size: 208 KiB |
Before Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 189 KiB |
Before Width: | Height: | Size: 214 KiB |
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 800 KiB After Width: | Height: | Size: 800 KiB |
@ -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>
|
@ -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,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,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>
|
||||
${item.seriesName}
|
||||
</div>
|
||||
<span> ${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,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,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
|
||||
}
|