local_alert/src/components/Max/CenterTop.vue

356 lines
8.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="camera-container">
<div class="top-header">
<el-select v-model="filterStatus" placeholder="筛选状态" @change="fetchCameras" class="status-filter">
<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> &nbsp;名称: {{ camera.name }}
</el-option>
</el-select>
</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>
<img 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>
<!-- 弹窗播放视频 -->
<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>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
import { BoxApi } from '@/utils/boxApi.ts';
import { VideoPlay, VideoPause, VideoCameraFilled } from '@element-plus/icons-vue';
const cameras = ref([]);
const selectedCameras = ref([]);
const showButton = ref(false);
const apiInstance = new BoxApi();
const canvasRefs = ref({});
const selectedCameraId = ref(null);
const dialogVisible = ref(false); // 控制弹窗的显示与隐藏
const currentCamera = ref(null); // 当前选中的摄像头
// 弹窗中的canvas引用
const dialogCanvas = ref(null);
const filterStatus = ref("all");
const fetchCameras = async () => {
try {
const token = localStorage.getItem('alertToken');
const cameraData = await apiInstance.getAllCameras(token);
// 根据 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; // 默认 "all" 显示全部摄像头
}
} catch (error) {
console.error('获取摄像头列表失败:', error);
}
};
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 });
}
};
// 打开弹窗并开始播放
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(() => {
fetchCameras();
});
onBeforeUnmount(() => {
selectedCameras.value.forEach(camera => {
if (camera.player) {
camera.player.destroy();
}
});
});
</script>
<style scoped>
.camera-container {
display: flex;
flex-direction: column;
}
.top-header {
width: 100%;
display: flex;
flex-direction: row;
margin: 0;
}
.status-filter {
position: relative;
top: 0vh;
left: 0vw;
width: 5vw;
margin-left: 1vh;
margin-top: 1vh;
}
::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;
width: 34vw;
height: 39vh;
max-height: 45vh;
overflow-y: scroll;
scrollbar-width: none;
background-color: #001529;
}
.camera-grid::-webkit-scrollbar {
display: none;
}
.grid-item {
margin: 1vh;
position: relative;
height: 17vh;
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%;
height: 100%;
}
</style>