大屏自适应,监控摄像头心跳机制

This commit is contained in:
wenxin 2024-12-15 17:21:39 +08:00
parent 187a350e15
commit a2dbfe7fd1
16 changed files with 386 additions and 240 deletions

View File

@ -19,5 +19,5 @@ const BASE_API = {
// 是否开启国际化
I18N: false,
// 管理员角色编码
MANAGER_ROLE: '2101'
MANAGER_ROLE: '2101',
}

12
package-lock.json generated
View File

@ -13,6 +13,7 @@
"@element-plus/icons-vue": "^2.3.1",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"autofit.js": "^3.2.2",
"axios": "^1.6.8",
"coordtransform": "^2.1.2",
"countup.js": "^2.8.0",
@ -1771,6 +1772,12 @@
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/autofit.js": {
"version": "3.2.2",
"resolved": "https://registry.npmmirror.com/autofit.js/-/autofit.js-3.2.2.tgz",
"integrity": "sha512-oo6e5GGhYKrpIgn6/KHzbtvWKm+dk6925wJoz8XGhKw2tlzc+dF9TBaJL/bOXnmpeRR82oxWLBOcOi2WDa6DIw==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
@ -5462,6 +5469,11 @@
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"autofit.js": {
"version": "3.2.2",
"resolved": "https://registry.npmmirror.com/autofit.js/-/autofit.js-3.2.2.tgz",
"integrity": "sha512-oo6e5GGhYKrpIgn6/KHzbtvWKm+dk6925wJoz8XGhKw2tlzc+dF9TBaJL/bOXnmpeRR82oxWLBOcOi2WDa6DIw=="
},
"axios": {
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",

View File

@ -14,6 +14,7 @@
"@element-plus/icons-vue": "^2.3.1",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"autofit.js": "^3.2.2",
"axios": "^1.6.8",
"coordtransform": "^2.1.2",
"countup.js": "^2.8.0",

View File

@ -20,6 +20,10 @@ import other from '/@/utils/other';
import { Local, Session } from '/@/utils/storage';
import mittBus from '/@/utils/mitt';
import setIntroduction from '/@/utils/setIconfont';
import { useCommon } from './stores/common';
import autofit from "autofit.js";
import screenfull from 'screenfull';
const commonStore = useCommon();
//
const LockScreen = defineAsyncComponent(() => import('/@/layout/lockScreen/index.vue'));
@ -67,7 +71,11 @@ onBeforeMount(() => {
});
//
onMounted(() => {
commonStore.createWebsocket();
nextTick(() => {
autofit.init()
// '
mittBus.on('openSetingsDrawer', () => {
setingsRef.value.openDrawer();
@ -85,7 +93,9 @@ onMounted(() => {
});
// /i18n
onUnmounted(() => {
mittBus.off('openSetingsDrawer', () => {});
mittBus.off('openSetingsDrawer', () => { });
commonStore.closeWs();
autofit.off()
});
//
watch(
@ -97,4 +107,59 @@ watch(
deep: true,
}
);
function isFullScreen() {
let isFull = false;
if (document.fullscreenElement !== null) {
isFull = true;
} else {
let availWidth = window.screen.availWidth;
let availHeight = window.screen.availHeight;
let innerWidth = window.innerWidth;
let innerHeight = window.innerHeight;
if (innerHeight >= availHeight) {
isFull = true;
}
} return isFull;
}
</script>
<style lang="scss">
body {
height: 100%;
overflow: hidden;
}
.control-buttons {
color: #fff;
position: absolute;
height: 15.5px;
bottom: 70px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 43px;
button {
border: none;
background: #375985;
padding: 5px 10px;
width: 110px;
height: 31px;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
color: #fff;
}
button:hover {
background: linear-gradient(180deg, #6AB2FC 0%, #104593 100%);
}
.active {
background: linear-gradient(180deg, #6AB2FC 0%, #104593 100%);
}
}
</style>

View File

@ -1,8 +1,7 @@
import request from '/@/utils/request';
export const getStream = (params: object) => {
return request({
url: 'http://127.0.0.1:8000/get_stream',
params
params
});
};
};

View File

@ -6,7 +6,7 @@
</el-icon>
<div class="monitoring-box">
<div class="monitoring">
<div class="monitoring-border"></div>
<!-- <div class="monitoring-border"></div> -->
<div class="monitoring-header">
<div>丈八东路监控</div>
<div>2023.10.12 14:00:00</div>
@ -18,7 +18,8 @@
<div>事件类型</div>
<div>2024-12-11 12:00:00</div>
</div>
<img src="https://haowallpaper.com/link/common/file/previewFileImg/83091f00ab941563dffe515ca90ec305" alt="">
<img src="https://haowallpaper.com/link/common/file/previewFileImg/83091f00ab941563dffe515ca90ec305"
alt="">
</div>
</div>
</div>
@ -29,9 +30,13 @@
import { ref, defineProps, toRefs, onMounted, watch, onBeforeUnmount, nextTick } from 'vue'
import { Close } from '@element-plus/icons-vue'
import FlvJs from 'flv.js'
import { useCommon } from '/@/stores/common';
const commonStore = useCommon()
const emit = defineEmits(['close'])
const volume = ref(0)
const intervalId = ref(null)
const carameRef = ref(null)
const flvPlayer = ref(null)
@ -66,6 +71,13 @@ watch(visible, async (newValue) => {
flvPlayer.value.attachMediaElement(carameRef.value)
flvPlayer.value.load()
flvPlayer.value.play()
// WebSocket
clearInterval(intervalId.value); //
intervalId.value = setInterval(() => {
commonStore.sendWs({ looking: cameraId.value });
}, 1000 * 60 * 3); // 3
//
commonStore.sendWs({ looking: cameraId.value });
}
})
} else {
@ -93,6 +105,9 @@ onBeforeUnmount(() => {
flvPlayer.value.destroy()
flvPlayer.value = null
}
if (intervalId.value) {
clearInterval(intervalId.value);
}
})
</script>
@ -107,6 +122,7 @@ onBeforeUnmount(() => {
left: 50%;
transform: translate(-50%, -50%);
padding: 40px 60px;
.pop-up-box {
padding: 15px;
height: 100%;
@ -115,24 +131,17 @@ onBeforeUnmount(() => {
flex-direction: column;
justify-content: center;
position: relative;
.close-btn {
position: absolute;
top: -10px;
right: -30px;
}
.monitoring-box {
.monitoring {
position: relative;
.monitoring-border {
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
border: 2px solid #fff;;
// background-image: url('/@/assets/monitoring-frame.png');
// background-size: 100% 100%;
}
.monitoring-header {
padding: 0 8px 0 15px;
@ -153,8 +162,10 @@ onBeforeUnmount(() => {
video {
width: 100%;
height: 100%;
border: 2px solid #fff;
}
}
.newEvent {
margin: 15px 0;
position: relative;
@ -175,6 +186,7 @@ onBeforeUnmount(() => {
align-items: center;
opacity: .5;
}
img {
width: 100%;
height: 100%;

View File

@ -43,43 +43,43 @@ const toConfigPath = () => {
//
const onHandleCommandClick = (path) => {
if (path === 'logOut') {
ElMessageBox({
closeOnClickModal: false,
closeOnPressEscape: false,
title: t('message.user.logOutTitle'),
message: t('message.user.logOutMessage'),
showCancelButton: true,
confirmButtonText: t('message.user.logOutConfirm'),
cancelButtonText: t('message.user.logOutCancel'),
buttonSize: 'default',
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
instance.confirmButtonLoading = true;
instance.confirmButtonText = t('message.user.logOutExit');
setTimeout(() => {
done();
setTimeout(() => {
instance.confirmButtonLoading = false;
}, 300);
}, 700);
} else {
done();
}
},
})
.then(async () => {
// /token
Session.clear();
// 使 reload resetRoute()
window.location.reload();
})
.catch(() => {});
} else if (path === 'wareHouse') {
window.open('https://gitee.com/lyt-top/vue-next-admin');
} else {
router.push(path);
}
if (path === 'logOut') {
ElMessageBox({
closeOnClickModal: false,
closeOnPressEscape: false,
title: t('message.user.logOutTitle'),
message: t('message.user.logOutMessage'),
showCancelButton: true,
confirmButtonText: t('message.user.logOutConfirm'),
cancelButtonText: t('message.user.logOutCancel'),
buttonSize: 'default',
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
instance.confirmButtonLoading = true;
instance.confirmButtonText = t('message.user.logOutExit');
setTimeout(() => {
done();
setTimeout(() => {
instance.confirmButtonLoading = false;
}, 300);
}, 700);
} else {
done();
}
},
})
.then(async () => {
// /token
Session.clear();
// 使 reload resetRoute()
window.location.reload();
})
.catch(() => { });
} else if (path === 'wareHouse') {
window.open('https://gitee.com/lyt-top/vue-next-admin');
} else {
router.push(path);
}
};
onMounted(() => {
@ -111,13 +111,12 @@ onMounted(() => {
height: 60px;
background: transparent;
font-weight: bold;
font-size: 22px;
font-size: 18px;
text-align: center;
}
}
.header-center {
width: 280px;
font-family: Source Han Sans CN;
font-weight: bold;
font-size: 28px;
@ -137,16 +136,33 @@ onMounted(() => {
.header-right {
:deep(.el-dropdown) {
width: 140px;
.el-dropdown-link {
width: 120px;
cursor: pointer;
color: #fff;
font-size: 22px;
font-size: 18px;
font-weight: bold;
}
}
}
}
// @media (max-width: 4096px) {}
// @media (min-width: 2560px) and (max-width: 4095px) {
// .layout-header {
// .header-center {
// height: 130px;
// line-height:100px;
// font-size: 40px;
// }
// }
// }
// @media (min-width: 1920px) and (max-width: 2559px) {}
// @media (min-width: 1440px) and (max-width: 1919px) {}
</style>

View File

@ -239,6 +239,6 @@ onUnmounted(() => {
#map-container {
width: 100%;
height: 100vh;
min-height: 1080px;
}
</style>

View File

@ -8,7 +8,7 @@
<script setup>
import TotalImage from '/@/assets/total.png';
import {defineProps, computed } from 'vue'
import { defineProps, computed } from 'vue'
const props = defineProps({
title: {
@ -35,10 +35,12 @@ const totalShow = computed(() => {
.total-container {
position: relative;
text-align: center;
img {
width: 105px;
height: 76.4px;
}
.title {
position: absolute;
width: 100%;
@ -47,6 +49,7 @@ const totalShow = computed(() => {
font-weight: bold;
color: #fff;
}
.total {
width: 100%;
position: absolute;
@ -59,4 +62,37 @@ const totalShow = computed(() => {
}
}
@media (min-width: 2560px) {
.total-container {
position: relative;
text-align: center;
img {
width: 160px;
height: 100px;
}
.title {
position: absolute;
width: 100%;
top: 11px;
left: 0;
font-weight: bold;
font-size: 16.5px;
color: #fff;
}
.total {
width: 100%;
position: absolute;
top: 60px;
left: 9px;
font-weight: bold;
font-size: 26px;
letter-spacing: 18px;
color: #fff;
}
}
}
</style>

View File

@ -1,23 +1,31 @@
import {defineStore} from 'pinia';
import { defineStore } from 'pinia';
export const useCommon = defineStore('common', {
state: () => ({
ws: null as WebSocket | null, // WebSocket 实例
reconnectTimer: null as number | null, // 重连定时器
reconnectDelay: 3000, // 重连间隔时间(毫秒)
deviceType: 'lamp',
markerData: [],
lampData: {
total: 0,
online: 0,
lighting: 0
lighting: 0,
records: [],
},
screenData: {
total: 0,
online: 0,
records: [],
},
broadcastData: {
total: 0,
online: 0,
}
records: [],
},
cameraData: {
records: [],
},
}),
actions: {
async setDeviceType(type: string) {
@ -34,6 +42,64 @@ export const useCommon = defineStore('common', {
},
async setBroadcastData(data: any) {
this.broadcastData = data;
}
},
createWebsocket() {
if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {
console.log('WebSocket 已存在,无需重新创建');
return;
}
this.ws = new WebSocket('ws://localhost:8000/ws');
this.ws.onopen = () => {
console.log('WebSocket 连接已打开');
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
};
this.ws.onmessage = (event) => {
console.log('收到服务器消息:', event.data);
};
this.ws.onclose = () => {
console.log('WebSocket 连接已关闭');
this.attemptReconnect();
};
this.ws.onerror = (error) => {
console.error('WebSocket 发生错误:', error);
this.ws?.close(); // 关闭 WebSocket触发 `onclose` 事件
};
},
attemptReconnect() {
if (this.reconnectTimer) {
return; // 避免重复设置定时器
}
console.log(`尝试在 ${this.reconnectDelay / 1000} 秒后重连...`);
this.reconnectTimer = window.setTimeout(() => {
console.log('正在尝试重连 WebSocket...');
this.createWebsocket();
}, this.reconnectDelay);
},
async sendWs(data: any) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
} else {
console.error('WebSocket is not connected.');
}
},
async closeWs() {
if (this.ws) {
this.ws.close();
this.ws = null;
}
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
},
},
});
});

View File

@ -75,8 +75,8 @@
<div class="control-buttons">
<button @click="router.push('/eventList')">预警管理</button>
<button class="active" @click="router.push('/dataStatistics')">数据统计</button>
<button @click="router.push('/home')">可视化</button>
<button class="active" @click="router.push('/dataStatistics')">数据统计</button>
</div>
<EventPopUp :visible="false"/>
<LampPopUp :visible="false"/>
@ -693,9 +693,12 @@ onMounted(async () => {
<style scoped lang="scss">
.visualization-container {
min-height: 1080px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
height: 100%;
padding: 119px 27.5px 94px 27.5px;
padding: 80px 27.5px;
background: linear-gradient(90deg, #272E42 0%, #304E7E 49%, #272E42 100%);
.data-show {
@ -854,38 +857,6 @@ onMounted(async () => {
}
}
.control-buttons {
color: #fff;
position: absolute;
height: 15.5px;
top: 1012px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 43px;
button {
border: none;
background: #375985;
padding: 5px 10px;
width: 110px;
height: 31px;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
color: #fff;
}
button:hover {
background: linear-gradient(180deg, #6AB2FC 0%, #104593 100%);
}
.active {
background: linear-gradient(180deg, #6AB2FC 0%, #104593 100%);
}
}
.bg-floor {
position: absolute;
left: 50%;

View File

@ -242,38 +242,6 @@ onMounted(async () => {
left: 0;
width: 441.5px;
}
.control-buttons {
color: #fff;
position: absolute;
height: 15.5px;
top: 1012px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 43px;
button {
border: none;
background: #375985;
padding: 5px 10px;
width: 110px;
height: 31px;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
color: #fff;
}
button:hover {
background: linear-gradient(180deg, #6AB2FC 0%, #104593 100%);
}
.active {
background: linear-gradient(180deg, #6AB2FC 0%, #104593 100%);
}
}
}
:deep(.el-slider) {

View File

@ -219,7 +219,7 @@
</template>
<script setup>
import { defineAsyncComponent, computed, ref, onBeforeMount, onMounted, onUnmounted, nextTick, reactive } from 'vue';
import { onMounted, onUnmounted, nextTick, reactive } from 'vue';
import { useRouter } from 'vue-router';
import { NextLoading } from '/@/utils/loading';
import { Close, Search, CaretRight, CaretBottom } from '@element-plus/icons-vue'
@ -242,7 +242,7 @@ onMounted(() => {
<style scoped lang="scss">
.visualization-container {
position: relative;
height: 100%;
min-height: 1080px;
padding: 119px 27.5px 94px 27.5px;
background: linear-gradient(90deg, #272E42 0%, #304E7E 49%, #272E42 100%);

View File

@ -3,24 +3,21 @@
<div class="data-show">
<el-form :inline="true" :model="formInline" class="form">
<el-form-item label="来源" >
<el-select v-model="formInline.SourceType" placeholder="请选择来源" :suffix-icon="PullDown" clearable
<el-select v-model="formInline.SourceType" @change="search" placeholder="请选择来源" :suffix-icon="PullDown" clearable
style="width: 180px; height: 34px;">
<el-option v-for="item in SourceTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="事件类型">
<el-select v-model="formInline.EventType" placeholder="请选择事件类型" :suffix-icon="PullDown" clearable
<el-select v-model="formInline.EventType" @change="search" placeholder="请选择事件类型" :suffix-icon="PullDown" clearable
style="width: 200px; height: 34px;">
<el-option v-for="item in EventTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="发生日期" style="margin-left: 40px;">
<el-date-picker v-model="dateValue" type="daterange" range-separator="" prefix-icon="none"
<el-date-picker v-model="dateValue" @change="search" type="daterange" range-separator="" prefix-icon="none"
start-placeholder="开始日期" end-placeholder="结束日期" style="width: 250px; height: 34px;" />
</el-form-item>
<el-form-item>
<button class="search-btn" style="width: 80px; height: 34px;" @click="search">搜索</button>
</el-form-item>
</el-form>
<el-table :default-sort="{ prop: 'occurrenceTime', order: 'descending' }" :data="eventInfoList"
style="width: 100%;" header-row-class-name="table-header">
@ -69,9 +66,9 @@
v-model:total="total" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
</div>
<div class="control-buttons">
<button @click="router.push('/dataStatistics')">数据统计</button>
<button @click="router.push('/home')">可视化</button>
<button class="active" @click="router.push('/eventList')">预警管理</button>
<button @click="router.push('/dataStatistics')">数据统计</button>
</div>
<div class="bg-floor">
</div>
@ -155,14 +152,16 @@ onMounted(async () => {
<style scoped lang="scss">
.visualization-container {
min-height: 1080px;
position: relative;
height: 100%;
padding: 119px 27.5px 94px 27.5px;
padding: 80px 27.5px;
background: linear-gradient(90deg, #272E42 0%, #304E7E 49%, #272E42 100%);
display: flex;
align-items: center;
justify-content: center;
.data-show {
width: 1862px;
height: 865px;
min-width: 1862px;
min-height: 865px;
border: 1px solid #0989d3;
padding: 34px 37px 13px 37px;
position: relative;
@ -250,39 +249,6 @@ onMounted(async () => {
}
}
.control-buttons {
color: #fff;
position: absolute;
height: 15.5px;
top: 1012px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 43px;
button {
border: none;
background: #375985;
padding: 5px 10px;
width: 110px;
height: 31px;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
color: #fff;
}
button:hover {
background: linear-gradient(180deg, #6AB2FC 0%, #104593 100%);
}
.active {
background: linear-gradient(180deg, #6AB2FC 0%, #104593 100%);
}
}
.bg-floor {
position: absolute;
left: 50%;

View File

@ -1,14 +1,12 @@
<template>
<div class="visualization-container">
<div class="visualization-container" ref="screenRef">
<Map ref="mapRef" :center="center" />
<div class="select">
<el-select v-model="deviceType" @change="changeDeviceType" placeholder="请选择设备"
style="width: 183px; height: 43px;">
<el-select v-model="deviceType" @change="changeDeviceType" placeholder="请选择设备">
<el-option v-for="item in deviceTypeOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
<el-select v-model="deviceId" @change="changeDevice" :placeholder="`请选择${deviceTitle}`"
style="width: 183px; height: 43px;">
<el-select v-model="deviceId" @change="changeDevice" :placeholder="`请选择${deviceTitle}`">
<el-option v-for="item in deviceOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
@ -94,9 +92,15 @@
</div>
<div class="right-rectangle">
<div class="rectangle rr1">
<div class="title">实时监控</div>
<div class="title"><span>实时监控</span><span class="sub-title">
<el-select v-model="cameraId" @change="changeCamera" placeholder="请选择要查看的摄像头"
style="width: 183px; height: 43px;">
<el-option v-for="item in cameraOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</span></div>
<div class="content">
<video width="100%" height="100%" controls autoplay ref="videoRef">
<video width="100%" height="100%" controls autoplay ref="currentCameraRef">
</video>
</div>
</div>
@ -145,7 +149,7 @@
</template>
<script setup>
import { defineAsyncComponent, computed, ref, onMounted } from 'vue';
import { defineAsyncComponent, computed, ref, onMounted, onUnmounted } from 'vue';
import * as echarts from 'echarts';
import { useRouter } from 'vue-router';
import { NextLoading } from '/@/utils/loading';
@ -157,14 +161,18 @@ import { formatDateTimeStr } from '/@/utils/formatTime';
import EnumOptions from '/@/components/Linxyun/Datas/enum_options.js';
import { storeToRefs } from 'pinia'
import { useCommon } from '/@/stores/common';
import { getStream } from '/@/api/camera';
import FlvJs from 'flv.js';
const EventTypes = EnumOptions.instance().getOptions('EventType')
const commonStore = useCommon()
const { deviceType, markerData, lampData, screenData, broadcastData } = storeToRefs(commonStore)
const { deviceType, markerData, lampData, screenData, broadcastData, cameraData } = storeToRefs(commonStore)
const Map = defineAsyncComponent(() => import('/@/components/map/index.vue'));
const Total = defineAsyncComponent(() => import('/@/components/total/index.vue'));
import { Session } from '/@/utils/storage';
const videoRef = ref(null)
const currentCameraRef = ref(null)
const intervalId = ref(null)
const cameraId = ref(null) // ID
const cameraOptions = ref([])
const flvPlayer = ref(null)
const mapRef = ref(null)
const center = ref(null)
const router = useRouter();
@ -238,12 +246,16 @@ const changeDevice = (newID) => {
const changeDeviceType = async (val) => {
if (deviceType.value == 'lamp') {
await getLampData()
markerData.value = lampData.value.records
} else if (deviceType.value == 'camera') {
await getCameraData()
markerData.value = cameraData.value.records
} else if (deviceType.value == 'screen') {
await getScreenData()
markerData.value = screenData.value.records
} else if (deviceType.value == 'broadcast') {
await getBroadcastData()
markerData.value = broadcastData.value.records
}
setDeviceOption()
}
@ -274,6 +286,51 @@ const setDeviceOption = () => {
return [];
}
const playCamera = () => {
getStream({ camera_id: cameraId.value }).then(res => {
const { code, success, data, msg } = res;
if (success && currentCameraRef.value && FlvJs.isSupported()) {
console.log('Initializing FlvJs player...');
//
if (flvPlayer.value) {
flvPlayer.value.destroy();
flvPlayer.value = null;
}
//
flvPlayer.value = FlvJs.createPlayer({
isLive: true,
hasAudio: true,
hasVideo: true,
cors: true,
type: 'flv',
url: data,
});
flvPlayer.value.attachMediaElement(currentCameraRef.value);
flvPlayer.value.load();
flvPlayer.value.play();
// WebSocket
clearInterval(intervalId.value); //
intervalId.value = setInterval(() => {
commonStore.sendWs({ looking: cameraId.value });
}, 1000 * 60 * 3); // 3
//
commonStore.sendWs({ looking: cameraId.value });
}
});
};
const changeCamera = (val) => {
if (flvPlayer.value) {
flvPlayer.value.destroy();
flvPlayer.value = null;
}
//
playCamera()
}
const deviceTitle = computed(() => {
switch (deviceType.value) {
case 'lamp':
@ -541,7 +598,7 @@ const getLampData = async () => {
lampData.value.total = records.length
lampData.value.online = records.filter(item => parseInt(item.OnLineStatus) === 1).length // 线
lampData.value.lighting = records.filter(item => parseInt(item.Light) > 0).length // 0
markerData.value = records
lampData.value.records = records
}
// 广
const getBroadcastData = async () => {
@ -549,7 +606,7 @@ const getBroadcastData = async () => {
if (!records) return;
lampData.value.online = records.filter(item => parseInt(item.OnLineStatus) === 1).length // 线
broadcastData.value.total = records.length
markerData.value = records
broadcastData.value.records = records
}
//
@ -558,7 +615,7 @@ const getScreenData = async () => {
if (!records) return;
screenData.value.online = records.filter(item => parseInt(item.OnLineStatus) === 1).length
screenData.value.total = records.length
markerData.value = records
screenData.value.records = records
}
//
@ -566,39 +623,51 @@ const getCameraData = async () => {
//
let records = await getAllData('queryCameraWithLamp')
if (!records) return;
// .value.total = records.length
markerData.value = records
cameraData.value.records = records
cameraOptions.value = records.map(item => ({
label: item.CameraName,
value: item.CameraID
}));
}
onMounted(async () => {
getEventInfoList();
getCameraData();
getBroadcastData();
getScreenData();
await getCameraData();
cameraId.value = cameraOptions.value[0].value //
playCamera()
await getLampData();
markerData.value = lampData.value.records //
setDeviceOption();
initOnlineRateChart();
initLightingRateChart();
initLightingElectricityChart()
NextLoading.done();
})
//
onUnmounted(() => {
clearInterval(intervalId.value);
});
</script>
<style scoped lang="scss">
.visualization-container {
// height: 100vh;
overflow: hidden;
position: relative;
display: flex;
justify-content: center;
align-items: center;
.select {
position: absolute;
width: 100%;
top: 133px;
left: 1240px;
right: 450px;
display: flex;
gap: 5px;
flex-direction: column;
width: 200px;
:deep(.el-select__placeholder) {
color: #dfd9d9;
@ -630,13 +699,13 @@ onMounted(async () => {
padding: 0 33px 0 29px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: Source Han Sans CN;
font-weight: bold;
font-size: 22px;
color: #FFFFFF;
line-height: 50px;
text-shadow: 0px 0px 5px #01AEFF;
width: 442px;
height: 50px;
background: linear-gradient(90deg, rgba(0, 131, 255, 0.5) 0%, rgba(9, 64, 125, 0.1) 100%);
}
@ -651,14 +720,13 @@ onMounted(async () => {
position: absolute;
top: 119.8px;
left: 0;
width: 441.5px;
.lr1 {
.content {
display: flex;
justify-content: center;
gap: 27px;
padding: 30px 0;
}
.chart {
@ -679,7 +747,6 @@ onMounted(async () => {
}
.lr2 {
.title {
.sub-title {
font-size: 18px;
@ -689,7 +756,7 @@ onMounted(async () => {
}
.warning-msgs {
width: 441.9px;
.warning-msg {
padding: 15px 20px;
@ -793,7 +860,6 @@ onMounted(async () => {
position: absolute;
top: 119.8px;
right: 0;
width: 441.5px;
.rr1 {
.content {
@ -855,38 +921,6 @@ onMounted(async () => {
}
}
}
.control-buttons {
color: #fff;
position: absolute;
height: 15.5px;
top: 1012px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 43px;
button {
border: none;
background: #375985;
padding: 5px 10px;
width: 110px;
height: 31px;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
color: #fff;
}
button:hover {
background: linear-gradient(180deg, #6AB2FC 0%, #104593 100%);
}
.active {
background: linear-gradient(180deg, #6AB2FC 0%, #104593 100%);
}
}
}
:deep(.el-slider) {

View File

@ -69,6 +69,6 @@
"skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"include": ["src/**/*.ts", "src/**/*.vue", "src/**/*.tsx", "src/**/*.d.ts"], // **Represents any directory, and * represents any file. Indicates that all files in the src directory will be compiled
"include": ["src/**/*.ts", "src/**/*.vue", "src/**/*.tsx", "src/**/*.d.ts", "src/utils/useResize.ts"], // **Represents any directory, and * represents any file. Indicates that all files in the src directory will be compiled
"exclude": ["node_modules", "dist"] // Indicates the file directory that does not need to be compiled
}