Compare commits

...

7 Commits

Author SHA1 Message Date
龚皓 1d550b8952 分布和趋势布局调整,卡片布局大小适配 2024-10-22 15:59:56 +08:00
龚皓 13425be2ca 用户列表查看滚动条 2024-10-22 13:56:23 +08:00
龚皓 6159ce3a18 横向滚动条调整 2024-10-22 13:54:02 +08:00
龚皓 7308c2f9bc 响应布局调整,容器高度限定,滚轮查看设置 2024-10-22 13:51:40 +08:00
龚皓 b02b125dad 高度调整等高 2024-10-22 13:50:24 +08:00
龚皓 8eaafbd577 摄像组件响应布局,缩小动态换行 2024-10-22 13:47:44 +08:00
龚皓 c1bf58a99a 2024-10-22 13:46:12 +08:00
8 changed files with 263 additions and 209 deletions

View File

@ -23,4 +23,4 @@ export default {
margin: 0; margin: 0;
padding: 0; padding: 0;
} */ } */
</style> </style>

View File

@ -487,8 +487,8 @@ watch(monthlyCounts, () => {
background-color: #304555; background-color: #304555;
color: #fff; color: #fff;
border-radius: 8px; border-radius: 8px;
padding: 0; /* padding: 10px; */
margin: 0; /* margin: 10px; */
} }
.alert-header { .alert-header {
@ -498,7 +498,9 @@ watch(monthlyCounts, () => {
} }
.chart-container { .chart-container {
height: 350px; /* min-height: 350px; */
min-height: 41vh;
min-width: 40vw;
} }
::v-deep .el-tabs__item { ::v-deep .el-tabs__item {

View File

@ -1,22 +1,30 @@
<template> <template>
<div> <div>
<el-card class="camera-card"> <el-card class="camera-card">
<el-row :gutter="20" class="camera-row"> <el-row class="camera-row">
<!-- 左侧块通道列表 --> <!-- 左侧块通道列表 -->
<el-col :span="8"> <el-col :sm="24" :md="8">
<el-card class="channel-card" shadow="hover"> <el-card class="channel-card" shadow="hover">
<div class="section-title">通道</div> <div class="section-title">通道</div>
<div class="scroll-container"> <div class="scroll-container">
<el-table :data="cameras" height="250"> <el-table :data="cameras" height="220">
<el-table-column prop="id" label="ID" width="100"></el-table-column> <el-table-column prop="id" label="ID" width="100" align="center"></el-table-column>
<el-table-column prop="name" label="名称"></el-table-column> <el-table-column prop="name" label="名称" align="center"></el-table-column>
<!-- 添加点播按钮的列 -->
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button type="primary" @click="handlePlay(scope.row)">
点播
</el-button>
</template>
</el-table-column>
</el-table> </el-table>
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
<!-- 中间块摄像头总数量 --> <!-- 中间块摄像头总数量 -->
<el-col :span="8"> <el-col :sm="24" :md="8">
<el-card class="count-card" shadow="hover"> <el-card class="count-card" shadow="hover">
<div class="section-title">摄像数量</div> <div class="section-title">摄像数量</div>
<div class="camera-count-chart"></div> <div class="camera-count-chart"></div>
@ -24,19 +32,37 @@
</el-col> </el-col>
<!-- 右侧块在线/离线/异常状态 --> <!-- 右侧块在线/离线/异常状态 -->
<el-col :span="8"> <el-col :sm="24" :md="8">
<el-card class="online-card" shadow="hover"> <el-card class="online-card" shadow="hover">
<div class="section-title">在线情况</div> <div class="section-title">在线情况</div>
<div class="status-summary-chart"></div> <div class="status-summary-chart"></div>
</el-card> </el-card>
</el-col> </el-col>
</el-row> </el-row>
<!-- 视频播放弹窗 -->
<el-dialog v-model="dialogVisible" width="50%" @close="handleStopStream" v-if="selectedCamera">
<p>
正在播放{{ selectedCamera.name }}
</p>
<div v-if="loading" class="loading-container">
<el-spinner size="large"></el-spinner>
</div>
<div v-show="!loading" class="video-container">
<canvas ref="canvasRef" class="video-canvas"></canvas>
</div>
<template #footer>
<el-button @click="dialogVisible = false">关闭</el-button>
</template>
</el-dialog>
</el-card> </el-card>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue'; import { ref, onMounted, onBeforeUnmount } from 'vue';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { BoxApi } from '@/utils/boxApi.ts'; import { BoxApi } from '@/utils/boxApi.ts';
@ -45,7 +71,11 @@ const cameraCount = ref(0);
const onlineCount = ref(0); const onlineCount = ref(0);
const offlineCount = ref(0); const offlineCount = ref(0);
const exceptionCount = ref(0); const exceptionCount = ref(0);
const selectedCamera = ref(null);
const dialogVisible = ref(false);
const loading = ref(true); //
const canvasRef = ref(null);
const playerRef = ref(null);
const apiInstance = new BoxApi(); const apiInstance = new BoxApi();
// //
@ -72,7 +102,7 @@ const fetchCameras = async () => {
cameras.value = allCameras; cameras.value = allCameras;
// 线线 // 线线
allCameras.forEach(camera => { allCameras.forEach((camera) => {
if (camera.status === 'online') { if (camera.status === 'online') {
onlineCount.value++; onlineCount.value++;
} else if (camera.status === 'offline') { } else if (camera.status === 'offline') {
@ -92,6 +122,59 @@ const fetchCameras = async () => {
} }
}; };
//
const handlePlay = (camera) => {
selectedCamera.value = camera;
dialogVisible.value = true;
loading.value = true;
//
setTimeout(() => {
loading.value = false;
handleStartStream(); //
}, 2000); // 2
};
//
const handleStartStream = async () => {
const token = localStorage.getItem('alertToken');
try {
const response = await apiInstance.startCameraStream(token, selectedCamera.value.id);
const streamPort = response.port;
//
const url = `ws://192.168.28.33:${streamPort}/`;
if (playerRef.value) {
playerRef.value.destroy(); //
}
if (window.JSMpeg) {
playerRef.value = new window.JSMpeg.Player(url, {
canvas: canvasRef.value,
autoplay: true,
videoBufferSize: 15 * 1024 * 1024,
audioBufferSize: 5 * 1024 * 1024,
});
}
} catch (error) {
console.error('Error starting stream:', error);
}
};
//
const handleStopStream = async () => {
const token = localStorage.getItem('alertToken');
try {
await apiInstance.stopCameraStream(token, selectedCamera.value.id);
if (playerRef.value) {
playerRef.value.destroy();
playerRef.value = null;
}
} catch (error) {
console.error('Error stopping stream:', error);
}
};
// //
const initCountChart = (count) => { const initCountChart = (count) => {
const chartDom = document.querySelector('.camera-count-chart'); const chartDom = document.querySelector('.camera-count-chart');
@ -109,15 +192,12 @@ const initCountChart = (count) => {
formatter: '{c}', formatter: '{c}',
fontSize: 24, fontSize: 24,
}, },
data: [ data: [{ value: count, name: '总数' }],
{ value: count, name: '总数' }
],
itemStyle: { itemStyle: {
color: 'rgba(80,160,225, 1)' color: 'rgba(80,160,225, 1)',
} },
},
} ],
]
}; };
myChart.setOption(option); myChart.setOption(option);
@ -135,24 +215,31 @@ const initStatusSummaryChart = (online, offline, exception) => {
radius: ['40%', '60%'], radius: ['40%', '60%'],
label: { label: {
show: true, show: true,
formatter: '{b}: {c} ({d}%)' formatter: '{b}: {c} ({d}%)',
}, },
data: [ data: [
{ value: online, name: '在线' }, { value: online, name: '在线' },
{ value: offline, name: '离线' }, { value: offline, name: '离线' },
{ value: exception, name: '异常' } { value: exception, name: '异常' },
], ],
itemStyle: { itemStyle: {
color: 'rgba(5,192,145, 1)' color: 'rgba(5,192,145, 1)',
// color: 'rgba(255,151,75, 1)' },
} },
} ],
]
}; };
myChart.setOption(option); myChart.setOption(option);
}; };
//
onBeforeUnmount(() => {
if (playerRef.value) {
playerRef.value.destroy();
playerRef.value = null;
}
});
onMounted(() => { onMounted(() => {
fetchCameras(); fetchCameras();
}); });
@ -160,22 +247,22 @@ onMounted(() => {
<style scoped> <style scoped>
.camera-card { .camera-card {
/* background-color: #abcef1; */
/* background-color: linear-gradient(to top, rgba(150, 206, 243, 0.2), rgba(214, 240, 250, 0.3)); */
color: #fff; color: #fff;
/* border-radius: 8px; */
margin: 0; margin: 0;
padding: 0; padding: 0;
/* padding-left: 5%; */
} }
.channel-card { .channel-card {
background: linear-gradient(to top, rgba(150, 206, 243, 1), rgba(125, 29, 235, 0.3)); background: linear-gradient(to top, rgba(150, 206, 243, 1), rgba(125, 29, 235, 0.3));
border-radius: 12px; border-radius: 12px;
} }
.count-card { .count-card {
background: linear-gradient(to bottom, rgba(246, 130, 85, 1), rgba(252, 186, 38, 0.3)); background: linear-gradient(to bottom, rgba(246, 130, 85, 1), rgba(252, 186, 38, 0.3));
border-radius: 12px; border-radius: 12px;
margin-left: 20px;
margin-right: 20px;
} }
.online-card { .online-card {
@ -183,43 +270,41 @@ onMounted(() => {
border-radius: 12px; border-radius: 12px;
} }
.camera-header {
font-size: 18px;
font-weight: bold;
border-bottom: 1px solid #3a4b5c;
}
.camera-row {
margin-top: 0px;
margin-bottom: 0px;
}
.section-title { .section-title {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
margin-bottom: 10px; margin-bottom: 10px;
} }
.el-card {
padding: 0;
text-align: center;
background-color: azure
}
.scroll-container { .scroll-container {
height: 170px; height: 170px;
/* overflow-y: scroll; */ height: 27vh;
scrollbar-width: none; /* overflow-y: auto; */
}
/* .scroll-container::-webkit-scrollbar { }
display: none;
} */
.status-summary-chart, .status-summary-chart,
.camera-count-chart { .camera-count-chart {
/* width: 100%; */
height: 27vh;
}
.loading-container {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
}
.video-container {
width: 100%; width: 100%;
height: 170px; height: 400px;
background-color: #000;
}
.video-canvas {
width: 100%;
height: 100%;
} }
.el-table { .el-table {
@ -229,14 +314,23 @@ onMounted(() => {
} }
.el-table th { .el-table th {
background-color: #f5f7fa; /* 表头背景色 */ background-color: #f5f7fa;
color: #333; /* 表头字体颜色 */ color: #333;
font-weight: bold; font-weight: bold;
text-align: center; /* 居中对齐 */ text-align: center;
} }
::v-deep .el-card__body{ ::v-deep .el-card__body {
padding: 15px; padding: 15px;
} }
.camera-row {
max-height: 37vh;
/* max-height: 250px; */
/* max-width: 1680px; */
overflow-x: auto;
/* gap: 1px; */
/* flex-wrap: nowrap; */
}
</style> </style>

View File

@ -1,75 +1,59 @@
<template> <template>
<div id="layout" class="layout-container"> <div id="layout" class="layout-container">
<el-aside class="nav-sidebar"> <!-- 侧边栏 -->
<div class="logo"> <el-aside class="nav-sidebar" :style="{ width: isCollapse ? '64px' : '180px' }">
<div class="logo" v-if="!isCollapse">
<el-image src="/turing.png" fit="contain" /> <el-image src="/turing.png" fit="contain" />
</div> </div>
<!-- <el-radio-group v-model="isCollapse" style="margin-bottom: 20px">
<el-radio-button :value="false">expand</el-radio-button>
<el-radio-button :value="true">collapse</el-radio-button>
</el-radio-group> -->
<el-menu :default-active="activeIndex" class="el-menu-part" router :collapse="isCollapse"> <el-menu :default-active="activeIndex" class="el-menu-part" router :collapse="isCollapse">
<el-menu-item index="/"> <el-menu-item index="/">
<el-icon> <el-icon><House /></el-icon>
<House />
</el-icon>
<template #title><span>首页</span></template> <template #title><span>首页</span></template>
</el-menu-item> </el-menu-item>
<el-menu-item index="/alertManagement"> <el-menu-item index="/alertManagement">
<el-icon> <el-icon><Management /></el-icon>
<Management />
</el-icon>
<template #title><span>告警列表</span></template> <template #title><span>告警列表</span></template>
</el-menu-item> </el-menu-item>
<el-menu-item index="/dataStatistics"> <el-menu-item index="/dataStatistics">
<el-icon> <el-icon><TrendCharts /></el-icon>
<TrendCharts />
</el-icon>
<template #title><span>数据统计</span></template> <template #title><span>数据统计</span></template>
</el-menu-item> </el-menu-item>
<el-menu-item index="/userList"> <el-menu-item index="/userList">
<el-icon> <el-icon><Avatar /></el-icon>
<Avatar />
</el-icon>
<template #title><span>用户管理</span></template> <template #title><span>用户管理</span></template>
</el-menu-item> </el-menu-item>
<el-sub-menu index="1"> <el-sub-menu index="1">
<template #title> <template #title>
<el-icon> <el-icon><Location /></el-icon>
<location />
</el-icon>
<span>面板测试</span> <span>面板测试</span>
</template> </template>
<el-menu-item index="/test"> <el-menu-item index="/test">
<el-icon> <el-icon><WarningFilled /></el-icon>
<WarningFilled />
</el-icon>
<template #title><span>布局备份</span></template> <template #title><span>布局备份</span></template>
</el-menu-item> </el-menu-item>
<el-menu-item index="/alertChart"> <el-menu-item index="/alertChart">
<el-icon> <el-icon><WarningFilled /></el-icon>
<WarningFilled />
</el-icon>
<template #title><span>功能点1测试</span></template> <template #title><span>功能点1测试</span></template>
</el-menu-item> </el-menu-item>
<el-menu-item index="/statistics"> <el-menu-item index="/statistics">
<el-icon> <el-icon><Document /></el-icon>
<document />
</el-icon>
<template #title><span>功能点2测试</span></template> <template #title><span>功能点2测试</span></template>
</el-menu-item> </el-menu-item>
<el-menu-item index="/cameras"> <el-menu-item index="/cameras">
<el-icon> <el-icon><Document /></el-icon>
<document />
</el-icon>
<template #title><span>功能点3测试</span></template> <template #title><span>功能点3测试</span></template>
</el-menu-item> </el-menu-item>
</el-sub-menu> </el-sub-menu>
</el-menu> </el-menu>
</el-aside> </el-aside>
<div class="content-layout"> <div class="content-layout">
<!-- 头部区域 --> <!-- 头部区域 -->
<el-header class="nav-header"> <el-header class="nav-header">
<!-- 收缩/展开按钮 -->
<el-icon @click="toggleCollapse" style="cursor: pointer; margin-right: 20px;">
<component :is="isCollapse ? Expand : Fold" />
</el-icon>
<div class="header-right"> <div class="header-right">
<!-- 用户头像下拉菜单 --> <!-- 用户头像下拉菜单 -->
@ -81,14 +65,10 @@
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item> <el-dropdown-item>
<el-icon> <el-icon><User /></el-icon>
<User />
</el-icon>
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item @click="onLogout"> <el-dropdown-item @click="onLogout">
<el-icon> <el-icon><SwitchButton /></el-icon> 退
<SwitchButton />
</el-icon> 退
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
@ -102,9 +82,7 @@
</el-main> </el-main>
<!-- 页脚区域 --> <!-- 页脚区域 -->
<el-footer class="nav-footer"> <el-footer class="nav-footer">Powered by AI</el-footer>
Powered by AI
</el-footer>
</div> </div>
</div> </div>
</template> </template>
@ -112,19 +90,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch, onMounted } from 'vue'; import { ref, watch, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { import {
Document, Document, WarningFilled, Location, User, SwitchButton,
WarningFilled, House, Management, TrendCharts, Avatar, Fold, Expand
Location,
User,
SwitchButton,
WarnTriangleFilled,
Setting,
House,
Grid,
Management,
TrendCharts,
Avatar
} from '@element-plus/icons-vue'; } from '@element-plus/icons-vue';
import { BoxApi } from '@/utils/boxApi'; import { BoxApi } from '@/utils/boxApi';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
@ -132,24 +100,14 @@ import { ElMessage } from 'element-plus';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const activeIndex = ref(route.path); const activeIndex = ref(route.path);
const isCollapse = ref(false); const isCollapse = ref(false); //
const username = ref(''); //
// username
const username = ref(''); //
// localStorage
onMounted(() => { onMounted(() => {
const storedUsername = localStorage.getItem('username'); const storedUsername = localStorage.getItem('username');
if (storedUsername) { username.value = storedUsername ? storedUsername : '用户';
username.value = storedUsername;
} else {
username.value = '用户'; // localStorage ""
}
}); });
const apiInstance = new BoxApi();
// active
watch( watch(
() => route.path, () => route.path,
(newPath) => { (newPath) => {
@ -157,21 +115,20 @@ watch(
} }
); );
// const onLogout = () => { const toggleCollapse = () => {
// localStorage.removeItem('alertToken'); isCollapse.value = !isCollapse.value; //
// router.push('/login'); };
// };
const onLogout = async () => { const onLogout = async () => {
try { try {
localStorage.removeItem('alertToken'); localStorage.removeItem('alertToken');
ElMessage.success('退出登录成功'); ElMessage.success('退出登录成功');
router.push('/login'); router.push('/login');
await apiInstance.logout(); await new BoxApi().logout();
} catch (error: any) { } catch (error: any) {
ElMessage.error(`后台接口调用失败: ${error.message}`); ElMessage.error(`后台接口调用失败: ${error.message}`);
} }
}; };
</script> </script>
<style scoped> <style scoped>
@ -179,19 +136,16 @@ const onLogout = async () => {
display: flex; display: flex;
height: 100%; height: 100%;
width: 100%; width: 100%;
margin: 1px; /* overflow: hidden; */
padding: 1px;
} }
.nav-sidebar { .nav-sidebar {
width: 150px;
background-color: #001529; background-color: #001529;
/* width: 200px; */
color: #fff; color: #fff;
min-height: 97.5vh; min-height: 100vh;
border-top-left-radius: 8px; border-top-left-radius: 5px;
border-bottom-left-radius: 8px; border-bottom-left-radius: 5px;
transition: width 1s ease;
} }
.logo { .logo {
@ -204,9 +158,7 @@ const onLogout = async () => {
background-color: transparent; background-color: transparent;
color: #fff; color: #fff;
border: 1px; border: 1px;
/* width: 200px; */
} }
.el-menu-part .el-menu-item { .el-menu-part .el-menu-item {
/* padding:30px 0; */ /* padding:30px 0; */
padding: 0px; padding: 0px;
@ -218,8 +170,6 @@ const onLogout = async () => {
background-color: #001529; background-color: #001529;
transition: background-color 0.3s ease, color 0.3s ease; transition: background-color 0.3s ease, color 0.3s ease;
} }
/* 悬停样式 */ /* 悬停样式 */
.el-menu-part .el-menu-item:hover { .el-menu-part .el-menu-item:hover {
background-color: #001529; background-color: #001529;
@ -244,29 +194,27 @@ const onLogout = async () => {
} }
/* 内容布局区域 */
.content-layout { .content-layout {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex-grow: 1; flex-grow: 1;
max-height: 100%; max-height: 100%;
/* overflow-x: auto; */
/* min-width: 0; */
} }
/* 头部样式 */
.nav-header { .nav-header {
/* background-color: #001529; */ background: linear-gradient(to right, rgba(0, 21, 41, 1), rgba(2, 16, 99, 0.9));
background: linear-gradient(to right, rgba(0, 21, 41, 1), rgb(2, 16, 99,0.9));
padding: 10px; padding: 10px;
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
color: white; color: white;
height: 58px; height: 68px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
justify-content: right; /* justify-content: right; */
max-width: 99.9%; /* max-width: 100%; */
/* flex-shrink: 1; */
} }
.header-right { .header-right {
@ -277,32 +225,30 @@ const onLogout = async () => {
.username { .username {
margin-left: 10px; margin-left: 10px;
margin-right: 30px; margin-right: 30px;
font-weight: bold; /* font-weight: bold;
font-size: 16px; font-size: 16px; */
color: #fff; color: #fff;
} }
/* 主内容样式 */
.main-content { .main-content {
background-color: #f5f8fc; background-color: #f5f8fc;
flex-grow: 1; flex-grow: 1;
max-height: 90%; max-height: 95vh;
margin: 0; margin: 0;
padding: 0; padding: 0;
width: 100%;
overflow-y: hidden;
} }
/* 页脚样式 */
.nav-footer { .nav-footer {
background-color: #fff; background-color: #fff;
/* padding: 10px; */
font-size: 16px; font-size: 16px;
color: #333; color: #333;
height: 2vh; height: 5vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
text-align: center; text-align: center;
margin: 0; margin: 0;
padding: 0; padding: 0;
/* flex-shrink: 0; */
} }
</style> </style>

View File

@ -117,7 +117,7 @@ const initChart = () => {
name: '告警类型', name: '告警类型',
type: 'pie', type: 'pie',
radius: '50%', radius: '50%',
center: ['50%', '150px'], center: ['50%', '50%'],
data: seriesData.value, data: seriesData.value,
data: [], data: [],
emphasis: { emphasis: {
@ -177,8 +177,8 @@ onMounted(async () => {
/* background: linear-gradient(to top, rgba(16, 84, 194, 0.6), rgba(31, 48, 207, 0.7)); */ /* background: linear-gradient(to top, rgba(16, 84, 194, 0.6), rgba(31, 48, 207, 0.7)); */
color: #fff; color: #fff;
border-radius: 8px; border-radius: 8px;
margin: 0; /* margin: 10px; */
padding: 0; /* padding: 10px; */
/* height: 100vh; */ /* height: 100vh; */
} }
@ -190,13 +190,15 @@ onMounted(async () => {
} }
.stats-row { .stats-row {
margin-top: 20px; margin-top: 10px;
margin-bottom: 10px; margin-bottom: 34px;
} }
.chart { .chart {
width: 100%; width: 100%;
height: 380px; /* min-height: 365px; */
height: 41vh;
min-width: 40vw;
/* height: 445px; */ /* height: 445px; */
} }
</style> </style>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="alert-container"> <div class="alert-container" >
<el-row class="bottom-pan"> <el-row class="bottom-pan">
<el-col :span="24" class="panel-bottom"> <el-col class="panel-bottom">
<Cameras/> <Cameras/>
</el-col> </el-col>
</el-row> </el-row>
@ -20,7 +20,7 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted } from 'vue'; import { ref, reactive, onMounted,computed, onBeforeUnmount} from 'vue';
import Statistics from '@/components/Statistics.vue'; import Statistics from '@/components/Statistics.vue';
import AlertChart from '@/components/AlertChart.vue'; import AlertChart from '@/components/AlertChart.vue';
import Cameras from '@/components/Cameras.vue'; import Cameras from '@/components/Cameras.vue';
@ -30,6 +30,19 @@ import { BoxApi } from '@/utils/boxApi.ts'; // 导入 BoxApi
const boxApi = new BoxApi(); const boxApi = new BoxApi();
const typeMapping = reactive({}); const typeMapping = reactive({});
const token = ref(null); const token = ref(null);
// const scale = ref(1);
// const scaleStyle = computed(() => ({
// transform: `scale(${scale.value})`,
// transformOrigin: 'top left',
// width: `${100 / scale.value}%`,
// }));
// const handleResize = () => {
// const clientWidth = document.documentElement.clientWidth;
// const scaleFactor = clientWidth / 1920;
// scale.value = scaleFactor < 1 ? scaleFactor : 1;
// };
// //
const fetchTypeMapping = async (token) => { const fetchTypeMapping = async (token) => {
@ -47,30 +60,45 @@ const fetchTypeMapping = async (token) => {
onMounted(async () => { onMounted(async () => {
token.value = localStorage.getItem('alertToken'); token.value = localStorage.getItem('alertToken');
await fetchTypeMapping(token.value); await fetchTypeMapping(token.value);
// handleResize();
// window.addEventListener('resize', handleResize);
}); });
// onBeforeUnmount(() => {
// window.removeEventListener('resize', handleResize);
// });
</script> </script>
<style scoped> <style scoped>
.alert-container { .alert-container {
/* transform: scale(0.97); */
padding: 0; padding: 0;
margin: 0; margin: 0;
background-color: #f5f7fa; background-color: #f5f7fa;
overflow-x: hidden;
height: 100vh;
width: 100%;
} }
.top-pan { .top-pan {
/* padding: 10px; */ /* padding: 10px; */
/* margin-bottom: 10px; */ /* margin-bottom: 10px; */
display: flex; /* display: flex; */
gap: 5px; /* gap: 5px; */
background-color: #fff; background-color: #fff;
overflow: hidden; /* overflow: hidden; */
/* height: 55vh; */
/* max-height: 450px; */
/* padding-left: 1vh; */
/* padding-right:1vh ; */
/* overflow: hidden; */
height: 55vh; height: 55vh;
padding-left: 10px; margin-top: 60px;
padding-right:10px ;
} }
.bottom-pan{ .bottom-pan{
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 33vh;
} }
.panel-top-left { .panel-top-left {
@ -83,7 +111,7 @@ onMounted(async () => {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 20px; /* gap: 20px; */
} }
.panel-bottom{ .panel-bottom{

View File

@ -30,14 +30,8 @@
<canvas v-show="playing" ref="canvasRef" class="camera-large"></canvas> <canvas v-show="playing" ref="canvasRef" class="camera-large"></canvas>
<!-- 播放按钮 --> <!-- 播放按钮 -->
<el-button <el-button v-show="!playing || showButton" class="play-button" type="primary" circle size="large"
v-show="!playing || showButton" @click="handlePlayPause">
class="play-button"
type="primary"
circle
size="large"
@click="handlePlayPause"
>
<el-icon> <el-icon>
<VideoPlay v-if="!playing" /> <VideoPlay v-if="!playing" />
<VideoPause v-if="playing" /> <VideoPause v-if="playing" />
@ -107,16 +101,16 @@ const handleStartStream = async () => {
playing.value = true; playing.value = true;
// //
const url = `ws://192.168.28.33:${streamPort.value}/`; const url = `ws://192.168.28.33:${streamPort.value}/`;
console.log('Playing set to true:', playing.value); console.log('Playing set to true:', playing.value);
if (playerRef.value) { if (playerRef.value) {
playerRef.value.destroy(); // playerRef.value.destroy(); //
} }
if (window.JSMpeg) { if (window.JSMpeg) {
playerRef.value = new window.JSMpeg.Player(url, { playerRef.value = new window.JSMpeg.Player(url, {
canvas: canvasRef.value, canvas: canvasRef.value,
autoplay: true, autoplay: true,
videoBufferSize: 15 * 1024 * 1024, videoBufferSize: 15 * 1024 * 1024,
audioBufferSize: 5 * 1024 * 1024, audioBufferSize: 5 * 1024 * 1024,
@ -170,6 +164,7 @@ onMounted(() => {
.camera-list { .camera-list {
width: 20%; width: 20%;
min-width: 215px;
max-height: 90vh; max-height: 90vh;
/* 限制高度为一屏 */ /* 限制高度为一屏 */
overflow-y: auto; overflow-y: auto;
@ -268,20 +263,4 @@ onMounted(() => {
.play-button:hover { .play-button:hover {
opacity: 1; opacity: 1;
} }
.camera-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
gap: 10px;
width: 80%;
height: 90vh;
}
.camera-box {
position: relative;
background-color: #1a1a1a;
}
</style> </style>

View File

@ -36,7 +36,7 @@
<el-col :span="24"> <el-col :span="24">
<div class="table-part"> <div class="table-part">
<el-table v-loading="loading" :data="tableData" <el-table v-loading="loading" :data="tableData"
class="user-table" border> class="user-table" border max-height="700px">
<el-table-column v-if="getColumnVisibility('selection')" type="selection" <el-table-column v-if="getColumnVisibility('selection')" type="selection"
min-width="55"></el-table-column> min-width="55"></el-table-column>
<el-table-column v-if="getColumnVisibility('id')" prop="id" label="序号" <el-table-column v-if="getColumnVisibility('id')" prop="id" label="序号"
@ -259,7 +259,8 @@ const handleDeleteSelected = () => {
<style scoped> <style scoped>
.user-list { .user-list {
width: auto; /* width: 100%; */
/* height: 100%; */
overflow: auto; overflow: auto;
/* min-height: 650px; */ /* min-height: 650px; */
/* height: 100vh; */ /* height: 100vh; */
@ -267,7 +268,9 @@ const handleDeleteSelected = () => {
} }
.user-table{ .user-table{
width: 100%;
overflow: auto;
overflow-x: auto;
} }
.header { .header {