@@ -22,3 +22,4 @@ export * from "./statistion"; | |||||
export * from "./usermanage"; | export * from "./usermanage"; | ||||
export * from "./attendance"; | export * from "./attendance"; | ||||
export * from "./violation"; | 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") | 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> | |||||