@@ -22,3 +22,4 @@ export * from "./statistion"; | |||
export * from "./usermanage"; | |||
export * from "./attendance"; | |||
export * from "./violation"; | |||
export * from "./screen" |
@@ -0,0 +1,54 @@ | |||
/** | |||
* @description | |||
* @license Apache License Version 2.0 | |||
* @Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛 | |||
* @remarks | |||
* SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款: | |||
* 1.请不要删除和修改根目录下的LICENSE文件。 | |||
* 2.请不要删除和修改SimpleAdmin源码头部的版权声明。 | |||
* 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin | |||
* 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。 | |||
* 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为不要删除和修改作者声明。 | |||
* 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关 | |||
* @see https://gitee.com/dotnetmoyu/SimpleAdmin | |||
*/ | |||
import { moduleRequest } from "@/api/request"; | |||
const http = moduleRequest("/large/screen/"); | |||
/** | |||
* @Description: 单页管理 | |||
* @Author: yxq | |||
* @Date: 2023-12-15 15:34:54 | |||
*/ | |||
const screenApi = { | |||
/** 获取大屏首页数据 */ | |||
getHomeData(params: any={}) { | |||
return http.get("getHomeData", params); | |||
}, | |||
/** 获取大屏学生归寝数据 */ | |||
getStudentReturnBed(params: any={}) { | |||
return http.get("getStudentReturnBed", params); | |||
}, | |||
/** 获取大屏智慧课堂数据 */ | |||
getSmartClassroom(params: any={}) { | |||
return http.get("getSmartClassroom", params); | |||
}, | |||
/** 获取宿舍楼 */ | |||
getNoPageList(params: any={}) { | |||
return http.get("getNoPageList", params); | |||
}, | |||
/** 获取班级列表 */ | |||
getPersonSetNoPageList(params: any={}) { | |||
return http.get("getPersonSetNoPageList", params); | |||
}, | |||
/** 获取单页详情 */ | |||
detail(params: any) { | |||
return http.get("getStartVideoLive", params); | |||
}, | |||
/** 获取单页详情 */ | |||
stopUrl(params: any) { | |||
return http.get("getStopVideoLive", params); | |||
}, | |||
}; | |||
export { screenApi }; |
@@ -55,7 +55,32 @@ export const staticRouter: RouteRecordRaw[] = [ | |||
component: () => import("@/views/violation/portrait/detail.vue") | |||
} | |||
] | |||
} | |||
}, | |||
// AI智能预警分析平台 | |||
{ | |||
path: "/screen", | |||
name: "AI智能预警分析平台", | |||
component: () => import("@/views/screen/index.vue"), | |||
meta: { | |||
title: "AI智能预警分析平台" | |||
}, | |||
}, | |||
{ | |||
name: "AI智能预警分析平台-智慧课堂", | |||
meta: { | |||
title: "AI智能预警分析平台-智慧课堂" | |||
}, | |||
path: "/screen/classroom", | |||
component: () => import("@/views/screen/classroom.vue") | |||
}, | |||
{ | |||
name: "AI智能预警分析平台-学生归寝", | |||
meta: { | |||
title: "AI智能预警分析平台-学生归寝" | |||
}, | |||
path: "/screen/stureturn", | |||
component: () => import("@/views/screen/stureturn.vue") | |||
}, | |||
]; | |||
/** | |||
@@ -0,0 +1,230 @@ | |||
<template> | |||
<div class="fullscreen-container"> | |||
<myHeader place="1" ref="myHeaderRef" @getClassRoomInfo="getClassRoomInfo"></myHeader> | |||
<div style="position: absolute; width: 100%; display: flex; justify-content: space-between; top: 102px"> | |||
<img style="width: 846px" src="/static/screen/img/header-aside-left.png" alt="" /> | |||
<img style="width: 845px" src="/static/screen/img/header-aside-right.png" alt="" /> | |||
</div> | |||
<main v-if="ready"> | |||
<div class="main-top"> | |||
<div class="tearchBox myborder"> | |||
<div class="headimgBox"> | |||
<div class="imgBox"> | |||
<img src="/static/screen/testimg/7.png" alt="" /> | |||
</div> | |||
<div class="title">{{ classRoom.teacher }}老师</div> | |||
<div class="status" :style="{ backgroundColor: classRoom.isClassing ? '#11ca2e' : '#b1b3b8' }"> | |||
{{ classRoom.isClassing ? "正在上课" : "未上课" }} | |||
</div> | |||
<!-- <img class="setting" src="/static/screen/img/setting.png" alt="" /> --> | |||
</div> | |||
<div class="classTime"> | |||
<div> | |||
<div>上课时间</div> | |||
<div class="time">{{ classRoom.classTime }}</div> | |||
</div> | |||
<div> | |||
<div>下课时间</div> | |||
<div class="time">{{ classRoom.classBreakTime }}</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="video"> | |||
<!-- <img style="width: 100%; height: 100%" src="/static/screen/testimg/3.png" alt="" /> --> | |||
<iframe :src="rtsUrl" frameborder="0" style="width: 100%; height: 100%"></iframe> | |||
</div> | |||
<div class="stuEnterList myborder"> | |||
<stuEnterList :screenData="screenData"></stuEnterList> | |||
</div> | |||
</div> | |||
<div class="main-bottom"> | |||
<div class="classStatistics myborder"> | |||
<classStatistics :screenData="screenData"></classStatistics> | |||
</div> | |||
<div class="classNotice myborder"> | |||
<classNotice :screenData="screenData"></classNotice> | |||
</div> | |||
<div class="classAnalysis myborder"> | |||
<classAnalysis :screenData="screenData" /> | |||
</div> | |||
</div> | |||
</main> | |||
</div> | |||
</template> | |||
<script setup> | |||
import myHeader from "./component/header.vue"; | |||
import stuEnterList from "./component/classroom/stuEnterList.vue"; | |||
import classNotice from "./component/classroom/classNotice.vue"; | |||
import classAnalysis from "./component/classroom/classAnalysis.vue"; | |||
import classStatistics from "./component/classroom/classStatistics.vue"; | |||
import { screenApi } from "@/api"; | |||
const myHeaderRef = ref(null); | |||
const screenData = ref({}); | |||
const classInfo = ref({}); | |||
const ready = ref(false); | |||
const classRoom = ref({}); | |||
const alarmList = ref([]); | |||
const rollCall = ref([]); | |||
const statisti = ref({ | |||
labale: [], | |||
value: [] | |||
}); | |||
const getClassRoomInfo = obj => { | |||
classInfo.value = obj; | |||
// 归寝人数信息 | |||
ready.value = false; | |||
screenApi.getSmartClassroom({ personSetId: obj.value }).then(res => { | |||
if (res.code == 200) { | |||
screenData.value = res.data; | |||
classRoom.value = screenData.value.classRoom; | |||
let classTime = new Date(classRoom.value.classTime).valueOf(); | |||
let classBreakTime = new Date(classRoom.value.classBreakTime).valueOf(); | |||
let currTime = new Date().valueOf(); | |||
if (currTime > classTime && currTime < classBreakTime) { | |||
classRoom.value.isClassing = true; | |||
} | |||
showRts(classRoom.value.cameraId); | |||
classInfo.value = screenData.value.classInfo; | |||
alarmList.value = screenData.value.alarmList; | |||
rollCall.value = screenData.value.rollCall; | |||
statisti.value = screenData.value.statisti; | |||
console.log(res.data); | |||
ready.value = true; | |||
nextTick(() => { | |||
myHeaderRef.value.setBoder(); | |||
}); | |||
} | |||
}); | |||
}; | |||
// 监控 | |||
const streamId = ref(""); | |||
const videoToken = ref(""); | |||
const rtsUrl = ref(""); | |||
const sensorId = ref(""); | |||
const showRts = sensorId_ => { | |||
if (sensorId_) { | |||
if (sensorId.value == sensorId_) return; | |||
if (streamId.value) stopUrl(); | |||
let rtsUrl_ = "/static/rtsPlayer.html?height=556px&rtsUrl="; | |||
sensorId.value = sensorId_; | |||
screenApi.detail({ sensorId: sensorId_ }).then(res => { | |||
if (res.code == 200) { | |||
if (res.data.rtsPullStreamUrls[0]) { | |||
rtsUrl.value = rtsUrl_ + res.data.rtsPullStreamUrls[0].url; | |||
} | |||
streamId.value = res.data.streamId; | |||
videoToken.value = res.data.videoToken; | |||
} | |||
}); | |||
} | |||
}; | |||
const stopUrl = () => { | |||
if (!streamId.value) return; | |||
screenApi.stopUrl({ | |||
sensorId: sensorId.value, | |||
streamId: streamId.value, | |||
videoToken: videoToken.value | |||
}); | |||
}; | |||
onUnmounted(() => { | |||
stopUrl(); | |||
}); | |||
</script> | |||
<style lang="scss" scoped> | |||
main { | |||
padding: 41px 53px 0px 58px; | |||
.main-top { | |||
height: 556px; | |||
display: flex; | |||
justify-content: space-between; | |||
> div { | |||
height: 100%; | |||
box-sizing: border-box; | |||
} | |||
.tearchBox { | |||
width: 208px; | |||
.headimgBox { | |||
box-sizing: border-box; | |||
border-bottom: 1px solid #2e84e5; | |||
height: 304px; | |||
text-align: center; | |||
position: relative; | |||
.setting { | |||
position: absolute; | |||
top: 37px; | |||
right: 24px; | |||
width: 25px; | |||
} | |||
.imgBox { | |||
margin-top: 83px; | |||
width: 87px; | |||
height: 87px; | |||
border-radius: 50%; | |||
overflow: hidden; | |||
display: inline-block; | |||
img { | |||
width: 100%; | |||
height: 100%; | |||
} | |||
} | |||
.title { | |||
color: #fff; | |||
font-size: 18px; | |||
margin-top: 16px; | |||
} | |||
.status { | |||
margin-top: 16px; | |||
display: inline-block; | |||
border-radius: 4px; | |||
width: 67px; | |||
height: 26px; | |||
color: #fff; | |||
font-size: 12px; | |||
line-height: 26px; | |||
} | |||
} | |||
.classTime { | |||
color: #78dfff; | |||
text-align: center; | |||
font-size: 14px; | |||
> div { | |||
margin-top: 50px; | |||
.time { | |||
margin-top: 11px; | |||
} | |||
} | |||
} | |||
} | |||
.video { | |||
width: 872px; | |||
} | |||
.stuEnterList { | |||
width: 1338px; | |||
padding: 35px 40px; | |||
} | |||
} | |||
.main-bottom { | |||
height: 652px; | |||
margin-top: 36px; | |||
display: flex; | |||
justify-content: space-between; | |||
> div { | |||
height: 100%; | |||
box-sizing: border-box; | |||
} | |||
.classStatistics { | |||
width: 579px; | |||
padding: 35px 37px; | |||
} | |||
.classNotice { | |||
width: 1095px; | |||
padding: 30px 40px; | |||
} | |||
.classAnalysis { | |||
width: 742px; | |||
padding: 30px 45px; | |||
} | |||
} | |||
} | |||
</style> | |||
@@ -0,0 +1,108 @@ | |||
<template> | |||
<div style="width: 100%; height: 100%"> | |||
<div class="commontitle"> | |||
<div class="left"> | |||
<span>课堂行为人数分析</span> | |||
<img src="/static/screen/img/titleIcon.png" alt="" /> | |||
</div> | |||
<div class="right"></div> | |||
</div> | |||
<div style="width: 100%; height: 450px; margin-top: 40px"> | |||
<div ref="chart1" style="width: 100%; height: 100%"></div> | |||
</div> | |||
</div> | |||
</template> | |||
<script setup> | |||
import * as echarts from "echarts"; | |||
let chart1 = ref(null); | |||
const props = defineProps({ | |||
screenData: {} | |||
}); | |||
const statisti = ref({}); | |||
onMounted(() => { | |||
statisti.value = props.screenData.statisti; | |||
getCharts1(); | |||
}); | |||
const getCharts1 = () => { | |||
const chart = echarts.init(chart1.value); | |||
let data = statisti.value.labale.map((e, i) => { | |||
return { | |||
name: e, | |||
value: statisti.value.value[i] | |||
}; | |||
}); | |||
data.push({ | |||
name: "正常听讲", | |||
value: props.screenData.normalClass | |||
}); | |||
let option = { | |||
color: ["#14C9C9", "#165DFF", "#CBE0FF", "#9FDB1D", "#C42ED1", "#F7BA1E"], | |||
legend: { | |||
show: true, | |||
itemGap: 20, | |||
textStyle: { | |||
color: "#2E84E5", | |||
fontSize: 12 | |||
} | |||
}, | |||
tooltip: { | |||
trigger: "item" | |||
}, | |||
animation: true, | |||
title: { | |||
text: props.screenData.studentList.length, | |||
subtext: "班级总人数", | |||
left: "center", | |||
top: "52%", | |||
textStyle: { | |||
color: "#78DFFF", | |||
fontSize: 24, | |||
fontWeight: "600" | |||
}, | |||
subtextStyle: { | |||
color: "#2E84E5", | |||
fontSize: 14 | |||
} | |||
}, | |||
series: [ | |||
{ | |||
tooltip: { show: false }, | |||
type: "pie", | |||
radius: ["0", "38%"], | |||
padAngle: 0, | |||
color: ["#182665"], | |||
data: [ | |||
{ | |||
name: "", | |||
value: 1 | |||
} | |||
], | |||
top: 80, | |||
label: { | |||
show: false | |||
} | |||
}, | |||
{ | |||
type: "pie", | |||
radius: ["70%", "90%"], | |||
padAngle: 5, | |||
data, | |||
top: 80, | |||
label: { | |||
color: "#FFF", | |||
formatter: "{b0}: {c}({d}%)" | |||
} | |||
} | |||
] | |||
}; | |||
chart.setOption(option); | |||
window.addEventListener("resize", function () { | |||
chart.resize(); | |||
}); | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
</style> |
@@ -0,0 +1,92 @@ | |||
<template> | |||
<div style="width: 100%; height: 100%"> | |||
<div class="commontitle"> | |||
<div class="left"> | |||
<span>学员课堂告警情况</span> | |||
<img src="/static/screen/img/titleIcon.png" alt="" /> | |||
</div> | |||
<div class="right"> | |||
<img src="/static/screen/img/redLight.png" alt="" /> | |||
<span>告警数量({{ alarmList.length }})</span> | |||
</div> | |||
</div> | |||
<div style="height: 568px; overflow: auto; margin-top: 4px"> | |||
<ul> | |||
<li v-for="item in alarmList" :key="item.id"> | |||
<div class="name">{{ item.personName || "未知" }}</div> | |||
<div class="imgbox"> | |||
<el-image :src="item.snapshotUrl" fit="cover" :preview-src-list="[item.snapshotUrl]"></el-image> | |||
</div> | |||
<div class="title">{{ item.alarmTypeDesc }}</div> | |||
</li> | |||
</ul> | |||
<div v-if="alarmList.length == 0" style="text-align: center; margin-top: 160px; color: #78dfff; font-size: 14px">暂无告警</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script setup> | |||
const props = defineProps({ | |||
screenData: {} | |||
}); | |||
const alarmList = ref([]); | |||
onMounted(() => { | |||
alarmList.value = props.screenData.alarmList || []; | |||
}); | |||
</script> | |||
<style scoped lang="scss"> | |||
.commontitle { | |||
.right { | |||
color: #ffa1a1; | |||
font-size: 14px; | |||
img { | |||
width: 18px; | |||
position: relative; | |||
top: 4px; | |||
right: 6px; | |||
} | |||
} | |||
} | |||
ul { | |||
display: flex; | |||
flex-wrap: wrap; | |||
li { | |||
width: 129.366px; | |||
height: 173.506px; | |||
background: #182665; | |||
padding: 0 10px; | |||
box-sizing: border-box; | |||
padding-top: 6px; | |||
margin-right: 16px; | |||
margin-bottom: 7.8px; | |||
margin-top: 7.8px; | |||
&:nth-child(7n) { | |||
margin-right: 0px; | |||
} | |||
.name { | |||
color: #fff; | |||
text-align: center; | |||
font-size: 14px; | |||
} | |||
.imgbox { | |||
margin-top: 5px; | |||
width: 109.702px; | |||
height: 112.44px; | |||
object-fit: cover; | |||
text-align: center; | |||
.el-image { | |||
height: 100%; | |||
width: 100%; | |||
} | |||
} | |||
.title { | |||
background: #0c4dcf; | |||
width: 109.702px; | |||
height: 23px; | |||
color: #fff; | |||
text-align: center; | |||
font-size: 14px; | |||
line-height: 23px; | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,104 @@ | |||
<template> | |||
<div style="width: 100%; height: 100%"> | |||
<div class="commontitle"> | |||
<div class="left"> | |||
<span>课堂行为次数统计</span> | |||
<img src="/static/screen/img/titleIcon.png" alt="" /> | |||
</div> | |||
<div class="right"></div> | |||
</div> | |||
<div style="width: 100%; height: 500px; margin-top: 40px"> | |||
<div ref="chart1" style="width: 540px; height: 500px"></div> | |||
</div> | |||
</div> | |||
</template> | |||
<script setup> | |||
import * as echarts from "echarts"; | |||
const props = defineProps({ | |||
screenData: {} | |||
}); | |||
const statisti = ref({}); | |||
let chart1 = ref(null); | |||
onMounted(() => { | |||
statisti.value = props.screenData.statisti; | |||
getCharts1(); | |||
}); | |||
const getCharts1 = () => { | |||
const chart = echarts.init(chart1.value); | |||
let data = statisti.value.labale.map((e, i) => { | |||
return { | |||
name: e, | |||
value: statisti.value.value[i] | |||
}; | |||
}); | |||
let data_ = data.map(e => e.name); | |||
let color = ["#14C9C9", "#165DFF", "#CBE0FF", "#9FDB1D", "#C42ED1", "#F7BA1E"]; | |||
data = data.map((e, i) => { | |||
e.itemStyle = { | |||
color: color[i % color.length] | |||
}; | |||
return e; | |||
}); | |||
let option = { | |||
textStyle: { | |||
color: "#fff", | |||
fontSize: 12 | |||
}, | |||
grid: { | |||
show: false, | |||
left: "12%" | |||
}, | |||
tooltip: { | |||
show: true, | |||
trigger: "item" //触发类型 | |||
}, | |||
animation: true, | |||
xAxis: { | |||
type: "value", //坐标轴类型 | |||
position: "top", //x 轴的位置 bottom top | |||
minInterval: 1, | |||
axisLine: { | |||
show: true | |||
}, | |||
splitLine: { | |||
show: true, | |||
lineStyle: { | |||
color: "#666", | |||
type: "dashed" | |||
} | |||
}, | |||
axisTick: { | |||
show: true, | |||
inside: false | |||
} | |||
}, | |||
yAxis: { | |||
type: "category", | |||
name: "行为", | |||
nameLocation: "start", | |||
position: "left", | |||
axisTick: false, | |||
axisLabel: { | |||
show: true, | |||
width: 100, | |||
overflow: "break" | |||
}, | |||
data: data_ | |||
}, | |||
series: [ | |||
{ | |||
type: "bar", | |||
barWidth: 7, | |||
data | |||
} | |||
] | |||
}; | |||
chart.setOption(option); | |||
window.addEventListener("resize", function () { | |||
chart.resize(); | |||
}); | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
</style> |
@@ -0,0 +1,130 @@ | |||
<template> | |||
<div style="width: 100%; height: 100%"> | |||
<div class="commontitle"> | |||
<div class="left"> | |||
<span>学员课堂出勤</span> | |||
<img src="/static/screen/img/titleIcon.png" alt="" /> | |||
</div> | |||
<div class="right"></div> | |||
</div> | |||
<div style="margin-top: 12px; overflow: auto; height: 448px"> | |||
<ul> | |||
<li v-for="(item, index) in studentList" :key="index"> | |||
<div class="left"> | |||
<el-image :src="item.faceUrl" fit="cover" :preview-src-list="[item.faceUrl]"></el-image> | |||
<!-- <img :src="item.faceUrl" alt="" /> --> | |||
</div> | |||
<div class="right"> | |||
<div class="name">{{ item.name }}</div> | |||
<div class="stucode">学号:{{ item.personId }}</div> | |||
<div class="enterTime">进班时间:{{ item.insTime }}</div> | |||
<div class="statusBox"> | |||
<div class="status" :style="{ backgroundColor: item.state != '正常' ? '#9a112c' : '#123CA0' }"> | |||
<div class="circle"></div> | |||
<div>{{ item.state }}</div> | |||
</div> | |||
<img v-if="item.state != '正常'" src="/static/screen/img/redLight.png" alt="" /> | |||
</div> | |||
</div> | |||
</li> | |||
</ul> | |||
</div> | |||
</div> | |||
</template> | |||
<script setup> | |||
const props = defineProps({ | |||
screenData: {} | |||
}); | |||
const studentList = ref([]); | |||
onMounted(() => { | |||
studentList.value = props.screenData.studentList; | |||
studentList.value = studentList.value.map(e => { | |||
e.faceUrl = e.faces.length ? e.faces[0].faceUrl : ""; | |||
return e; | |||
}); | |||
}); | |||
</script> | |||
<style scoped lang="scss"> | |||
ul { | |||
display: flex; | |||
flex-wrap: wrap; | |||
li { | |||
display: flex; | |||
width: 398.372px; | |||
height: 121px; | |||
background: #182665; | |||
margin-right: 26px; | |||
margin-bottom: 28px; | |||
margin-top: 14px; | |||
margin-bottom: 14px; | |||
&:nth-child(3n) { | |||
margin-right: 0px; | |||
} | |||
.left { | |||
width: 131px; | |||
height: 100%; | |||
box-sizing: border-box; | |||
position: relative; | |||
padding-top: 14px; | |||
text-align: center; | |||
&::after { | |||
content: ""; | |||
display: block; | |||
position: absolute; | |||
width: 1px; | |||
height: 93px; | |||
right: 0; | |||
top: 12px; | |||
background: #1848bc; | |||
} | |||
.el-image { | |||
height: 93px; | |||
width: 73px; | |||
} | |||
} | |||
.right { | |||
flex: 1; | |||
padding-left: 29px; | |||
padding-top: 15px; | |||
padding-right: 16px; | |||
.name { | |||
color: #fff; | |||
font-size: 14px; | |||
line-height: 1.5; | |||
} | |||
.stucode { | |||
color: #78dfff; | |||
font-size: 12px; | |||
line-height: 1.5; | |||
margin-top: 2px; | |||
} | |||
.enterTime { | |||
color: #78dfff; | |||
font-size: 12px; | |||
line-height: 1.5; | |||
} | |||
.statusBox { | |||
display: flex; | |||
justify-content: space-between; | |||
margin-top: 10px; | |||
.status { | |||
width: 82px; | |||
height: 22px; | |||
color: #fff; | |||
font-size: 12px; | |||
display: flex; | |||
justify-content: space-between; | |||
line-height: 20px; | |||
align-content: center; | |||
padding-left: 9px; | |||
padding-right: 16px; | |||
box-sizing: border-box; | |||
} | |||
img { | |||
width: 15px; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,377 @@ | |||
<template> | |||
<header> | |||
<div class="left"> | |||
<div> | |||
<el-select style="width: 321px" class="header-select" v-model="place" :teleported="false" placeholder="场景选择" @change="placeChange"> | |||
<el-option v-for="item in placeOptions" :value="item.value" :label="item.label" :key="item.value"></el-option> | |||
</el-select> | |||
<el-select | |||
v-if="place == 1" | |||
style="width: 186px; margin-left: 14px" | |||
class="header-select" | |||
v-model="classValue" | |||
:teleported="false" | |||
placeholder="班级选择" | |||
@change="classChange" | |||
> | |||
<el-option v-for="item in classoptions" :value="item.value" :label="item.label" :key="item.value"></el-option> | |||
</el-select> | |||
<el-select | |||
v-if="place == 2" | |||
style="width: 186px; margin-left: 14px" | |||
class="header-select" | |||
v-model="buildId" | |||
:teleported="false" | |||
placeholder="宿舍楼选择" | |||
@change="buildIdChange" | |||
> | |||
<el-option v-for="item in dormitoryOptions" :value="item.value" :label="item.label" :key="item.value"></el-option> | |||
</el-select> | |||
</div> | |||
</div> | |||
<div class="middle"> | |||
<img style="width: 79px; height: 2px; position: relative; top: 98px; left: 46px" src="/static/screen/img/title-bg_aside.png" alt="" /> | |||
<img src="/static/screen/img/title-bg.png" alt="" /> | |||
<img style="width: 79px; height: 2px; position: relative; top: 98px; right: 42px" src="/static/screen/img/title-bg_aside.png" alt="" /> | |||
<h2>AI智能预警分析平台</h2> | |||
</div> | |||
<div class="right"> | |||
<div class="time">{{ currentDate }}</div> | |||
<img src="/static/screen/img/greenLight.png" alt="" /> | |||
</div> | |||
</header> | |||
</template> | |||
<script setup> | |||
import router from "@/routers"; | |||
import { screenApi } from "@/api"; | |||
const emit = defineEmits(["getStuReturnInfo", "getClassRoomInfo"]); | |||
const props = defineProps({ | |||
place: "1" | |||
}); | |||
// 场景选择 | |||
const placeOptions = ref([ | |||
{ value: "0", label: "场景选择", url: "/screen" }, | |||
{ value: "1", label: "智慧课堂", url: "/screen/classroom" }, | |||
{ value: "2", label: "学生归寝", url: "/screen/stureturn" } | |||
]); | |||
const place = ref(""); | |||
if (props.place) place.value = props.place; | |||
const placeChange = value => { | |||
let obj = placeOptions.value.find(e => e.value == value); | |||
router.push(obj.url); | |||
}; | |||
// 班级选择 | |||
const classValue = ref(""); | |||
const classoptions = ref([]); | |||
screenApi.getPersonSetNoPageList().then(res => { | |||
if (res.code == 200) { | |||
classoptions.value = res.data.map(e => { | |||
e.label = e.personSetName; | |||
e.value = e.personSetId; | |||
return e; | |||
}); | |||
classValue.value = classoptions.value[0].value; | |||
emit("getClassRoomInfo", classoptions.value[0]); | |||
} | |||
}); | |||
const classChange = () => { | |||
let obj = classoptions.value.find(e => e.value == classValue.value); | |||
emit("getClassRoomInfo", obj); | |||
}; | |||
// 宿舍楼选择 | |||
const buildId = ref(""); | |||
const dormitoryOptions = ref([]); | |||
screenApi.getNoPageList().then(res => { | |||
if (res.code == 200) { | |||
dormitoryOptions.value = res.data.map(e => { | |||
e.label = e.name; | |||
e.value = e.id; | |||
return e; | |||
}); | |||
buildId.value = dormitoryOptions.value[0].value; | |||
emit("getStuReturnInfo", dormitoryOptions.value[0]); | |||
} | |||
}); | |||
const buildIdChange = () => { | |||
let obj = dormitoryOptions.value.find(e => e.value == buildId.value); | |||
emit("getStuReturnInfo", obj); | |||
}; | |||
// 当前时间 | |||
const currentDate = ref(""); | |||
onMounted(() => { | |||
// 时间 | |||
setInterval(() => { | |||
const now = new Date(); | |||
let hours = now.getHours(); | |||
let minutes = now.getMinutes(); | |||
let seconds = now.getSeconds(); | |||
// 添加零以确保时、分、秒是两位数 | |||
let hour = hours < 10 ? "0" + hours : hours; | |||
let minute = minutes < 10 ? "0" + minutes : minutes; | |||
let second = seconds < 10 ? "0" + seconds : seconds; | |||
currentDate.value = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()} ${hour}:${minute}:${second}`; | |||
}, 1000); | |||
// 边框 | |||
setBoder(); | |||
setBoder1(); | |||
}); | |||
onBeforeMount(() => { | |||
handleResize(); | |||
window.addEventListener("resize", handleResize); | |||
}); | |||
onBeforeUnmount(() => { | |||
window.removeEventListener("resize", handleResize); | |||
}); | |||
// 边框添加 | |||
const setBoder = () => { | |||
let borderEle = document.querySelectorAll(".myborder"); | |||
borderEle.forEach(e => { | |||
e.style.cssText = "position:relative;border: 1px solid #2e84e5;background: linear-gradient(179deg, #081340 -54.28%, #111e55 124.4%);"; | |||
let borderImgs = document.createElement("div"); | |||
borderImgs.style.cssText = `position: absolute;top: -3px;left: -3px;width: calc(100% + 6px);;height: calc(100% + 6px);;display: flex;justify-content: space-between;flex-flow: column;pointer-events:none`; | |||
borderImgs.innerHTML = `<div style="width: 100%;display: flex;justify-content: space-between;"> | |||
<img src="/static/screen/img/topleft.png" alt="" /> | |||
<img src="/static/screen/img/topright.png" alt="" /></div> | |||
<div style="width: 100%;display: flex;justify-content: space-between;"><img src="/static/screen/img/bottomleft.png" alt="" /> | |||
<img src="/static/screen/img/bottomright.png" alt="" /></div>`; | |||
e.append(borderImgs); | |||
}); | |||
}; | |||
// 边框添加1 | |||
const setBoder1 = () => { | |||
let borderEle = document.querySelectorAll(".myborder1"); | |||
borderEle.forEach(e => { | |||
if (e.classList.contains("hasSetBoder")) return; | |||
e.classList.add("hasSetBoder"); | |||
e.style.cssText = "position:relative;border: 1px solid #2e84e5;background: linear-gradient(179deg, #081340 -54.28%, #111e55 124.4%);"; | |||
let borderImgs = document.createElement("div"); | |||
borderImgs.style.cssText = `position: absolute;top: -2px;left: -2px;width: calc(100% + 4px);;height: calc(100% + 4px);;display: flex;justify-content: space-between;flex-flow: column;pointer-events:none`; | |||
borderImgs.innerHTML = `<div style="width: 100%;display: flex;justify-content: space-between;"> | |||
<img src="/static/screen/img/topleft1.png" alt="" style="width:12.7px;height:12.7px"/> | |||
<img src="/static/screen/img/topright1.png" alt="" style="width:12.7px;height:12.7px"/></div> | |||
<div style="width: 100%;display: flex;justify-content: space-between;"><img src="/static/screen/img/bottomleft1.png" alt="" style="width:12.7px;height:12.7px"/> | |||
<img src="/static/screen/img/bottomright1.png" alt="" style="width:12.7px;height:12.7px"/></div>`; | |||
e.append(borderImgs); | |||
}); | |||
}; | |||
// 屏幕宽度响应 | |||
const handleResize = () => { | |||
// 设计稿: 1920 * 1080 | |||
// 目标适配: 1920 * 1080 3840 * 2160 ( 2 * 2 ) ; 7680 * 2160( 4 * 2) | |||
// 1.设计稿的尺寸 | |||
let targetX = 2560; | |||
let targetY = 1440; | |||
// let targetX = 1920; | |||
// let targetY = 1080; | |||
let targetRatio = 16 / 9; // 宽高比率 | |||
// 2.拿到当前设备(浏览器)的宽度 | |||
let currentX = document.documentElement.clientWidth || document.body.clientWidth; | |||
let currentY = document.documentElement.clientHeight || document.body.clientHeight; | |||
// 1920 * 1080 -> 3840 * 2160 | |||
// 3.计算缩放比例 | |||
let scaleRatio = currentX / targetX; // 参照宽度进行缩放 ( 默认情况 ) | |||
let currentRatio = currentX / currentY; // 宽高比率 | |||
// 超宽屏 | |||
if (currentRatio > targetRatio) { | |||
// 如果当前设备的宽高比率大于设计稿的宽高比率,那么就以高度为参照进行缩放,并且居中显示 | |||
scaleRatio = currentY / targetY; | |||
document.body.style = `width:${targetX}px; height:${targetY}px;position:fixed;transform: scale(${scaleRatio}) translateX(-50%);left:50%;transform-origin: left top;`; | |||
} else { | |||
// 4.开始缩放网页 | |||
document.body.style = `width:${targetX}px; height:${targetY}px; transform: scale(${scaleRatio});transform-origin: left top;`; | |||
} | |||
}; | |||
defineExpose({ | |||
setBoder, | |||
setBoder1, | |||
classValue, | |||
buildId | |||
}); | |||
</script> | |||
<style lang="scss" scoped> | |||
.header-select { | |||
:deep(.el-select__wrapper) { | |||
border: 1px solid #35c9f0; | |||
background: #0c4dcf; | |||
width: 100%; | |||
height: 55px; | |||
outline: none; | |||
} | |||
:deep(.el-popper) { | |||
position: absolute !important; | |||
top: 58px !important; | |||
left: 0px !important; | |||
} | |||
:deep(.el-select-dropdown__item) { | |||
color: #0c4dcf; | |||
font-size: 20px; | |||
height: 55px; | |||
line-height: 55px; | |||
} | |||
:deep(.el-select__placeholder) { | |||
background: linear-gradient(9deg, #10cefe 7.44%, #fff 79.62%); | |||
background-clip: text; | |||
-webkit-background-clip: text; | |||
-webkit-text-fill-color: transparent; | |||
font-size: 20px; | |||
} | |||
} | |||
header { | |||
position: relative; | |||
display: flex; | |||
justify-content: space-between; | |||
box-sizing: border-box; | |||
padding-left: 58px; | |||
padding-right: 53px; | |||
> * { | |||
flex: 1; | |||
} | |||
.left { | |||
padding-top: 25px; | |||
} | |||
.middle { | |||
display: flex; | |||
justify-content: center; | |||
position: relative; | |||
img { | |||
width: 997.5px; | |||
height: 108px; | |||
} | |||
h2 { | |||
position: absolute; | |||
left: 0; | |||
right: 0; | |||
top: 0; | |||
bottom: 0; | |||
margin: 0 auto; | |||
text-align: center; | |||
line-height: 108px; | |||
background: linear-gradient(90deg, #10cefe 0.82%, #10cefe 13.05%, #81e1ff 50.75%, #10cefe 98.64%); | |||
background-clip: text; | |||
-webkit-background-clip: text; | |||
-webkit-text-fill-color: transparent; | |||
font-family: "Microsoft YaHei UI"; | |||
font-size: 48px; | |||
font-style: normal; | |||
font-weight: 700; | |||
width: 444px; | |||
border-bottom: 3px solid #10cefe; | |||
} | |||
} | |||
.right { | |||
display: flex; | |||
justify-content: right; | |||
img { | |||
width: 38px; | |||
height: 48.384px; | |||
margin-top: 29px; | |||
margin-right: 30px; | |||
} | |||
.time { | |||
color: #cce3fe; | |||
margin-top: 44px; | |||
margin-right: 80px; | |||
} | |||
} | |||
} | |||
</style> | |||
<style lang="scss"> | |||
ul:not(.el-scrollbar__view) { | |||
list-style-type: none; | |||
margin: 0; | |||
padding: 0; | |||
li { | |||
margin: 0; | |||
padding: 0; | |||
} | |||
} | |||
.circle { | |||
width: 5px; | |||
height: 5px; | |||
background: #fff; | |||
border-radius: 50%; | |||
position: relative; | |||
top: 8px; | |||
} | |||
.commontitle { | |||
width: 100%; | |||
display: flex; | |||
justify-content: space-between; | |||
> .left { | |||
display: flex; | |||
padding-top: 6px; | |||
font-weight: 700; | |||
img { | |||
width: 171.859px; | |||
height: 27.139px; | |||
} | |||
span { | |||
height: 35px; | |||
color: #78dfff; | |||
font-family: "Microsoft YaHei UI"; | |||
font-size: 24px; | |||
line-height: 1; | |||
margin-right: 16px; | |||
} | |||
} | |||
} | |||
.commonTitle1 { | |||
width: 100%; | |||
display: flex; | |||
justify-content: space-between; | |||
.left { | |||
color: #fff; | |||
font-size: 18px; | |||
font-weight: 700; | |||
} | |||
.right { | |||
position: relative; | |||
bottom: 3px; | |||
color: #78dfff; | |||
font-size: 14px; | |||
span { | |||
display: inline-block; | |||
color: #78dfff; | |||
width: 40px; | |||
font-size: 20px; | |||
position: relative; | |||
top: 2px; | |||
text-align: right; | |||
} | |||
} | |||
} | |||
.fullscreen-container { | |||
font-size: 20px; | |||
height: 100%; | |||
background: #08123e; | |||
} | |||
:deep(.el-select) { | |||
border: none; | |||
} | |||
.borderImgs { | |||
position: absolute; | |||
top: 0; | |||
left: 0; | |||
width: 100%; | |||
height: 100%; | |||
display: flex; | |||
justify-content: space-between; | |||
flex-flow: column; | |||
> div { | |||
width: 100%; | |||
display: flex; | |||
justify-content: space-between; | |||
} | |||
} | |||
::-webkit-scrollbar-thumb { | |||
background-color: #3877f2; /* 滑块的背景色 */ | |||
border-radius: 10px; /* 滑块的圆角 */ | |||
// border: 2px solid #ffffff; /* 滑块边框 */ | |||
} | |||
</style> |
@@ -0,0 +1,383 @@ | |||
<template> | |||
<div style="width: 100%; height: 100%"> | |||
<ul> | |||
<li> | |||
<span class="title">学生总人数:</span> | |||
<div class="numbox"> | |||
<div v-for="(item, index) in studentPersonNum.totalNum" :key="index"> | |||
<span class="num">{{ item }}</span> | |||
</div> | |||
</div> | |||
<img src="/static/screen/img/numaside.png" alt="" /> | |||
</li> | |||
<li> | |||
<span class="title">男生人数:</span> | |||
<div class="numbox"> | |||
<div v-for="(item, index) in studentPersonNum.maleNum" :key="index"> | |||
<span class="num">{{ item }}</span> | |||
</div> | |||
</div> | |||
<img src="/static/screen/img/numaside.png" alt="" /> | |||
</li> | |||
<li> | |||
<span class="title">女生人数:</span> | |||
<div class="numbox"> | |||
<div v-for="(item, index) in studentPersonNum.femaleNum" :key="index"> | |||
<span class="num">{{ item }}</span> | |||
</div> | |||
</div> | |||
</li> | |||
</ul> | |||
<div style="display: flex; justify-content: space-around; padding: 25px 0"> | |||
<div style="width: 175px; height: 175px; position: relative; display: flex; justify-content: center; align-items: center"> | |||
<div ref="chart1" style="width: 160px; height: 160px"></div> | |||
<img src="/static/screen/img/circle.png" style="position: absolute; top: 0; left: 0; width: 175px; height: 175px" alt="" /> | |||
</div> | |||
<div style="width: 175px; height: 175px; position: relative; display: flex; justify-content: center; align-items: center"> | |||
<div ref="chart2" style="width: 160px; height: 160px"></div> | |||
<img src="/static/screen/img/circle.png" style="position: absolute; top: 0; left: 0; width: 175px; height: 175px" alt="" /> | |||
</div> | |||
</div> | |||
<div class="commontitle"> | |||
<div class="left"> | |||
<span>设备统计</span> | |||
<img src="/static/screen/img/titleIcon.png" alt="" /> | |||
</div> | |||
<div class="right"> | |||
设备总数:<span>{{ count }}</span> | |||
</div> | |||
</div> | |||
<div style="width: 100%; height: 200px"> | |||
<div ref="chart3" style="width: 100%; height: 100%"></div> | |||
</div> | |||
</div> | |||
</template> | |||
<script setup> | |||
const props = defineProps({ | |||
screenData: {} | |||
}); | |||
import * as echarts from "echarts"; | |||
let chart1 = ref(null); | |||
let chart2 = ref(null); | |||
let chart3 = ref(null); | |||
onMounted(() => { | |||
getstudentPersonNum(); | |||
getCharts1(); | |||
getCharts2(); | |||
getCharts3(); | |||
}); | |||
// 学生人数 | |||
const studentPersonNumKeyValue = { femaleNum: "女生人数", maleNum: "男生人数", totalNum: "学生总人数" }; | |||
const studentPersonNum_ = reactive({ | |||
femaleNum: 0, | |||
maleNum: 0, | |||
totalNum: 0 | |||
}); | |||
const studentPersonNum = reactive({ | |||
femaleNum: [], | |||
maleNum: [], | |||
totalNum: [] | |||
}); | |||
const getstudentPersonNum = () => { | |||
studentPersonNum_.value = props.screenData.studentPersonNum; | |||
for (const key in studentPersonNum_.value) { | |||
let item = studentPersonNum_.value[key]; | |||
let arr = item.toString().split(""); | |||
function initArr(arr) { | |||
if (arr.length < 4) { | |||
arr.unshift("0"); | |||
} | |||
if (arr.length < 4) { | |||
return initArr(arr); | |||
} | |||
return arr; | |||
} | |||
arr = initArr(arr); | |||
studentPersonNum[key] = arr; | |||
} | |||
}; | |||
const getCharts1 = () => { | |||
const chart = echarts.init(chart1.value); | |||
let arr = []; | |||
for (const key in studentPersonNum_.value) { | |||
if (key == "femaleNum") { | |||
arr[0] = { | |||
name: studentPersonNumKeyValue[key], | |||
value: studentPersonNum_.value[key] | |||
}; | |||
} | |||
if (key == "maleNum") { | |||
arr[1] = { | |||
name: studentPersonNumKeyValue[key], | |||
value: studentPersonNum_.value[key] | |||
}; | |||
} | |||
} | |||
var title = ((studentPersonNum_.value.maleNum * 100) / studentPersonNum_.value.totalNum).toFixed(0) + "%"; | |||
let option = { | |||
backgroundColor: "#021841", | |||
animation: true, | |||
title: { | |||
text: title, | |||
subtext: "男", | |||
x: "center", | |||
y: "35%", | |||
textStyle: { | |||
color: "#D5C54A", | |||
fontSize: 25, | |||
fontWeight: "600", | |||
align: "center" | |||
// "width": "200px" | |||
}, | |||
subtextStyle: { | |||
color: "#fff", | |||
fontSize: 20, | |||
fontWeight: "normal", | |||
align: "center" | |||
} | |||
}, | |||
series: [ | |||
{ | |||
type: "pie", | |||
center: ["50%", "50%"], | |||
radius: ["80%", "90%"], | |||
color: ["#154076", "#18EAFE"], | |||
labelLine: { | |||
normal: { | |||
length: 25 | |||
} | |||
}, | |||
label: { | |||
normal: { | |||
show: false | |||
} | |||
}, | |||
data: arr | |||
} | |||
] | |||
}; | |||
chart.setOption(option); | |||
window.addEventListener("resize", function () { | |||
chart.resize(); | |||
}); | |||
}; | |||
const getCharts2 = data => { | |||
const chart = echarts.init(chart2.value); | |||
let arr = []; | |||
for (const key in studentPersonNum_.value) { | |||
if (key == "maleNum") { | |||
arr[0] = { | |||
name: studentPersonNumKeyValue[key], | |||
value: studentPersonNum_.value[key] | |||
}; | |||
} | |||
if (key == "femaleNum") { | |||
arr[1] = { | |||
name: studentPersonNumKeyValue[key], | |||
value: studentPersonNum_.value[key] | |||
}; | |||
} | |||
} | |||
var title = ((studentPersonNum_.value.femaleNum * 100) / studentPersonNum_.value.totalNum).toFixed(0) + "%"; | |||
let option = { | |||
backgroundColor: "#021841", | |||
animation: true, | |||
title: { | |||
text: title, | |||
subtext: "女", | |||
x: "center", | |||
y: "35%", | |||
textStyle: { | |||
color: "#D5C54A", | |||
fontSize: 25, | |||
fontWeight: "600", | |||
align: "center" | |||
}, | |||
subtextStyle: { | |||
color: "#fff", | |||
fontSize: 20, | |||
fontWeight: "normal", | |||
align: "center" | |||
} | |||
}, | |||
series: [ | |||
{ | |||
type: "pie", | |||
center: ["50%", "50%"], | |||
radius: ["80%", "90%"], | |||
color: ["#154076", "#E74B3C"], | |||
startAngle: 270, | |||
labelLine: { | |||
normal: { | |||
length: 25 | |||
} | |||
}, | |||
label: { | |||
normal: { | |||
show: false | |||
} | |||
}, | |||
data: arr | |||
} | |||
] | |||
}; | |||
chart.setOption(option); | |||
window.addEventListener("resize", function () { | |||
chart.resize(); | |||
}); | |||
}; | |||
// 设备统计 | |||
const count = ref(0); | |||
const getCharts3 = () => { | |||
const chart = echarts.init(chart3.value); | |||
let data = props.screenData.camera; | |||
let xArr = data.map(e => e.cameraInfos.length); | |||
xArr.forEach(element => { | |||
count.value += element; | |||
}); | |||
let yArr = data.map(e => { | |||
return { | |||
value: e.name | |||
}; | |||
}); | |||
let option = { | |||
tooltip: { | |||
show: true, | |||
trigger: "item" //触发类型 | |||
}, | |||
textStyle: { | |||
color: "rgba(255, 255, 255, 0.64)" | |||
}, | |||
grid: { | |||
left: "3%", | |||
right: "6%", | |||
bottom: "3%", | |||
top: "10%", | |||
containLabel: true | |||
}, | |||
xAxis: { | |||
type: "value", | |||
name: "个", | |||
axisLabel: { | |||
show: true, | |||
fontSize: 12 | |||
}, | |||
splitLine: { | |||
show: true, | |||
lineStyle: { | |||
color: "rgba(255, 255, 255, 0.15)" | |||
} | |||
} | |||
}, | |||
yAxis: { | |||
type: "category", | |||
data: yArr, | |||
axisTick: false, | |||
axisLabel: { | |||
show: true, | |||
color: "rgba(255, 255, 255, 0.64)", | |||
fontSize: 12, | |||
overflow: "break", | |||
width: 100 | |||
} | |||
}, | |||
series: [ | |||
{ | |||
name: "设备统计", | |||
type: "bar", | |||
barWidth: "11px", | |||
itemStyle: { | |||
color: { | |||
type: "linear", | |||
colorStops: [ | |||
{ offset: 0, color: "rgba(27,99,204,0.9)" }, | |||
{ offset: 1, color: "rgba(3,223,252,0.9)" } | |||
] | |||
} | |||
}, | |||
data: xArr | |||
} | |||
] | |||
}; | |||
chart.setOption(option); | |||
window.addEventListener("resize", function () { | |||
chart.resize(); | |||
}); | |||
}; | |||
</script> | |||
<style scoped lang="scss"> | |||
ul { | |||
display: flex; | |||
justify-content: space-between; | |||
li { | |||
position: relative; | |||
.title { | |||
color: #fff; | |||
font-family: "Microsoft YaHei UI"; | |||
font-size: 20px; | |||
} | |||
.numbox { | |||
margin-top: 18px; | |||
display: flex; | |||
color: #fff; | |||
text-align: center; | |||
font-family: "Microsoft YaHei UI"; | |||
font-size: 28px; | |||
> div { | |||
background: url("/static/screen/img/numborder.png") 100% 100% no-repeat; | |||
width: 34px; | |||
height: 42px; | |||
line-height: 42px; | |||
margin-right: 5px; | |||
} | |||
> div:last-child { | |||
margin-right: 0px; | |||
} | |||
} | |||
img { | |||
width: 6px; | |||
height: 79px; | |||
position: absolute; | |||
right: -36px; | |||
top: 6px; | |||
} | |||
} | |||
li:nth-child(2) { | |||
.title { | |||
color: #00fff4; | |||
} | |||
.numbox { | |||
> div { | |||
background: url("/static/screen/img/numborder1.png") 100% 100% no-repeat; | |||
} | |||
} | |||
} | |||
li:nth-child(3) { | |||
.title { | |||
color: #fde0b9; | |||
} | |||
.numbox { | |||
> div { | |||
background: url("/static/screen/img/numborder2.png") 100% 100% no-repeat; | |||
} | |||
} | |||
} | |||
} | |||
.commontitle .right { | |||
color: #79bde5; | |||
font-size: 16px; | |||
padding-top: 8px; | |||
span { | |||
font-size: 18px; | |||
font-weight: 700; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,234 @@ | |||
<template> | |||
<div style="width: 100%; height: 100%"> | |||
<div class="commontitle"> | |||
<div class="left"> | |||
<span>今日告警信息</span> | |||
<img src="/static/screen/img/titleIcon.png" alt="" /> | |||
</div> | |||
<div class="right"> | |||
<el-select | |||
style="width: 160px" | |||
class="header-select nobg" | |||
v-model="place1" | |||
:teleported="false" | |||
@change="placeChange" | |||
placeholder="场景选择" | |||
clearable | |||
> | |||
<el-option v-for="item in placeOptions" :value="item.value" :label="item.label" :key="item.value"></el-option> | |||
</el-select> | |||
</div> | |||
</div> | |||
<div class="todayBoticeTotal myborder1"> | |||
<div | |||
style=" | |||
width: 40px; | |||
height: 40px; | |||
border: 1px solid #ab2626; | |||
border-radius: 50%; | |||
text-align: center; | |||
position: relative; | |||
bottom: 3px; | |||
margin-right: 14px; | |||
" | |||
> | |||
<img src="/static/screen/img/redLight.png" alt="" style="width: 22px; margin-top: 5px" /> | |||
</div> | |||
<div> | |||
今日<span>7</span>个场景预警总计:<span class="total">{{ count }}</span> 起 | |||
</div> | |||
</div> | |||
<div style="width: 100%; height: calc(100% - 136px); overflow: auto; margin-top: 29px"> | |||
<ul> | |||
<template v-for="item in warnList" :key="item.id"> | |||
<li> | |||
<div class="left"> | |||
<el-image :src="item.snapshotUrl" fit="cover" :preview-src-list="[item.snapshotUrl]"></el-image> | |||
<!-- <img :src="item.snapshotUrl" alt="" /> --> | |||
</div> | |||
<div class="right"> | |||
<div class="time">{{ item.tick }}</div> | |||
<div v-if="item.warnHand == 0" class="status" style="background-color: #9a112c"> | |||
<div class="circle"></div> | |||
<div>未处理</div> | |||
</div> | |||
<div class="title">{{ item.alarmTypeDesc }}</div> | |||
<div class="des"> | |||
<div>所属位置:{{ item.cameraName }}</div> | |||
<div>所属场景:{{ item.cameraGroup }}</div> | |||
</div> | |||
</div> | |||
</li> | |||
</template> | |||
<template v-if="!warnList.length"> | |||
<div style="color: #10cefe; text-align: center; margin-top: 18px; font-size: 14px">暂无数据</div> | |||
</template> | |||
</ul> | |||
</div> | |||
</div> | |||
</template> | |||
<script setup> | |||
const props = defineProps({ | |||
screenData: {} | |||
}); | |||
// 场景选择 | |||
const placeOptions = ref([]); | |||
placeOptions.value = props.screenData.camera.map(e => { | |||
return { | |||
value: e.id, | |||
label: e.name | |||
}; | |||
}); | |||
const place1 = ref(""); | |||
const count = ref(0); | |||
// 列表 | |||
const warnList = ref([]); | |||
const warnList_copy = ref([]); | |||
const getwarnList = () => { | |||
warnList.value = props.screenData.alarmStatisti.warnList; | |||
warnList_copy.value = JSON.parse(JSON.stringify(warnList.value)); | |||
count.value = warnList.value.length; | |||
}; | |||
const placeChange = () => { | |||
let obj = placeOptions.value.find(e => e.value == place1.value); | |||
if (obj) { | |||
warnList.value = warnList_copy.value.filter(e => e.cameraGroup == obj.label); | |||
} else { | |||
warnList.value = warnList_copy.value; | |||
} | |||
}; | |||
onMounted(() => { | |||
getwarnList(); | |||
}); | |||
</script> | |||
<style scoped lang="scss"> | |||
.todayBoticeTotal { | |||
width: 588px; | |||
height: 59px; | |||
background: #0f1e5e; | |||
color: #78dfff; | |||
font-family: "Microsoft YaHei UI"; | |||
font-size: 16px; | |||
margin-top: 22px; | |||
padding-left: 25px; | |||
padding-top: 13px; | |||
box-sizing: border-box; | |||
display: flex; | |||
align-content: center; | |||
span.total { | |||
color: #ff483a; | |||
font-family: "Microsoft YaHei UI"; | |||
font-size: 22px; | |||
position: relative; | |||
top: 3px; | |||
} | |||
} | |||
ul:not(.el-scrollbar__view) { | |||
li:first-child { | |||
margin-top: 0px; | |||
} | |||
li { | |||
display: flex; | |||
margin-top: 18px; | |||
background: #182665; | |||
padding-right: 20px; | |||
box-sizing: border-box; | |||
.left { | |||
width: 195px; | |||
height: 114px; | |||
object-fit: cover; | |||
.el-image { | |||
width: 100%; | |||
height: 100%; | |||
} | |||
} | |||
.right { | |||
flex: 1; | |||
padding-left: 20px; | |||
position: relative; | |||
.status { | |||
width: 82px; | |||
height: 22px; | |||
color: #fff; | |||
font-size: 12px; | |||
display: flex; | |||
justify-content: space-between; | |||
line-height: 20px; | |||
align-content: center; | |||
padding-left: 9px; | |||
padding-right: 16px; | |||
box-sizing: border-box; | |||
position: absolute; | |||
top: 16px; | |||
right: 0px; | |||
} | |||
.time { | |||
color: #78dfff; | |||
font-size: 14px; | |||
margin-top: 13px; | |||
} | |||
.title { | |||
color: #fff; | |||
font-size: 16px; | |||
margin-top: 13px; | |||
} | |||
.des { | |||
display: flex; | |||
justify-content: space-between; | |||
color: #78dfff; | |||
font-size: 12px; | |||
margin-top: 16px; | |||
} | |||
} | |||
} | |||
} | |||
.header-select { | |||
:deep(.el-select__wrapper) { | |||
border: 1px solid #35c9f0; | |||
background: unset; | |||
width: 100%; | |||
height: 40px; | |||
outline: none; | |||
} | |||
// :deep(.el-popper) { | |||
// position: absolute !important; | |||
// top: 58px !important; | |||
// left: 0px !important; | |||
// } | |||
:deep(.el-select-dropdown__item) { | |||
color: #0c4dcf; | |||
font-size: 20px; | |||
height: 55px; | |||
line-height: 55px; | |||
} | |||
:deep(.el-select__placeholder) { | |||
background: linear-gradient(9deg, #10cefe 7.44%, #fff 79.62%); | |||
background-clip: text; | |||
-webkit-background-clip: text; | |||
-webkit-text-fill-color: transparent; | |||
font-size: 16px; | |||
} | |||
} | |||
// .header-select.nobg { | |||
// :deep(.el-select__wrapper) { | |||
// background-color: unset; | |||
// font-size: 16px; | |||
// width: 100%; | |||
// height: 40px; | |||
// } | |||
// :deep(.el-select-dropdown__item) { | |||
// color: #0c4dcf; | |||
// max-width: 220px; | |||
// font-size: 16px; | |||
// line-height: 40px; | |||
// } | |||
// :deep(.el-select__placeholder) { | |||
// background: linear-gradient(9deg, #10cefe 7.44%, #fff 79.62%); | |||
// background-clip: text; | |||
// -webkit-background-clip: text; | |||
// -webkit-text-fill-color: transparent; | |||
// font-size: 16px; | |||
// } | |||
// } | |||
</style> |
@@ -0,0 +1,230 @@ | |||
<template> | |||
<div style="width: 100%; height: 100%"> | |||
<div class="commontitle"> | |||
<div class="left"> | |||
<span>今日场景告警次数</span> | |||
<img src="/static/screen/img/titleIcon.png" alt="" /> | |||
</div> | |||
<div class="right"></div> | |||
</div> | |||
<div style="display: flex"> | |||
<div class="safe myborder1"> | |||
<div class="commonTitle1"> | |||
<span class="left">校园安全</span> | |||
<span class="right" | |||
>告警次数:<span class="num">{{ typeStatistiCount }}</span></span | |||
> | |||
</div> | |||
<div style="height: 410px; overflow-y: auto; margin-top: 18px"> | |||
<ul> | |||
<template v-for="(item, index) in typeStatisti" :key="index"> | |||
<li> | |||
<div class="title"> | |||
<span class="top">Top{{ index + 1 }}</span> | |||
<span class="title_">{{ item.name }}</span> | |||
</div> | |||
<div class="progressBox"> | |||
<el-progress | |||
:percentage="item.value" | |||
:show-text="false" | |||
:stroke-width="8" | |||
style="width: 100%" | |||
:color=" | |||
index == 0 | |||
? 'linear-gradient(to right,rgba(226,74,59,0.01),rgba(226,74,59,1))' | |||
: index == 1 | |||
? 'linear-gradient(to right,rgba(246,152,14,0.01),rgba(246,152,14,1))' | |||
: 'linear-gradient(to right,rgba(55,116,237,0.01),rgba(55,116,237,1))' | |||
" | |||
/> | |||
</div> | |||
<div class="num">{{ item.value }}次</div> | |||
</li> | |||
</template> | |||
</ul> | |||
</div> | |||
</div> | |||
<div class="cate"> | |||
<ul> | |||
<template v-for="(item, index) in groupStatisti" :key="index"> | |||
<li class="myborder1"> | |||
<div class="commonTitle1"> | |||
<span class="left">{{ item.name }}</span> | |||
<span class="right" | |||
>告警次数:<span class="num">{{ item.value }}</span></span | |||
> | |||
</div> | |||
<div style="height: 146px; overflow: auto; margin-top: 24px"> | |||
<ul> | |||
<template v-for="(item1, index1) in item.subset" :key="index1"> | |||
<li> | |||
<div>{{ item1.name }}:</div> | |||
<div>{{ item1.value }}次</div> | |||
</li> | |||
</template> | |||
</ul> | |||
</div> | |||
</li> | |||
</template> | |||
</ul> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script setup> | |||
const props = defineProps({ | |||
screenData: {} | |||
}); | |||
// 校园安全 | |||
const typeStatisti = ref([]); | |||
const typeStatistiCount = ref(0); | |||
const getData = () => { | |||
typeStatisti.value = props.screenData.alarmStatisti.typeStatisti.sort((a, b) => { | |||
return b.value - a.value; | |||
}); | |||
typeStatisti.value.forEach(e => { | |||
typeStatistiCount.value += e.value; | |||
}); | |||
}; | |||
onMounted(() => { | |||
getData(); | |||
getRightData(); | |||
}); | |||
// 右边模块 | |||
const groupStatisti = ref([]); | |||
const getRightData = () => { | |||
groupStatisti.value = props.screenData.alarmStatisti.groupStatisti; | |||
nextTick(() => { | |||
setBoder1(); | |||
}); | |||
}; | |||
// 边框添加1 | |||
const setBoder1 = () => { | |||
let borderEle = document.querySelectorAll(".myborder1"); | |||
borderEle.forEach(e => { | |||
if (e.classList.contains("hasSetBoder")) return; | |||
e.classList.add("hasSetBoder"); | |||
e.style.cssText = "position:relative;border: 1px solid #2e84e5;background: linear-gradient(179deg, #081340 -54.28%, #111e55 124.4%);"; | |||
let borderImgs = document.createElement("div"); | |||
borderImgs.style.cssText = `position: absolute;top: -2px;left: -2px;width: calc(100% + 4px);;height: calc(100% + 4px);;display: flex;justify-content: space-between;flex-flow: column;pointer-events:none`; | |||
borderImgs.innerHTML = `<div style="width: 100%;display: flex;justify-content: space-between;"> | |||
<img src="/static/screen/img/topleft1.png" alt="" style="width:12.7px;height:12.7px"/> | |||
<img src="/static/screen/img/topright1.png" alt="" style="width:12.7px;height:12.7px"/></div> | |||
<div style="width: 100%;display: flex;justify-content: space-between;"><img src="/static/screen/img/bottomleft1.png" alt="" style="width:12.7px;height:12.7px"/> | |||
<img src="/static/screen/img/bottomright1.png" alt="" style="width:12.7px;height:12.7px"/></div>`; | |||
e.append(borderImgs); | |||
}); | |||
}; | |||
</script> | |||
<style scoped lang="scss"> | |||
.commontitle { | |||
margin-bottom: 22px; | |||
} | |||
.safe { | |||
padding: 24px 29px 0px 29px; | |||
box-sizing: border-box; | |||
width: 456px; | |||
height: 494px; | |||
ul { | |||
padding-right: 10px; | |||
li { | |||
display: flex; | |||
margin-top: 18.6px; | |||
&:first-child { | |||
margin-top: 0px; | |||
} | |||
.title { | |||
display: flex; | |||
color: #e3e9f3; | |||
font-size: 12px; | |||
.top { | |||
display: inline-block; | |||
width: 60px; | |||
height: 20px; | |||
border-radius: 10px; | |||
background: linear-gradient(to left, rgba(55, 116, 237, 0.01), rgba(55, 116, 237, 1)); | |||
box-sizing: border-box; | |||
padding-left: 10px; | |||
} | |||
.title_ { | |||
display: block; | |||
width: 76px; | |||
position: relative; | |||
right: 10px; | |||
} | |||
} | |||
.progressBox { | |||
width: 190px; | |||
padding-top: 6px; | |||
} | |||
.num { | |||
color: #fff; | |||
text-align: right; | |||
font-size: 14px; | |||
display: block; | |||
width: 42px; | |||
} | |||
&:nth-child(1) { | |||
.title { | |||
.top { | |||
background: linear-gradient(to right, rgba(226, 74, 59, 1), rgba(226, 74, 59, 0.01)); | |||
} | |||
} | |||
} | |||
&:nth-child(2) { | |||
.title { | |||
.top { | |||
background: linear-gradient(to left, rgba(246, 152, 14, 0.01), rgba(246, 152, 14, 1)); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
.cate { | |||
width: calc(100% - 200px); | |||
margin-left: 22px; | |||
box-sizing: border-box; | |||
height: 494px; | |||
overflow-y: auto; | |||
> ul { | |||
display: flex; | |||
flex-wrap: wrap; | |||
justify-content: space-between; | |||
padding-right: 10px; | |||
> li { | |||
margin-right: 16px; | |||
width: 383.228px; | |||
height: 235.65px; | |||
margin-bottom: 22px; | |||
padding: 22px; | |||
box-sizing: border-box; | |||
} | |||
> li:nth-child(3n) { | |||
margin-right: 0px; | |||
} | |||
ul { | |||
display: flex; | |||
flex-wrap: wrap; | |||
justify-content: space-between; | |||
li { | |||
display: flex; | |||
justify-content: space-between; | |||
color: #fff; | |||
font-size: 12px; | |||
width: 48%; | |||
height: 40.92px; | |||
box-shadow: 0px 0px 10px 0px #3877f2 inset; | |||
background: #0e298b; | |||
box-sizing: border-box; | |||
padding: 0 8px; | |||
line-height: 40.92px; | |||
margin-bottom: 7.7px; | |||
} | |||
} | |||
} | |||
} | |||
:deep(.el-progress-bar__outer) { | |||
background: #404c80; | |||
} | |||
</style> |
@@ -0,0 +1,226 @@ | |||
<template> | |||
<div class="myvideo" style="width: 100%; height: 100%"> | |||
<div class="left"> | |||
<div class="commontitle"> | |||
<div class="left"> | |||
<span>实时监控</span> | |||
<img src="/static/screen/img/titleIcon.png" alt="" /> | |||
</div> | |||
<div class="right"> | |||
<el-input v-model="searchText" @keyup.enter="searchHandle" placeholder="搜索区域/设备"> | |||
<template #suffix> | |||
<el-icon style="cursor: pointer" class="el-input__icon" @click="searchHandle" color="#2E84E5"><search /></el-icon> | |||
</template> | |||
</el-input> | |||
</div> | |||
</div> | |||
<!-- --> | |||
<div style="width: 794px; height: 487px; padding-top: 26px"> | |||
<!-- <img src="/static/screen/testimg/2.png" alt="" style="width: 100%; height: 100%" /> --> | |||
<iframe id="rtsIframe" style="width: 100%; height: 100%" :src="rtsUrl" frameborder="0"></iframe> | |||
</div> | |||
</div> | |||
<div class="right"> | |||
<div class="cateBox"> | |||
<div @click="cate = 1" :class="{ active: cate == 1 }"> | |||
<div class="title">区域选择</div> | |||
<CaretBottom v-if="cate == 1" color="#fff" width="11px" /> | |||
</div> | |||
<div @click="cate = 2" :class="{ active: cate == 2 }"> | |||
<div class="title">设备选择</div> | |||
<CaretBottom v-if="cate == 2" color="#fff" width="11px" /> | |||
</div> | |||
</div> | |||
<ul v-if="cate == 1"> | |||
<li | |||
v-for="(item, index) in areaList" | |||
:class="{ active: item.id == areaId }" | |||
:key="index" | |||
class="wrap1" | |||
@click="areaItemClick(item)" | |||
:title="item.name" | |||
> | |||
{{ item.name }} | |||
</li> | |||
<template v-if="!areaList.length"> | |||
<li class="wrap1" style="border: none">暂无区域</li> | |||
</template> | |||
</ul> | |||
<ul v-if="cate == 2"> | |||
<li | |||
v-for="(item, index) in deviceList" | |||
:class="{ active: item.sensorId == sensorId }" | |||
:key="index" | |||
class="wrap1" | |||
@click="itemClick(item)" | |||
:title="item.sensorName" | |||
> | |||
{{ item.sensorName }} | |||
</li> | |||
<template v-if="!deviceList.length"> | |||
<li class="wrap1" style="border: none">暂无设备</li> | |||
</template> | |||
</ul> | |||
</div> | |||
</div> | |||
</template> | |||
<script setup> | |||
import { screenApi } from "@/api"; | |||
const props = defineProps({ | |||
screenData: {} | |||
}); | |||
const searchText = ref(""); | |||
const cate = ref(1); | |||
const resInfo = ref([]); | |||
const allDevice = ref([]); | |||
const areaList = ref([]); | |||
const deviceList = ref([]); | |||
const deviceList_copy = ref([]); | |||
const areaId = ref(""); | |||
const sensorId = ref(""); | |||
// 获取区域设备信息 | |||
const getInfo = () => { | |||
resInfo.value = props.screenData.camera; | |||
areaList.value = JSON.parse(JSON.stringify(resInfo.value)); | |||
props.screenData.camera.forEach(e => { | |||
allDevice.value = allDevice.value.concat(e.cameraInfos); | |||
}); | |||
let obj = areaList.value.find(e => e.cameraInfos.length); | |||
areaId.value = obj.id; | |||
deviceList.value = obj.cameraInfos; | |||
deviceList_copy.value = JSON.parse(JSON.stringify(obj.cameraInfos)); | |||
cate.value = 2; | |||
itemClick(deviceList.value[0]); | |||
}; | |||
// 搜索 | |||
const searchHandle = () => { | |||
if (cate.value == 1) { | |||
areaList.value = resInfo.value.filter(e => { | |||
return e.name.includes(searchText.value); | |||
}); | |||
} | |||
if (cate.value == 2) { | |||
deviceList.value = deviceList_copy.value.filter(e => { | |||
return e.sensorName.includes(searchText.value); | |||
}); | |||
} | |||
}; | |||
// 点击某区域 | |||
const areaItemClick = item => { | |||
areaId.value = item.id; | |||
deviceList.value = item.cameraInfos; | |||
deviceList_copy.value = JSON.parse(JSON.stringify(item.cameraInfos)); | |||
}; | |||
// 点击某设备 | |||
const itemClick = item => { | |||
if (sensorId.value == item.sensorId) return; | |||
if (streamId.value) stopUrl(); | |||
let rtsUrl_ = "/static/rtsPlayer.html?height=487px&rtsUrl="; | |||
sensorId.value = item.sensorId; | |||
screenApi.detail({ sensorId: item.sensorId }).then(res => { | |||
if (res.code == 200) { | |||
if (res.data.rtsPullStreamUrls[0]) { | |||
rtsUrl.value = rtsUrl_ + res.data.rtsPullStreamUrls[0].url; | |||
} | |||
streamId.value = res.data.streamId; | |||
videoToken.value = res.data.videoToken; | |||
} | |||
}); | |||
}; | |||
// 展示视频 | |||
const streamId = ref(""); | |||
const videoToken = ref(""); | |||
const rtsUrl = ref(""); | |||
const stopUrl = () => { | |||
if (!streamId.value) return; | |||
screenApi.stopUrl({ | |||
sensorId: sensorId.value, | |||
streamId: streamId.value, | |||
videoToken: videoToken.value | |||
}); | |||
}; | |||
onMounted(() => { | |||
getInfo(); | |||
}); | |||
onUnmounted(() => { | |||
stopUrl(); | |||
}); | |||
</script> | |||
<style lang="scss" scoped> | |||
.myvideo { | |||
display: flex; | |||
.left { | |||
flex: 1; | |||
} | |||
.right { | |||
width: 186px; | |||
padding-left: 14px; | |||
.cateBox { | |||
display: flex; | |||
color: #79bde5; | |||
font-size: 16px; | |||
height: 50px; | |||
padding-top: 8px; | |||
> div { | |||
color: #79bde5; | |||
font-size: 16px; | |||
flex: 1; | |||
text-align: center; | |||
cursor: pointer; | |||
&.active { | |||
background: linear-gradient(9deg, #10cefe 7.44%, #fff 79.62%); | |||
background-clip: text; | |||
-webkit-background-clip: text; | |||
-webkit-text-fill-color: transparent; | |||
} | |||
} | |||
& > div:first-child > .title { | |||
display: block; | |||
border-right: 1px solid #2e84e5; | |||
} | |||
} | |||
ul { | |||
li { | |||
height: 47px; | |||
border: 1px solid #35c9f0; | |||
background: #0c1749; | |||
margin-top: 8px; | |||
box-sizing: border-box; | |||
color: #10cefe; | |||
font-size: 14px; | |||
line-height: 47px; | |||
text-align: center; | |||
cursor: pointer; | |||
padding: 0 12px; | |||
box-sizing: border-box; | |||
&.active { | |||
background: #0c4dcf; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
:deep(.el-input__wrapper) { | |||
border-radius: 17.5px; | |||
border: 1px solid #2e84e5; | |||
background: #112060; | |||
width: 260px; | |||
height: 35px; | |||
} | |||
:deep(.el-input__inner) { | |||
color: #fff; /* 将文字颜色改为红色 */ | |||
font-size: 12px; | |||
} | |||
:deep(input::-webkit-input-placeholder) { | |||
-webkit-text-fill-color: #2e84e5; | |||
font-size: 12px; | |||
} | |||
/* 文字溢出 */ | |||
.wrap1 { | |||
display: block; | |||
text-overflow: ellipsis; | |||
overflow: hidden; | |||
white-space: nowrap; | |||
} | |||
</style> |
@@ -0,0 +1,95 @@ | |||
<template> | |||
<div class="fullscreen-container"> | |||
<myHeader ref="myHeaderRef" place="0"></myHeader> | |||
<div style="position: absolute; width: 100%; display: flex; justify-content: space-between; top: 102px"> | |||
<img style="width: 846px" src="/static/screen/img/header-aside-left.png" alt="" /> | |||
<img style="width: 845px" src="/static/screen/img/header-aside-right.png" alt="" /> | |||
</div> | |||
<main v-if="ready"> | |||
<!-- 页面左边模块 --> | |||
<div> | |||
<div class="main-left"> | |||
<div style="display: flex"> | |||
<div class="peapleNum myborder"> | |||
<peopeleNum :screenData="screenData"></peopeleNum> | |||
</div> | |||
<div class="videoBox myborder"> | |||
<myVideo :screenData="screenData"></myVideo> | |||
</div> | |||
</div> | |||
<div class="todayNotice myborder"> | |||
<todayNotice :screenData="screenData"></todayNotice> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 页面右边模块 --> | |||
<div> | |||
<div class="main-right myborder"> | |||
<todayInfo :screenData="screenData"></todayInfo> | |||
</div> | |||
</div> | |||
</main> | |||
</div> | |||
</template> | |||
<script setup> | |||
import myHeader from "./component/header.vue"; | |||
import peopeleNum from "./component/index/peopeleNum.vue"; | |||
import myVideo from "./component/index/video.vue"; | |||
import todayNotice from "./component/index/todayNotice.vue"; | |||
import todayInfo from "./component/index/todayInfo.vue"; | |||
import { screenApi } from "@/api"; | |||
const screenData = ref({}); | |||
const ready = ref(false); | |||
const myHeaderRef = ref(null); | |||
onMounted(() => { | |||
ready.value = false; | |||
screenApi.getHomeData().then(res => { | |||
if (res.code == 200) { | |||
screenData.value = res.data; | |||
console.log(res.data); | |||
ready.value = true; | |||
nextTick(() => { | |||
myHeaderRef.value.setBoder(); | |||
}); | |||
} | |||
}); | |||
}); | |||
</script> | |||
<style lang="scss" scoped> | |||
main { | |||
padding: 41px 53px 0px 58px; | |||
display: flex; | |||
justify-content: space-between; | |||
.main-left { | |||
.peapleNum { | |||
padding: 28px 36px; | |||
box-sizing: border-box; | |||
display: inline-block; | |||
width: 661px; | |||
height: 608.896px; | |||
margin-right: 26px; | |||
} | |||
.videoBox { | |||
padding: 32px 36px; | |||
box-sizing: border-box; | |||
display: inline-block; | |||
width: 1070px; | |||
height: 609px; | |||
} | |||
.todayNotice { | |||
padding: 32px 45px; | |||
box-sizing: border-box; | |||
display: inline-block; | |||
width: 1760px; | |||
height: 617px; | |||
margin-top: 24px; | |||
} | |||
} | |||
.main-right { | |||
width: 661px; | |||
height: 1249px; | |||
padding: 36px; | |||
box-sizing: border-box; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,355 @@ | |||
<template> | |||
<div class="fullscreen-container"> | |||
<myHeader place="2" @getStuReturnInfo="getStuReturnInfo"></myHeader> | |||
<div v-if="ready" style="position: absolute; width: 100%; display: flex; justify-content: space-between; top: 102px"> | |||
<img style="width: 846px" src="/static/screen/img/header-aside-left.png" alt="" /> | |||
<img style="width: 845px" src="/static/screen/img/header-aside-right.png" alt="" /> | |||
</div> | |||
<main> | |||
<div class="numsBox"> | |||
<ul> | |||
<li> | |||
<div class="title">寝室总人数</div> | |||
<div class="numList"> | |||
<div v-for="(item, index) in studentPersonNum.totalNum" :key="index">{{ item }}</div> | |||
</div> | |||
</li> | |||
<li> | |||
<div class="title">在寝人数</div> | |||
<div class="numList"> | |||
<div v-for="(item, index) in studentPersonNum.inNum" :key="index">{{ item }}</div> | |||
</div> | |||
</li> | |||
<li> | |||
<div class="title">不在寝人数</div> | |||
<div class="numList"> | |||
<div v-for="(item, index) in studentPersonNum.noInNum" :key="index">{{ item }}</div> | |||
</div> | |||
</li> | |||
</ul> | |||
</div> | |||
<div class="main"> | |||
<div class="left myborder1"> | |||
<div class="timeslot"> | |||
当前考勤时段:{{ screenData.returnTime }} | |||
(入寝) | |||
</div> | |||
<div style="height: 880px; overflow: auto; margin-top: 39px"> | |||
<ul> | |||
<li v-for="(item, index) in chamberList" :key="index" :data-status="item.status"> | |||
<div class="title">{{ item.name }}</div> | |||
<div class="des"> | |||
<template v-if="item.personCount == 0"> 未安排入住 </template> | |||
<template v-else> {{ item.dormitoryCount }}/{{ item.personCount }} </template> | |||
</div> | |||
<div class="sex">{{ item.gender ? "男生" : "女生" }}宿舍</div> | |||
</li> | |||
</ul> | |||
</div> | |||
</div> | |||
<div class="right myborder1"> | |||
<div class="imgBox"> | |||
<!-- <img src="/static/screen/testimg/8.png" alt="" /> --> | |||
<iframe :src="rtsUrl" frameborder="0" style="width: 100%; height: 100%"></iframe> | |||
</div> | |||
<el-table | |||
:data="tableData" | |||
style="width: 100%" | |||
:row-class-name=" | |||
() => { | |||
return 'tableRowClassName'; | |||
} | |||
" | |||
height="590px" | |||
> | |||
<el-table-column type="index" width="50" /> | |||
<el-table-column prop="personName" label="姓名" align="center"> </el-table-column> | |||
<el-table-column prop="dormitName" label="所在宿舍" align="center"> </el-table-column> | |||
<el-table-column prop="gender" label="宿舍性别" align="center" :formatter="genderFormatter"> </el-table-column> | |||
<el-table-column prop="$status" label="归寝状态" align="center"> </el-table-column> | |||
</el-table> | |||
</div> | |||
</div> | |||
</main> | |||
</div> | |||
</template> | |||
<script setup> | |||
import myHeader from "./component/header.vue"; | |||
import { screenApi } from "@/api"; | |||
const screenData = ref({}); | |||
const ready = ref(false); | |||
const chamberList = ref([]); | |||
const tableData = ref([]); | |||
const buildInfo = ref({}); | |||
const getStuReturnInfo = obj => { | |||
buildInfo.value = obj; | |||
// 归寝人数信息 | |||
ready.value = false; | |||
screenApi.getStudentReturnBed({ buildId: obj.value }).then(res => { | |||
if (res.code == 200) { | |||
res.data.attendList = res.data.attendList.map(e => { | |||
e.$status = e.cameraId == buildInfo.value.outCameraId ? "未归寝" : e.cameraId == buildInfo.value.insCameraId ? "归寝" : ""; | |||
return e; | |||
}); | |||
screenData.value = res.data; | |||
tableData.value = screenData.value.attendList; | |||
console.log(res.data); | |||
ready.value = true; | |||
getstudentPersonNum(); | |||
} | |||
}); | |||
// 监控 | |||
showRts(buildInfo.value.insCameraId); | |||
}; | |||
// 性别 | |||
const genderOptions = ref([ | |||
{ | |||
label: "未知", | |||
value: "GENDER_UNKNOWN" | |||
}, | |||
{ | |||
label: "男", | |||
value: "GENDER_MALE" | |||
}, | |||
{ | |||
label: "女", | |||
value: "GENDER_FEMALE" | |||
} | |||
]); | |||
const genderFormatter = row => { | |||
let obj = genderOptions.value.find(e => e.value == row.gender); | |||
return obj ? obj.label : ""; | |||
}; | |||
// 监控 | |||
const streamId = ref(""); | |||
const videoToken = ref(""); | |||
const rtsUrl = ref(""); | |||
const sensorId = ref(""); | |||
const showRts = sensorId_ => { | |||
if (sensorId_) { | |||
if (sensorId.value == sensorId_) return; | |||
if (streamId.value) stopUrl(); | |||
let rtsUrl_ = "/static/rtsPlayer.html?height=446px&rtsUrl="; | |||
sensorId.value = sensorId_; | |||
screenApi.detail({ sensorId: sensorId_ }).then(res => { | |||
if (res.code == 200) { | |||
if (res.data.rtsPullStreamUrls[0]) { | |||
rtsUrl.value = rtsUrl_ + res.data.rtsPullStreamUrls[0].url; | |||
} | |||
streamId.value = res.data.streamId; | |||
videoToken.value = res.data.videoToken; | |||
} | |||
}); | |||
} | |||
}; | |||
const stopUrl = () => { | |||
if (!streamId.value) return; | |||
screenApi.stopUrl({ | |||
sensorId: sensorId.value, | |||
streamId: streamId.value, | |||
videoToken: videoToken.value | |||
}); | |||
}; | |||
onUnmounted(() => { | |||
stopUrl(); | |||
}); | |||
// 归寝信息 | |||
const studentPersonNum_ = reactive({ | |||
inNum: 0, | |||
noInNum: 0, | |||
totalNum: 0 | |||
}); | |||
const studentPersonNum = reactive({ | |||
inNum: [], | |||
noInNum: [], | |||
totalNum: [] | |||
}); | |||
// 0全部归寝 1部分归寝 2全未归寝 3未安排入住 | |||
const getstudentPersonNum = () => { | |||
studentPersonNum_.value = screenData.value.building; | |||
chamberList.value = screenData.value.dormitoryList.map((e, index) => { | |||
e.status = e.personCount == 0 ? 3 : e.dormitoryCount == 0 ? 2 : e.dormitoryCount < e.personCount ? 1 : 0; | |||
return e; | |||
}); | |||
for (const key in studentPersonNum_.value) { | |||
let item = studentPersonNum_.value[key]; | |||
let arr = item.toString().split(""); | |||
function initArr(arr) { | |||
if (arr.length < 4) { | |||
arr.unshift("0"); | |||
} | |||
if (arr.length < 4) { | |||
return initArr(arr); | |||
} | |||
return arr; | |||
} | |||
arr = initArr(arr); | |||
studentPersonNum[key] = arr; | |||
} | |||
}; | |||
onMounted(() => {}); | |||
</script> | |||
<style lang="scss" scoped> | |||
main { | |||
padding: 50px 67px 0px 63px; | |||
.numsBox { | |||
ul { | |||
display: flex; | |||
justify-content: space-around; | |||
padding: 0px 400px 57px 400px; | |||
li { | |||
.title { | |||
color: #08edf3; | |||
text-align: center; | |||
font-size: 22px; | |||
margin-bottom: 21px; | |||
} | |||
.numList { | |||
display: flex; | |||
height: 82px; | |||
div { | |||
background: url("/static/screen/img/numbg.png") no-repeat; | |||
background-size: cover; | |||
width: 55px; | |||
height: 82px; | |||
color: #09fbff; | |||
text-shadow: 0px 0px 4px rgba(168, 233, 255, 0.64); | |||
font-size: 48px; | |||
text-align: center; | |||
line-height: 82px; | |||
margin-right: 9px; | |||
&:last-child { | |||
margin-right: 0; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
.main { | |||
display: flex; | |||
.left { | |||
width: 1630px; | |||
height: 1036.977px; | |||
margin-right: 24px; | |||
padding: 48px 42px; | |||
box-sizing: border-box; | |||
.timeslot { | |||
color: #fff; | |||
font-size: 20px; | |||
} | |||
ul { | |||
display: flex; | |||
flex-wrap: wrap; | |||
li { | |||
width: 152.372px; | |||
height: 153.691px; | |||
border-radius: 18px; | |||
margin-right: 18.6px; | |||
margin-bottom: 24px; | |||
&:nth-child(9n) { | |||
margin-right: 0px; | |||
} | |||
.title { | |||
text-align: center; | |||
font-size: 30px; | |||
font-weight: 700; | |||
color: #fff; | |||
width: 152.372px; | |||
height: 65.401px; | |||
box-sizing: border-box; | |||
padding-top: 20px; | |||
} | |||
.des { | |||
color: #fff; | |||
text-align: center; | |||
font-size: 20px; | |||
margin-top: 13px; | |||
} | |||
.sex { | |||
color: #040404; | |||
text-align: center; | |||
font-size: 14px; | |||
margin-top: 13px; | |||
} | |||
} | |||
li[data-status="0"] { | |||
box-shadow: 0 0 0 0px rgb(255, 255, 255, 0.7), inset 0 0 36px rgb(255, 255, 255, 0.4); | |||
background: #0c4dcf; | |||
.title { | |||
color: #59edfd; | |||
} | |||
.des { | |||
color: #fff; | |||
} | |||
.sex { | |||
color: #09fbff; | |||
} | |||
} | |||
li[data-status="1"] { | |||
background: #fff8e5; | |||
.title { | |||
background: url("/static/screen/img/yellowbg.png"); | |||
background-size: cover; | |||
} | |||
.des { | |||
color: #ee8f00; | |||
} | |||
} | |||
li[data-status="2"] { | |||
background: #ffe7e5; | |||
.title { | |||
background: url("/static/screen/img/redbg.png"); | |||
background-size: cover; | |||
} | |||
.des { | |||
color: #de3d32; | |||
} | |||
} | |||
li[data-status="3"] { | |||
background: #c6f2e1; | |||
.title { | |||
background: url("/static/screen/img/greenbg.png"); | |||
background-size: cover; | |||
} | |||
.des { | |||
color: #26ab43; | |||
} | |||
} | |||
} | |||
} | |||
.right { | |||
flex: 1; | |||
.imgBox { | |||
width: 776px; | |||
height: 446px; | |||
img { | |||
width: 100%; | |||
height: 100%; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
:deep(.el-table) { | |||
--el-table-border-color: #2e84e5; | |||
--el-table-header-bg-color: #0037a4; | |||
--el-table-text-color: #fff; | |||
--el-table-header-text-color: #fff; | |||
} | |||
:deep(.el-table .tableRowClassName:nth-child(2n)) { | |||
--el-table-tr-bg-color: #021940; | |||
--el-table-row-hover-bg-color: #021940; | |||
} | |||
:deep(.el-table .tableRowClassName:nth-child(2n-1)) { | |||
--el-table-tr-bg-color: #082253; | |||
--el-table-row-hover-bg-color: #082253; | |||
} | |||
:deep(.el-table__empty-block) { | |||
background: linear-gradient(179deg, rgb(8, 19, 64) -54.28%, rgb(17, 30, 85) 124.4%); | |||
} | |||
:deep(.el-table__body-wrapper) { | |||
background: linear-gradient(179deg, rgb(8, 19, 64) -54.28%, rgb(17, 30, 85) 124.4%); | |||
} | |||
</style> | |||