其他(测试,布局滚轮适应添加宽度)
This commit is contained in:
parent
bcb0c77111
commit
220bf593aa
|
@ -42,7 +42,7 @@
|
||||||
</el-icon>
|
</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><Location /></el-icon>
|
<el-icon><Location /></el-icon>
|
||||||
<span>面板测试</span>
|
<span>面板测试</span>
|
||||||
|
@ -50,20 +50,20 @@
|
||||||
<el-menu-item index="/test">
|
<el-menu-item index="/test">
|
||||||
<el-icon><WarningFilled /></el-icon>
|
<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><WarningFilled /></el-icon>
|
<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><Document /></el-icon>
|
<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><Document /></el-icon>
|
<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>
|
||||||
|
|
||||||
|
|
|
@ -122,7 +122,7 @@ import CenterTop from '@/components/Max/CenterTop.vue';
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
padding: 4vh 10vw 10vh 7vw;
|
padding: 4vh 10vw 10vh 9vw;
|
||||||
/* background-color: rgb(121, 184, 243); */
|
/* background-color: rgb(121, 184, 243); */
|
||||||
background-color: #001529;
|
background-color: #001529;
|
||||||
/* background-image: url('/bg05.png'); */
|
/* background-image: url('/bg05.png'); */
|
||||||
|
@ -133,6 +133,7 @@ import CenterTop from '@/components/Max/CenterTop.vue';
|
||||||
position: relative;
|
position: relative;
|
||||||
color: black;
|
color: black;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-decoration{
|
.custom-decoration{
|
||||||
|
@ -161,12 +162,13 @@ import CenterTop from '@/components/Max/CenterTop.vue';
|
||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 80vw;
|
width: 82vw;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
gap: 1vh;
|
gap: 1vh;
|
||||||
position: relative;
|
position: relative;
|
||||||
/* 为了在背景下显示 */
|
/* 为了在背景下显示 */
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
@ -213,6 +215,8 @@ import CenterTop from '@/components/Max/CenterTop.vue';
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 1vw;
|
gap: 1vw;
|
||||||
|
overflow-y: scroll;
|
||||||
|
scrollbar-width:none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-section,
|
.left-section,
|
||||||
|
|
|
@ -1,164 +1,616 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="settings-container">
|
<div class="camera-container">
|
||||||
<el-row class="popup-row">
|
<div class="top-header">
|
||||||
<el-col :sm="24" :md="24">弹窗设置</el-col>
|
<div class="search-row">
|
||||||
<el-col :sm="24" :md="24">
|
<el-input v-model="searchKeyword" placeholder="搜索摄像头名称" @input="filterCameras" class="search-input" />
|
||||||
<el-checkbox v-model="isInteractivePopupEnabled" @change="handleInteractiveChange" style="color: aliceblue;">
|
</div>
|
||||||
开启交互式弹窗
|
<el-select v-model="filterStatus" placeholder="筛选状态" @change="fetchCameras" class="status-filter">
|
||||||
</el-checkbox>
|
<el-option label="全部" value="all"></el-option>
|
||||||
|
<el-option label="在线" value="online"></el-option>
|
||||||
|
<el-option label="离线" value="offline"></el-option>
|
||||||
|
</el-select>
|
||||||
|
<!-- <div class="top-text">警戒点位</div>
|
||||||
|
<el-select v-model="selectedCameraId" placeholder="搜索摄像头名称" @change="selectCameraById" clearable filterable
|
||||||
|
class="camera-select">
|
||||||
|
<el-option v-for="camera in cameras" :key="camera.id" :label="camera.name" :value="camera.id">
|
||||||
|
<span>{{ camera.id }}.</span> 名称: {{ camera.name }}
|
||||||
|
</el-option>
|
||||||
|
</el-select> -->
|
||||||
|
</div>
|
||||||
|
<div class="content-container">
|
||||||
|
<div class="left-part">
|
||||||
|
<div class="camera-list">
|
||||||
|
<el-card v-for="camera in filteredCameras" :key="camera.id" class="camera-item"
|
||||||
|
@click="selectCameraById(camera.id)">
|
||||||
|
<template #header>
|
||||||
|
<el-row class="row-id-name">
|
||||||
|
<el-col :span="14" class="col-camera-id">
|
||||||
|
{{ camera.name }}
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :sm="24" :md="24">
|
<!-- <el-col :span="8" class="col-camera-name">
|
||||||
<el-checkbox v-model="isResponsivePopupEnabled" @change="handleResponsiveChange" style="color: aliceblue;">
|
{{ camera.name }}
|
||||||
开启响应式弹窗
|
</el-col> -->
|
||||||
</el-checkbox>
|
<el-col :span="10" class="col-camera-setting">
|
||||||
|
<el-button type="text" icon="el-icon-setting" class="settings-button"
|
||||||
|
@click.stop="handleSettings(camera.id)">设置</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row class="channel-row">
|
</template>
|
||||||
<Channel />
|
<div class="div-content">
|
||||||
|
<el-row class="row-content">
|
||||||
|
<el-col :span="10" class="col-camera-snapshot">
|
||||||
|
通道{{ camera.id }}
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="14" class="col-camera-snapshot">
|
||||||
|
<el-image :src="camera.snapshot" :zoom-rate="1.2" :max-scale="7" :min-scale="0.2"
|
||||||
|
:preview-src-list="camera.snapshot" class="camera-img" />
|
||||||
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="camera-grid">
|
||||||
|
<div v-for="(camera, index) in selectedCameras" :key="camera.id" class="grid-item">
|
||||||
|
<div class="stream-control">
|
||||||
|
<p class="camera-name-title">{{ camera.name }}</p>
|
||||||
|
<div class="close-button" @click="closeStream(camera)">×</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="play-button-container" @mouseenter="showButton = true" @mouseleave="showButton = false">
|
||||||
|
<div class="camera-placeholder" v-if="!camera.playing && !camera.snapshot">
|
||||||
|
<el-icon size="48">
|
||||||
|
<VideoCameraFilled />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<el-image v-if="!camera.playing && camera.snapshot" :src="camera.snapshot" alt="camera snapshot"
|
||||||
|
class="camera-snapshot" />
|
||||||
|
<el-button v-show="!camera.playing || showButton" class="play-button" type="primary" circle size="large"
|
||||||
|
@click="openDialog(camera)">
|
||||||
|
<el-icon>
|
||||||
|
<VideoPlay v-if="!camera.playing" />
|
||||||
|
<VideoPause v-if="camera.playing" />
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-dialog v-model="dialogVisible" width="50%" @close="closeDialog">
|
||||||
|
<template #title>播放摄像头: {{ currentCamera?.name }}</template>
|
||||||
|
<canvas v-show="dialogVisible" ref="dialogCanvas" class="dialog-canvas"></canvas>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
|
||||||
|
<CameraRules :visible="rulesDialogVisible" :cameraData="currentCameraData"
|
||||||
|
@update:visible="rulesDialogVisible = $event" @save-result="handleSaveResult" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script setup>
|
||||||
import { ref, inject, onMounted } from 'vue';
|
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
import { BoxApi } from '@/utils/boxApi.ts';
|
||||||
import type { GlobalWebSocket } from '@/utils/useGlobalWebSocket';
|
import { VideoPlay, VideoPause, VideoCameraFilled } from '@element-plus/icons-vue';
|
||||||
import Channel from '@/components/Channel.vue';
|
import CameraRules from '@/html/CameraRules.vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
const isInteractivePopupEnabled = ref(false); // 交互式弹窗状态
|
const cameras = ref([]);
|
||||||
const isResponsivePopupEnabled = ref(false); // 响应式弹窗状态
|
const selectedCameras = ref([]);
|
||||||
const globalWebSocket = inject<GlobalWebSocket>('globalWebSocket');
|
const showButton = ref(false);
|
||||||
|
const apiInstance = new BoxApi();
|
||||||
|
const canvasRefs = ref({});
|
||||||
|
const selectedCameraId = ref(null);
|
||||||
|
const dialogVisible = ref(false); // 控制弹窗的显示与隐藏
|
||||||
|
const currentCamera = ref(null); // 当前选中的摄像头
|
||||||
|
|
||||||
if (!globalWebSocket) {
|
// 弹窗中的canvas引用
|
||||||
throw new Error('globalWebSocket 注入失败');
|
const dialogCanvas = ref(null);
|
||||||
|
const filterStatus = ref("all");
|
||||||
|
|
||||||
|
const filteredCameras = ref([]);
|
||||||
|
const searchKeyword = ref('');
|
||||||
|
|
||||||
|
const currentCameraData = ref(null);
|
||||||
|
const rulesDialogVisible = ref(false);
|
||||||
|
|
||||||
|
|
||||||
|
const fetchCameras = async () => {
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem('alertToken');
|
||||||
|
const cameraData = await apiInstance.getAllCameras(token);
|
||||||
|
// console.log("cameraData>>>>>>>>>>>>>>>", cameraData);
|
||||||
|
|
||||||
|
// 根据 filterStatus 筛选摄像头状态
|
||||||
|
if (filterStatus.value === "online") {
|
||||||
|
cameras.value = cameraData.filter(camera => camera.status === "online");
|
||||||
|
} else if (filterStatus.value === "offline") {
|
||||||
|
cameras.value = cameraData.filter(camera => camera.status === "offline");
|
||||||
|
} else {
|
||||||
|
cameras.value = cameraData;
|
||||||
|
// console.log("all cameras:", cameras.value);
|
||||||
|
}
|
||||||
|
filteredCameras.value = [...cameras.value];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取摄像头列表失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterCameras = () => {
|
||||||
|
if (!searchKeyword.value.trim()) {
|
||||||
|
filteredCameras.value = [...cameras.value];
|
||||||
|
} else {
|
||||||
|
filteredCameras.value = cameras.value.filter(camera =>
|
||||||
|
camera.name.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化时加载弹窗模式状态
|
};
|
||||||
|
|
||||||
|
// 名称+ID查询
|
||||||
|
// const filterCameras = () => {
|
||||||
|
// const keyword = searchKeyword.value.trim().toLowerCase();
|
||||||
|
// if (!keyword) {
|
||||||
|
// filteredCameras.value = [...cameras.value];
|
||||||
|
// } else {
|
||||||
|
// filteredCameras.value = cameras.value.filter(camera => {
|
||||||
|
// const idMatch = camera.id.toString().toLowerCase().includes(keyword);
|
||||||
|
// const nameMatch = camera.name.toLowerCase().includes(keyword);
|
||||||
|
// return idMatch || nameMatch;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
const selectCameraById = (cameraId) => {
|
||||||
|
const camera = cameras.value.find(c => c.id === cameraId);
|
||||||
|
if (camera && !selectedCameras.value.some(c => c.id === camera.id)) {
|
||||||
|
selectedCameras.value.push({ ...camera, playing: false, streamPort: null });
|
||||||
|
// console.log("搜索摄像头的数组内含有", selectedCameras.value)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleSettings = async (cameraId) => {
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem('alertToken');
|
||||||
|
const cameraData = await apiInstance.getCameraById(token, cameraId);
|
||||||
|
// console.log("获取到的 cameraData:", cameraData);
|
||||||
|
currentCameraData.value = cameraData;
|
||||||
|
rulesDialogVisible.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取摄像头规则失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveResult = ({ success, message }) => {
|
||||||
|
// console.log('收到子组件保存结果事件:', { success, message });
|
||||||
|
|
||||||
|
ElMessage({
|
||||||
|
message,
|
||||||
|
type: success ? 'success' : 'error',
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
rulesDialogVisible.value = false; // 关闭规则对话框
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开弹窗并开始播放
|
||||||
|
const openDialog = async (camera) => {
|
||||||
|
currentCamera.value = camera;
|
||||||
|
dialogVisible.value = true;
|
||||||
|
await nextTick();
|
||||||
|
startStreamInDialog(camera);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 在弹窗中播放视频
|
||||||
|
const startStreamInDialog = async (camera) => {
|
||||||
|
const canvas = dialogCanvas.value;
|
||||||
|
|
||||||
|
if (!camera || !canvas) {
|
||||||
|
console.error('未找到对应的 canvas');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = localStorage.getItem('alertToken');
|
||||||
|
const rememberedAddress = localStorage.getItem('rememberedAddress');
|
||||||
|
|
||||||
|
if (!rememberedAddress) {
|
||||||
|
alert('主机地址获取异常,请确认地址配置');
|
||||||
|
console.error('主机地址获取异常');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await apiInstance.startCameraStream(token, camera.id);
|
||||||
|
camera.streamPort = response.port;
|
||||||
|
camera.playing = true;
|
||||||
|
const url = `ws://${rememberedAddress}:${camera.streamPort}/`;
|
||||||
|
// const url = `ws://192.168.28.33:${camera.streamPort}/`;
|
||||||
|
// console.log('播放路径:', url);
|
||||||
|
|
||||||
|
if (window.JSMpeg) {
|
||||||
|
const player = new window.JSMpeg.Player(url, {
|
||||||
|
canvas: canvas,
|
||||||
|
autoplay: true,
|
||||||
|
videoBufferSize: 15 * 1024 * 1024,
|
||||||
|
audioBufferSize: 5 * 1024 * 1024,
|
||||||
|
});
|
||||||
|
camera.player = player;
|
||||||
|
} else {
|
||||||
|
console.error('JSMpeg 未加载');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('启动视频流失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭弹窗并停止视频播放
|
||||||
|
const closeDialog = () => {
|
||||||
|
if (currentCamera.value) {
|
||||||
|
handleStopStream(currentCamera.value);
|
||||||
|
}
|
||||||
|
dialogVisible.value = false;
|
||||||
|
currentCamera.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStopStream = async (camera) => {
|
||||||
|
const token = localStorage.getItem('alertToken');
|
||||||
|
try {
|
||||||
|
await apiInstance.stopCameraStream(token, camera.id);
|
||||||
|
camera.playing = false;
|
||||||
|
|
||||||
|
if (camera.player) {
|
||||||
|
camera.player.destroy();
|
||||||
|
camera.player = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('停止视频流失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeStream = (camera) => {
|
||||||
|
handleStopStream(camera);
|
||||||
|
selectedCameras.value = selectedCameras.value.filter(c => c.id !== camera.id);
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
isInteractivePopupEnabled.value = localStorage.getItem('isInteractivePopupEnabled') === 'true';
|
fetchCameras();
|
||||||
isResponsivePopupEnabled.value = localStorage.getItem('isResponsivePopupEnabled') === 'true';
|
|
||||||
updateWebSocketConnection();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// const handleInteractiveChange = () => {
|
onBeforeUnmount(() => {
|
||||||
// if (isInteractivePopupEnabled.value) {
|
selectedCameras.value.forEach(camera => {
|
||||||
// isResponsivePopupEnabled.value = false;
|
if (camera.player) {
|
||||||
// localStorage.setItem('isResponsivePopupEnabled', 'false');
|
camera.player.destroy();
|
||||||
// }
|
|
||||||
// localStorage.setItem('isInteractivePopupEnabled', String(isInteractivePopupEnabled.value));
|
|
||||||
// updateWebSocketConnection();
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleResponsiveChange = () => {
|
|
||||||
// if (isResponsivePopupEnabled.value) {
|
|
||||||
// isInteractivePopupEnabled.value = false;
|
|
||||||
// localStorage.setItem('isInteractivePopupEnabled', 'false');
|
|
||||||
// }
|
|
||||||
// localStorage.setItem('isResponsivePopupEnabled', String(isResponsivePopupEnabled.value));
|
|
||||||
// updateWebSocketConnection();
|
|
||||||
// };
|
|
||||||
|
|
||||||
const handleInteractiveChange = () => {
|
|
||||||
if (isInteractivePopupEnabled.value) {
|
|
||||||
ElMessageBox.confirm('是否开启交互式弹窗提示?', '确认提示', {
|
|
||||||
confirmButtonText: '是',
|
|
||||||
cancelButtonText: '否',
|
|
||||||
type: 'warning',
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
isResponsivePopupEnabled.value = false;
|
|
||||||
localStorage.setItem('isResponsivePopupEnabled', 'false');
|
|
||||||
|
|
||||||
localStorage.setItem('isInteractivePopupEnabled', 'true');
|
|
||||||
updateWebSocketConnection();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
isInteractivePopupEnabled.value = false;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ElMessageBox.confirm('是否关闭交互式弹窗?', '确认提示', {
|
|
||||||
confirmButtonText: '是',
|
|
||||||
cancelButtonText: '否',
|
|
||||||
type: 'warning',
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
localStorage.setItem('isInteractivePopupEnabled', 'false');
|
|
||||||
updateWebSocketConnection();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
isInteractivePopupEnabled.value = true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const handleResponsiveChange = () => {
|
|
||||||
if (isResponsivePopupEnabled.value) {
|
|
||||||
ElMessageBox.confirm('是否开启响应式弹窗提示?', '确认提示', {
|
|
||||||
confirmButtonText: '是',
|
|
||||||
cancelButtonText: '否',
|
|
||||||
type: 'warning',
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
isInteractivePopupEnabled.value = false;
|
|
||||||
localStorage.setItem('isInteractivePopupEnabled', 'false');
|
|
||||||
localStorage.setItem('isResponsivePopupEnabled', 'true');
|
|
||||||
updateWebSocketConnection();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
isResponsivePopupEnabled.value = false;
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
ElMessageBox.confirm('是否关闭响应式弹窗?', '确认提示', {
|
|
||||||
confirmButtonText: '是',
|
|
||||||
cancelButtonText: '否',
|
|
||||||
type: 'warning',
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
localStorage.setItem('isResponsivePopupEnabled', 'false');
|
|
||||||
updateWebSocketConnection();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
isResponsivePopupEnabled.value = true;
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据开关状态更新 WebSocket 连接
|
|
||||||
const updateWebSocketConnection = () => {
|
|
||||||
if (isInteractivePopupEnabled.value || isResponsivePopupEnabled.value) {
|
|
||||||
globalWebSocket.connectWebSocket();
|
|
||||||
} else {
|
|
||||||
globalWebSocket.closeWebSocket();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.settings-container {
|
.camera-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: #F1F1F1;
|
background-color: #F1F1F1;
|
||||||
|
border-radius: 20px;
|
||||||
|
width: 80vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-header {
|
||||||
|
/* width: 100%; */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.search-row {
|
||||||
|
display: flex;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
top: 0vh;
|
||||||
|
left: 0vw;
|
||||||
|
width: 10vw;
|
||||||
|
margin-left: 1vh;
|
||||||
|
margin: 1vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .search-row input{
|
||||||
|
color: #fffefe;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .search-input .el-input__inner{
|
||||||
|
background-color: #001529;
|
||||||
|
/* background-color:red; */
|
||||||
|
display: flex;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
::v-deep .search-input .el-input__wrapper{
|
||||||
|
background-color: #001529;
|
||||||
|
/* background-color: red; */
|
||||||
|
box-shadow: 0 0 0 0px
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .search-input .el-input__inner::placeholder{
|
||||||
|
color: #fffefe;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.status-filter {
|
||||||
|
position: relative;
|
||||||
|
top: 0vh;
|
||||||
|
left: 0vw;
|
||||||
|
width: 10vw;
|
||||||
|
margin-left: 1vh;
|
||||||
|
margin: 1vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
display: flex;
|
||||||
|
height: 58vh;
|
||||||
|
width: 80vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-part {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 12vw;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 1vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 40%;
|
||||||
|
background-color: #001529;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .camera-item .el-card__header {
|
||||||
|
border-radius: 10px 10px 0 0 ;
|
||||||
|
/* background-color: rgb(41, 12, 150); */
|
||||||
|
background: linear-gradient(to left top, rgb(18, 110, 196), rgba(3, 55, 153, 0.3));
|
||||||
|
height: 4vh;
|
||||||
|
/* min-height: 3vh;
|
||||||
|
min-width: 13vw; */
|
||||||
|
min-height: 50px;
|
||||||
|
min-width: 300px;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
border: none;
|
||||||
|
/* align-items: center; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-id-name {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-camera-id {
|
||||||
|
display: flex;
|
||||||
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-camera-name {
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-camera-setting {
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: start;
|
||||||
|
|
||||||
|
}
|
||||||
|
.settings-button{
|
||||||
|
color: #fffefe;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .camera-item .el-card__body {
|
||||||
|
/* min-height: 10vh;
|
||||||
|
min-width: 13vw; */
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
background-color: #001529;
|
||||||
|
padding: 1vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.div-content,
|
||||||
|
.row-content {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-camera-snapshot {
|
||||||
|
text-align: center;
|
||||||
|
background-color: #001529;
|
||||||
|
width: 10vw;
|
||||||
|
align-content: center;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-img {
|
||||||
|
/* object-fit: cover; */
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 1vh;
|
||||||
|
max-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
::v-deep .status-filter .el-select__wrapper {
|
||||||
|
background-color: #001529;
|
||||||
|
box-shadow: 0 0 0 0 !important;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .camera-select .el-select__wrapper {
|
||||||
|
background-color: #001529;
|
||||||
|
box-shadow: 0 0 0 0 !important;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .camera-select .el-select__selected-item {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .status-filter .el-select__selected-item {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-select {
|
||||||
|
position: relative;
|
||||||
|
top: 0vh;
|
||||||
|
left: 0vw;
|
||||||
|
width: 12vw;
|
||||||
|
margin-left: 0vh;
|
||||||
|
margin-top: 1vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.top-text {
|
||||||
|
display: block;
|
||||||
|
font-size: 15px;
|
||||||
|
width: 7vw;
|
||||||
|
margin: 1vh 0 0 0;
|
||||||
|
padding: 0 0 0 10vw;
|
||||||
|
/* justify-content: center; */
|
||||||
|
align-content: center;
|
||||||
|
background-color: #001529;
|
||||||
|
line-height: 0px;
|
||||||
|
color: aliceblue;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-name-title {
|
||||||
|
padding: 0.2vh;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
display: block;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-grid {
|
||||||
|
margin: 0vh 1vh;
|
||||||
|
padding: 0 0 2vh 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 1vh 0vh;
|
||||||
|
/* height: 39vh;
|
||||||
|
width: 34vw; */
|
||||||
|
width: 68vw;
|
||||||
|
height: 55vh;
|
||||||
|
max-height: 58vh;
|
||||||
|
/* overflow-y: scroll; */
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width:thin;
|
||||||
|
/* background-color: #ffffff; */
|
||||||
|
background-color: #F1F1F1;
|
||||||
|
/* background-color: #c71515; */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .camera-grid::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
} */
|
||||||
|
|
||||||
|
.grid-item {
|
||||||
|
margin: 1vh;
|
||||||
|
position: relative;
|
||||||
|
height: 25vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-snapshot,
|
||||||
|
.camera-large {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
right: 1px;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
background-color: #000000;
|
||||||
|
color: aliceblue;
|
||||||
|
padding: 0 2px 2px 0;
|
||||||
|
border-radius: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button:hover {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-button-container {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 10;
|
||||||
|
opacity: 0.4;
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-button:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-canvas {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-row {
|
.rule-card {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 1rem;
|
||||||
height: 20vh;
|
padding: 1rem;
|
||||||
width: 80vw;
|
background-color: #f9f9f9;
|
||||||
padding: 1vh 1vw;
|
|
||||||
margin: 1vh 2vw;
|
|
||||||
background-color: #001529;
|
|
||||||
border-radius: 8px;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.channel-row {
|
|
||||||
margin: 1vh 2vw;
|
|
||||||
width: 80vw;
|
|
||||||
height: 70vh;
|
|
||||||
background-color: #001529;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue