942 lines
25 KiB
Vue
942 lines
25 KiB
Vue
<template>
|
||
<div class="video-contain">
|
||
<div class="video-left">
|
||
<div class="left-title">无人机实时画面 <ReloadOutlined title="刷新" @click="getList" /></div>
|
||
<div class="monitor-status">
|
||
<div class="on-line">
|
||
<i></i>
|
||
<span>在线:{{ onlineCount }}</span>
|
||
</div>
|
||
<span class="line"></span>
|
||
<div class="under-line">
|
||
<i></i>
|
||
<span>离线:{{ underlineCount }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="monitor-list">
|
||
<div class="monitor-item" v-for="(item, index) in monitoringList" :key="index">
|
||
<div
|
||
:class="isSn(item.uavSn) ? 'item-parent active' : 'item-parent'"
|
||
@click="pushStreaming(item)"
|
||
:title="item.uavName"
|
||
>
|
||
<div class="item-img online" v-if="item.uavStatus == 1">
|
||
<img src="@/assets/images/monitoring/monitor-icon.png" alt="" />
|
||
</div>
|
||
<div class="item-img underline" v-else>
|
||
<img src="@/assets/images/monitoring/monitor-no-icon.png" alt="" />
|
||
</div>
|
||
<div class="item-content">
|
||
<span>{{ item.uavName }}</span>
|
||
</div>
|
||
<i :class="item.uavStatus == 1 ? 'online-i' : 'underline-i'"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="video-right">
|
||
<div class="split-screen">
|
||
<div
|
||
:class="selectVal == 1 ? 'split-item active' : 'split-item'"
|
||
@click="selecttype('classtype', 1, 24)"
|
||
>
|
||
<img src="@/assets/images/monitoring/one.png" alt="" />
|
||
<span>单屏</span>
|
||
</div>
|
||
<div
|
||
:class="selectVal == 4 ? 'split-item active' : 'split-item'"
|
||
@click="selecttype('classtype1', 4, 12)"
|
||
>
|
||
<img src="@/assets/images/monitoring/four.png" alt="" />
|
||
<span>四分屏</span>
|
||
</div>
|
||
<div
|
||
:class="selectVal == 9 ? 'split-item active' : 'split-item'"
|
||
@click="selecttype('classtype3', 9, 8)"
|
||
>
|
||
<img src="@/assets/images/monitoring/nine.png" alt="" />
|
||
<span>九分屏</span>
|
||
</div>
|
||
</div>
|
||
<div class="main">
|
||
<div class="conter" ref="box">
|
||
<el-row :gutter="16">
|
||
<el-col
|
||
v-for="(n, index) in state.fornum"
|
||
:key="index"
|
||
:xs="24"
|
||
:sm="24"
|
||
:md="state.clonum"
|
||
:lg="state.clonum"
|
||
:xl="state.clonum"
|
||
style="margin-bottom: 10px"
|
||
>
|
||
<div
|
||
class="player-wrapper"
|
||
element-loading-text="加载中..."
|
||
element-loading-background="#000"
|
||
v-if="onlineList[index]"
|
||
>
|
||
<div class="video-container">
|
||
<div class="video-header">
|
||
<div class="video-title">
|
||
<div class="video-icon">
|
||
<img src="@/assets/images/monitoring/monitor-icon.png" alt="" />
|
||
</div>
|
||
<span>{{ onlineList[index].uavName }} </span>
|
||
</div>
|
||
<div class="video-controls" v-if="onlineList[index].uavType == 'GDY'">
|
||
<button
|
||
class="control-btn"
|
||
title="停止"
|
||
@click="stopGDY(index, onlineList[index])"
|
||
>
|
||
<StopOutlined />
|
||
</button>
|
||
<button
|
||
class="control-btn"
|
||
title="位置"
|
||
@click="showPosition(onlineList[index])"
|
||
>
|
||
<InfoCircleOutlined />
|
||
</button>
|
||
<button class="control-btn" title="设置">
|
||
<i class="el-icon-setting"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="video-content" :style="{ height: videoHeight }">
|
||
<flv-player
|
||
style="width: 100%; height: 100%"
|
||
:src="live_info.url + onlineList[index].uavSn + '.flv'"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-else>
|
||
<div class="video-container">
|
||
<div class="video-header">
|
||
<div class="video-title">
|
||
<div class="video-icon no-icon">
|
||
<img src="@/assets/images/monitoring/monitor-no-icon.png" alt="" />
|
||
</div>
|
||
<span>视频</span>
|
||
</div>
|
||
<div class="video-controls">
|
||
<button class="control-btn" title="全屏">
|
||
<i class="el-icon-full-screen"></i>
|
||
</button>
|
||
<button class="control-btn" title="音量">
|
||
<i class="el-icon-video-camera"></i>
|
||
</button>
|
||
<button class="control-btn" title="设置">
|
||
<i class="el-icon-setting"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="video-content" :style="{ height: videoHeight }">
|
||
<flv-player style="width: 100%; height: 100%" src="" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
<div class="status-bar">
|
||
<span>最后更新:{{ getCurrentDate('time') }} 系统运行正常</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<a-modal
|
||
width="100%"
|
||
wrap-class-name="full-modal"
|
||
v-model:open="gpsVisible"
|
||
:destroyOnClose="true"
|
||
:footer="null"
|
||
title="视频信息弹窗"
|
||
>
|
||
<GPS :uavSn="showModelSn" />
|
||
</a-modal>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { onMounted, reactive, ref, onUnmounted } from 'vue';
|
||
import FlvPlayer from '@/views/demo/workmanagement/monitoring/FlvPlayer.vue'; // 确保路径正确
|
||
import { getUavStateFromRedis } from '@/api/workmanagement/droneDock';
|
||
import { airPortStore } from '@/store/modules/airport';
|
||
import { buildGUID } from '@/utils/uuid';
|
||
import { getCurrentDate } from '@/utils/index';
|
||
import { getClient, createConnection, clientPublish, clientSubscribe } from '@/utils/mqtt';
|
||
import { cameraCode } from '@/utils/debugging/remote';
|
||
import { useMessage } from '@/hooks/web/useMessage';
|
||
import { ReloadOutlined, StopOutlined, InfoCircleOutlined } from '@ant-design/icons-vue';
|
||
import GPS from './gps.vue';
|
||
|
||
const { createMessage } = useMessage();
|
||
const airPortStoreVal = airPortStore();
|
||
const live_info = airPortStoreVal.getLiveInfo;
|
||
const selectVal = ref(1);
|
||
const monitoringList = ref([]);
|
||
const onlineCount = ref(0);
|
||
const underlineCount = ref(0);
|
||
const gpsVisible = ref(false);
|
||
const showModelSn = ref('');
|
||
const state = reactive({
|
||
fullscreen: false,
|
||
fornum: 1,
|
||
clonum: 24,
|
||
classtype1: '',
|
||
classtype2: 'primary',
|
||
classtype3: '',
|
||
classtype4: '',
|
||
classtype5: '',
|
||
items: [false, false, false, false],
|
||
});
|
||
const videoHeight = ref('73vh');
|
||
function selecttype(item, fnum, clo) {
|
||
selectVal.value = fnum;
|
||
state.items = [];
|
||
for (let i = 0; i < fnum; i++) {
|
||
state.items[i] = false;
|
||
}
|
||
state.fornum = fnum;
|
||
state.clonum = clo;
|
||
switch (fnum) {
|
||
case 1:
|
||
videoHeight.value = '73vh';
|
||
break;
|
||
case 4:
|
||
videoHeight.value = '38vh';
|
||
break;
|
||
case 9:
|
||
videoHeight.value = '25vh';
|
||
break;
|
||
}
|
||
if (item === 'classtype1') {
|
||
state.classtype1 = 'primary';
|
||
state.classtype2 = '';
|
||
state.classtype3 = '';
|
||
state.classtype4 = '';
|
||
state.classtype5 = '';
|
||
} else if (item === 'classtype2') {
|
||
state.classtype1 = '';
|
||
state.classtype2 = 'primary';
|
||
state.classtype3 = '';
|
||
state.classtype4 = '';
|
||
state.classtype5 = '';
|
||
} else if (item === 'classtype3') {
|
||
state.classtype1 = '';
|
||
state.classtype2 = '';
|
||
state.classtype3 = 'primary';
|
||
state.classtype4 = '';
|
||
state.classtype5 = '';
|
||
} else if (item === 'classtype4') {
|
||
state.classtype1 = '';
|
||
state.classtype2 = '';
|
||
state.classtype3 = '';
|
||
state.classtype4 = 'primary';
|
||
state.classtype5 = '';
|
||
} else if (item === 'classtype5') {
|
||
state.classtype1 = '';
|
||
state.classtype2 = '';
|
||
state.classtype3 = '';
|
||
state.classtype4 = '';
|
||
state.classtype5 = 'primary';
|
||
}
|
||
}
|
||
const onlineList = ref([]);
|
||
const underlineList = ref([]);
|
||
// 启动直播
|
||
const startLiveFun = (item) => {
|
||
let video_id;
|
||
if (item.node == 'airport') {
|
||
video_id = item.sn + '/165-0-7/normal-0';
|
||
} else {
|
||
video_id = item.sn + '/' + cameraCode(item.type) + '/normal-0';
|
||
}
|
||
// 构建直播启动参数
|
||
const querys = {
|
||
bid: buildGUID(),
|
||
method: 'live_start_push',
|
||
tid: buildGUID(),
|
||
timestamp: new Date().getTime(),
|
||
data: {
|
||
url_type: 1, // 0 = 自适应;如需 RTMP 改为 1;GB28181 为 3;WebRTC 为 4
|
||
url: live_info.rtmp + item.sn,
|
||
video_id: video_id,
|
||
video_quality: 3, // 0=自适应,1=流畅,2=标清,3=高清,4=超清
|
||
},
|
||
};
|
||
|
||
// 发送直播启动指令
|
||
clientPublish('thing/product/' + item.parentSn + '/services', querys);
|
||
};
|
||
const startLive = async (element) => {
|
||
console.log(element);
|
||
const index = liveList.value.findIndex((item) => item.url === `/live/` + element.sn);
|
||
if (index == -1) {
|
||
startLiveFun(element);
|
||
} else {
|
||
// 检查直播流是否活跃
|
||
if (!liveList.value[index].publish.active) {
|
||
startLiveFun(element);
|
||
}
|
||
}
|
||
};
|
||
// 当先点击固定翼sn
|
||
const currentlySn = ref({});
|
||
// 判断是否正在推流中
|
||
const streamingVisible = ref(false);
|
||
// 固定翼视频
|
||
const fixedWing = (item) => {
|
||
console.log(streamingVisible.value);
|
||
console.log(item);
|
||
console.log(item);
|
||
// if (streamingVisible.value && currentlySn.value.uavSn == item.uavSn) {
|
||
// return createMessage.warning('已请求,请稍后');
|
||
// } else {
|
||
streamingVisible.value = true;
|
||
currentlySn.value = item;
|
||
// }
|
||
// 1、检测状态
|
||
// 2、状态不在线时进行推流
|
||
// 3、推流成功后,进行播放
|
||
// 4、推流不成功
|
||
// 检测状态
|
||
const querys = {
|
||
type: 'check_status',
|
||
// type: 'start_forward',
|
||
device_id: currentlySn.value.uavSn,
|
||
};
|
||
console.log(querys);
|
||
|
||
// 发送直播启动指令
|
||
clientPublish('thing/product/' + currentlySn.value.uavSn + '/onboardcase', querys);
|
||
clientSubscribe('thing/product/' + currentlySn.value.uavSn + '/onboardcase');
|
||
};
|
||
const getList = async () => {
|
||
onlineCount.value = 0;
|
||
underlineCount.value = 0;
|
||
await getUavStateFromRedis({}).then((res) => {
|
||
monitoringList.value = res;
|
||
res.forEach((element) => {
|
||
if (element.uavStatus == 1) {
|
||
onlineCount.value++;
|
||
} else {
|
||
underlineCount.value++;
|
||
}
|
||
});
|
||
});
|
||
};
|
||
const liveList = ref([]);
|
||
const getOpenLiveList = async () => {
|
||
// 查询直播流列表
|
||
axios.get(live_info.getUrl + 'api/v1/streams/').then((res) => {
|
||
if (res.data.streams.length > 0) {
|
||
liveList.value = res.data.streams;
|
||
}
|
||
});
|
||
};
|
||
// MQTT连接状态
|
||
const connected = ref(false);
|
||
const connectCallback = () => {
|
||
connected.value = true;
|
||
};
|
||
// 选中推流
|
||
const pushStreaming = async (element) => {
|
||
let item;
|
||
const index = onlineList.value.findIndex((item) => item.name == element.droneName);
|
||
if (index != -1) {
|
||
return;
|
||
}
|
||
if (element.uavType == 'GDY') {
|
||
console.log('固定翼');
|
||
console.log(streamingVisible.value);
|
||
console.log(currentlySn.value.uavSn);
|
||
if (streamingVisible.value && currentlySn.value.uavSn == element.uavSn) {
|
||
return createMessage.warning('已请求,请稍后');
|
||
} else {
|
||
// if (element.uavSn == '123HSDJASLDJKEJSKSUWJS') {
|
||
fixedWing(element);
|
||
createMessage.warning('正在开启' + element.uavName + ',请稍后');
|
||
// } else {
|
||
// createMessage.error('当前设备不在线');
|
||
// }
|
||
}
|
||
} else {
|
||
if (element.uavStatus == 1) {
|
||
item = {
|
||
name: element.uavName,
|
||
sn: element.uavSn,
|
||
parentSn: element.droneSn,
|
||
type: element.uavType,
|
||
node: 'uav',
|
||
};
|
||
startLive(item);
|
||
switch (selectVal.value) {
|
||
case '1':
|
||
onlineList.value = [item];
|
||
break;
|
||
case '4':
|
||
if (onlineList.value.length == 4) {
|
||
onlineList.value[3] = item;
|
||
} else {
|
||
onlineList.value.push(item);
|
||
}
|
||
break;
|
||
case '9':
|
||
if (onlineList.value.length == 9) {
|
||
onlineList.value[8] = item;
|
||
} else {
|
||
onlineList.value.push(item);
|
||
}
|
||
break;
|
||
}
|
||
} else {
|
||
return createMessage.error('当前设备不在线');
|
||
}
|
||
}
|
||
};
|
||
const isSn = (sn) => {
|
||
return onlineList.value.some((item) => item.uavSn == sn);
|
||
};
|
||
onMounted(() => {
|
||
// 初始化MQTT连接
|
||
if (!getClient() || !getClient().connected) {
|
||
createConnection(connectCallback);
|
||
}
|
||
getList();
|
||
getOpenLiveList();
|
||
setTimeout(() => {
|
||
getClient().on('message', (topic, message) => {
|
||
const rs = JSON.parse(message);
|
||
// console.log(rs);
|
||
if ('thing/product/' + currentlySn.value.uavSn + '/onboardcase' == topic) {
|
||
if (rs.type == 'command_response') {
|
||
const topicType = rs.command.type;
|
||
if (topicType == 'check_status') {
|
||
// 检测状态
|
||
if (rs.data.is_input_available && rs.data.is_forwarding) {
|
||
streamingVisible.value = false;
|
||
fixedWingPush(currentlySn.value);
|
||
} else {
|
||
if (rs.data.is_input_available) {
|
||
// 推流
|
||
const querys = {
|
||
type: 'start_forward',
|
||
device_id: currentlySn.value.uavSn,
|
||
};
|
||
clientPublish(
|
||
'thing/product/' + currentlySn.value.uavSn + '/onboardcase',
|
||
querys,
|
||
);
|
||
}
|
||
}
|
||
} else if (topicType == 'start_forward') {
|
||
console.log(rs.data);
|
||
if (rs.status == 'success') {
|
||
console.log('推流成功');
|
||
streamingVisible.value = false;
|
||
fixedWingPush(currentlySn.value);
|
||
} else {
|
||
console.log('推流失败');
|
||
return createMessage.error(rs.message);
|
||
}
|
||
}
|
||
}
|
||
// if (rs.type == 'status_update') {
|
||
// // 检测状态
|
||
// if (rs.is_input_available && rs.is_forwarding) {
|
||
// streamingVisible.value = false;
|
||
// fixedWingPush(currentlySn.value);
|
||
// } else {
|
||
// if (rs.is_input_available) {
|
||
// // 推流
|
||
// const querys = {
|
||
// type: 'start_forward',
|
||
// device_id: currentlySn.value.uavSn,
|
||
// };
|
||
// clientPublish('thing/product/' + currentlySn.value.uavSn + '/onboardcase', querys);
|
||
// }
|
||
// }
|
||
// }
|
||
}
|
||
// setTimeout(() => {
|
||
// console.log(streamingVisible.value);
|
||
// if (streamingVisible.value) {
|
||
// console.log('推流超时');
|
||
// if (!rs) {
|
||
// return createMessage.error('当前设备不在线');
|
||
// } else {
|
||
// if (!rs.command) {
|
||
// return createMessage.error('当前设备不在线');
|
||
// }
|
||
// }
|
||
// }
|
||
// }, 20000);
|
||
});
|
||
}, 1000);
|
||
});
|
||
const fixedWingPush = async (item) => {
|
||
console.log('成功后加入到播放列表里');
|
||
console.log(item);
|
||
console.log(selectVal.value);
|
||
switch (selectVal.value) {
|
||
case 1:
|
||
onlineList.value = [item];
|
||
break;
|
||
case 4:
|
||
if (onlineList.value.length == 4) {
|
||
onlineList.value[3] = item;
|
||
} else {
|
||
onlineList.value.push(item);
|
||
}
|
||
break;
|
||
case 9:
|
||
if (onlineList.value.length == 9) {
|
||
onlineList.value[8] = item;
|
||
} else {
|
||
onlineList.value.push(item);
|
||
}
|
||
break;
|
||
}
|
||
console.log(onlineList.value);
|
||
};
|
||
const stopGDY = async (index, item) => {
|
||
onlineList.value.splice(index, 1);
|
||
const querys = {
|
||
type: 'stop_forward',
|
||
device_id: item.uavSn,
|
||
};
|
||
clientPublish('thing/product/' + item.uavSn + '/onboardcase', querys);
|
||
};
|
||
const showPosition = (item) => {
|
||
showModelSn.value = item.uavSn;
|
||
gpsVisible.value = true;
|
||
};
|
||
onUnmounted(() => {
|
||
// 停止推流
|
||
onlineList.value.forEach((item) => {
|
||
if (item.uavType == 'GDY') {
|
||
const querys = {
|
||
type: 'stop_forward',
|
||
device_id: currentlySn.value.uavSn,
|
||
};
|
||
clientPublish('thing/product/' + item.uavSn + '/onboardcase', querys);
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
<style lang="less">
|
||
.full-modal {
|
||
background-color: #00152a;
|
||
.ant-modal {
|
||
max-width: 100%;
|
||
top: 0;
|
||
padding-bottom: 0;
|
||
margin: 0;
|
||
}
|
||
.ant-modal-header {
|
||
background-color: #10203a;
|
||
border: none;
|
||
.ant-modal-title {
|
||
color: #fff;
|
||
}
|
||
}
|
||
.ant-modal-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
background-color: #00152a;
|
||
height: calc(100vh);
|
||
button {
|
||
color: #fff;
|
||
}
|
||
}
|
||
.ant-modal-body {
|
||
flex: 1;
|
||
}
|
||
}
|
||
</style>
|
||
<style scoped lang="less">
|
||
.video-contain {
|
||
display: flex;
|
||
background: #0f0f13;
|
||
height: calc(100vh - 80px);
|
||
width: 100%;
|
||
}
|
||
.video-left {
|
||
width: 16%;
|
||
background: #16161a;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
border-right: 1px solid #232328;
|
||
.left-title {
|
||
font-weight: 500;
|
||
font-size: 18px;
|
||
color: #ffffff;
|
||
padding: 20px 15px 15px 0;
|
||
width: 90%;
|
||
text-align: left;
|
||
margin-left: 20px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
cursor: pointer;
|
||
}
|
||
.monitor-status {
|
||
width: 90%;
|
||
height: 50px;
|
||
background: #1a1a20;
|
||
color: #fff;
|
||
display: flex;
|
||
align-items: center;
|
||
border-radius: 6px;
|
||
margin-bottom: 20px;
|
||
.on-line {
|
||
width: 49%;
|
||
text-align: center;
|
||
i {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 8px;
|
||
background: #4bd884;
|
||
display: inline-block;
|
||
margin-right: 6px;
|
||
}
|
||
span {
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
.line {
|
||
width: 1px;
|
||
height: 30px;
|
||
background: #2a2a33;
|
||
opacity: 0.8;
|
||
}
|
||
.under-line {
|
||
width: 49%;
|
||
text-align: center;
|
||
i {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 8px;
|
||
background: #fe4848;
|
||
margin-right: 6px;
|
||
display: inline-block;
|
||
}
|
||
span {
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.video-right {
|
||
width: 84%;
|
||
background: #0f0f13;
|
||
display: flex;
|
||
flex-direction: column;
|
||
.split-screen {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 20px;
|
||
background: #16161a;
|
||
border-bottom: 1px solid #232328;
|
||
.split-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100px;
|
||
height: 36px;
|
||
color: #fff;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
transition: all 0.3s ease;
|
||
img {
|
||
width: 16px;
|
||
margin-right: 8px;
|
||
}
|
||
span {
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
.active {
|
||
background: #1e75ff;
|
||
color: #ffffff;
|
||
}
|
||
.split-item:first-child {
|
||
border: 1px solid #2a2a33;
|
||
border-radius: 6px 0 0 6px;
|
||
}
|
||
.split-item:nth-child(2) {
|
||
border-top: 1px solid #2a2a33;
|
||
border-bottom: 1px solid #2a2a33;
|
||
}
|
||
.split-item:last-child {
|
||
border: 1px solid #2a2a33;
|
||
border-radius: 0 6px 6px 0;
|
||
}
|
||
}
|
||
}
|
||
.main {
|
||
flex: 1;
|
||
padding: 10px 20px;
|
||
overflow: auto;
|
||
}
|
||
.player-wrapper {
|
||
height: 100%;
|
||
min-height: 200px;
|
||
}
|
||
.video-container {
|
||
background: #16161a;
|
||
border-radius: 6px;
|
||
overflow: hidden;
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.video-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 12px 16px;
|
||
background: #1a1a20;
|
||
border-bottom: 1px solid #232328;
|
||
}
|
||
.video-title {
|
||
display: flex;
|
||
align-items: center;
|
||
.video-icon {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 4px;
|
||
background: #1e75ff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 10px;
|
||
img {
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
}
|
||
.no-icon {
|
||
background: #25252f;
|
||
}
|
||
span {
|
||
font-size: 14px;
|
||
color: #ffffff;
|
||
font-weight: 400;
|
||
}
|
||
}
|
||
.video-controls {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.control-btn {
|
||
width: 28px;
|
||
height: 28px;
|
||
border: none;
|
||
background: transparent;
|
||
color: #92929d;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 4px;
|
||
margin-left: 8px;
|
||
transition: all 0.3s ease;
|
||
&:hover {
|
||
background: #25252f;
|
||
color: #ffffff;
|
||
}
|
||
i {
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
.video-content {
|
||
background: #0f0f13;
|
||
position: relative;
|
||
min-height: 150px;
|
||
video {
|
||
height: 100%;
|
||
}
|
||
}
|
||
.status-bar {
|
||
margin-top: 20px;
|
||
padding: 12px 20px;
|
||
background: #16161a;
|
||
border-radius: 6px;
|
||
text-align: right;
|
||
span {
|
||
font-size: 14px;
|
||
color: #92929d;
|
||
}
|
||
}
|
||
.monitor-list {
|
||
width: 90%;
|
||
margin-top: 10px;
|
||
.monitor-item {
|
||
.active {
|
||
border-left: 4px solid #1e75ff;
|
||
}
|
||
&:hover {
|
||
background: #1e1e26;
|
||
}
|
||
.item-parent {
|
||
display: flex;
|
||
align-items: center;
|
||
color: #fff;
|
||
font-size: 14px;
|
||
padding: 12px 16px;
|
||
margin-bottom: 8px;
|
||
background: #1a1a20;
|
||
border-radius: 6px;
|
||
transition: all 0.3s ease;
|
||
cursor: pointer;
|
||
}
|
||
.item-child {
|
||
display: flex;
|
||
align-items: center;
|
||
color: #fff;
|
||
font-size: 14px;
|
||
padding: 12px 16px;
|
||
margin-bottom: 8px;
|
||
background: #1a1a20;
|
||
border-radius: 6px;
|
||
transition: all 0.3s ease;
|
||
cursor: pointer;
|
||
margin-left: 10px;
|
||
}
|
||
.item-img {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 6px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 12px;
|
||
img {
|
||
width: 20px;
|
||
}
|
||
}
|
||
.online {
|
||
background: #1e75ff;
|
||
}
|
||
.underline {
|
||
background: #25252f;
|
||
}
|
||
.item-content {
|
||
flex: 1;
|
||
min-width: 0;
|
||
span {
|
||
display: block;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
font-size: 14px;
|
||
color: #ffffff;
|
||
}
|
||
}
|
||
.online-i {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
background: #4bd884;
|
||
display: inline-block;
|
||
margin-left: 12px;
|
||
position: relative;
|
||
&:after {
|
||
content: '';
|
||
position: absolute;
|
||
top: -2px;
|
||
left: -2px;
|
||
right: -2px;
|
||
bottom: -2px;
|
||
border-radius: 50%;
|
||
background: #4bd884;
|
||
opacity: 0.5;
|
||
animation: pulse 2s infinite;
|
||
}
|
||
}
|
||
.underline-i {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
background: #fe4848;
|
||
display: inline-block;
|
||
margin-left: 12px;
|
||
}
|
||
}
|
||
}
|
||
@keyframes pulse {
|
||
0% {
|
||
transform: scale(0.8);
|
||
opacity: 0.5;
|
||
}
|
||
70% {
|
||
transform: scale(1.2);
|
||
opacity: 0;
|
||
}
|
||
100% {
|
||
transform: scale(0.8);
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
/* 响应式布局调整 */
|
||
@media screen and (max-width: 1200px) {
|
||
.video-left {
|
||
width: 20%;
|
||
}
|
||
.video-right {
|
||
width: 80%;
|
||
}
|
||
.main {
|
||
padding: 15px;
|
||
}
|
||
}
|
||
|
||
@media screen and (max-width: 992px) {
|
||
.video-left {
|
||
width: 25%;
|
||
}
|
||
.video-right {
|
||
width: 75%;
|
||
}
|
||
.left-title {
|
||
font-size: 16px !important;
|
||
}
|
||
.split-screen {
|
||
padding: 15px;
|
||
}
|
||
.split-item {
|
||
width: 90px !important;
|
||
height: 32px !important;
|
||
}
|
||
}
|
||
|
||
@media screen and (max-width: 768px) {
|
||
.video-contain {
|
||
flex-direction: column;
|
||
}
|
||
.video-left {
|
||
width: 100%;
|
||
border-right: none;
|
||
border-bottom: 1px solid #232328;
|
||
max-height: 300px;
|
||
}
|
||
.video-right {
|
||
width: 100%;
|
||
}
|
||
.monitor-list {
|
||
max-height: 200px;
|
||
overflow-y: auto;
|
||
}
|
||
.main {
|
||
padding: 10px;
|
||
}
|
||
}
|
||
</style>
|