大屏自适应,监控摄像头心跳机制
This commit is contained in:
parent
187a350e15
commit
a2dbfe7fd1
@ -19,5 +19,5 @@ const BASE_API = {
|
||||
// 是否开启国际化
|
||||
I18N: false,
|
||||
// 管理员角色编码
|
||||
MANAGER_ROLE: '2101'
|
||||
MANAGER_ROLE: '2101',
|
||||
}
|
||||
|
12
package-lock.json
generated
12
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
67
src/App.vue
67
src/App.vue
@ -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>
|
||||
|
@ -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
|
||||
});
|
||||
};
|
||||
};
|
||||
|
@ -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%;
|
||||
|
@ -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>
|
||||
|
@ -239,6 +239,6 @@ onUnmounted(() => {
|
||||
|
||||
#map-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
min-height: 1080px;
|
||||
}
|
||||
</style>
|
||||
|
@ -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>
|
@ -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;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
@ -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%;
|
||||
|
@ -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) {
|
||||
|
@ -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%);
|
||||
|
||||
|
@ -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%;
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user