Files
lc_frontend/src/views/screen/parkScreen.vue

1091 lines
29 KiB
Vue
Raw Normal View History

2025-10-17 10:31:13 +08:00
<template>
<div class="dashboard-container">
<!-- 顶部标题栏 -->
<div class="header-container">
<div class="header-left">
<img class="back-img" @click="returnToHeadquarters" src="@/assets/images/screen/back_image.png" />
<div class="back-button"> {{ selectedPark }} </div>
</div>
<h1 class="header-title">{{ selectedPark }}综合监控大屏</h1>
<div class="header-right">
{{ currentTime }}
</div>
</div>
<!-- 天气预报 -->
<WeatherWarning />
<!-- 主内容区 -->
<div class="content-container">
<div class="left-wrapper">
<!-- 左上区域 -->
<div class="left-top">
<div class="panel-title">总体概览</div>
<img style="margin: 8px 0" src="@/assets/images/title_border_line_1.png" />
<div class="top-card">
<div class="top-card-left">
<div>
<img width="33px" src="@/assets/images/1_224520_821.png" />
</div>
<span>总计</span>
<div class="number-wrapper">
<span class="total-number" v-for="item in 6" :key="item">{{ item }}</span>
</div>
<span></span>
</div>
<div class="top-card-right">
<div class="top-card-right-item">
<img width="18px" src="@/assets/images/v2_rel0n8.png" />
<span>正式员工</span>
<div class="type-number-wrapper" style="margin-left: 2vw">
<span class="type-number" v-for="item in 3" :key="item">{{ item }}</span>
</div>
<span></span>
</div>
<div class="top-card-right-item">
<img width="18px" src="@/assets/images/v2_rel0n23.png" />
<span>外协人员</span>
<div class="type-number-wrapper" style="margin-left: 1vw">
<span class="type-number" v-for="item in 2" :key="item">{{ item }}</span>
</div>
<span></span>
</div>
<div class="top-card-right-item">
<img width="18px" src="@/assets/images/24508_654.png" />
<span>访客</span>
<div class="type-number-wrapper">
<span class="type-number" v-for="item in 4" :key="item">{{ item }}</span>
</div>
<span></span>
</div>
</div>
</div>
<div class="bottom-card">
<div class="bottom-card-title">
<!--隐患排查治理-->
<span></span>
2025-10-17 10:31:13 +08:00
<img width="50%" style="margin: 8px 0" src="@/assets/images/line_1.png" />
</div>
<div class="type-wrapper">
<div class="type-item">
<span class="type-btn green">一般</span>
<span class="type-num">1234</span>
</div>
<!-- <div class="type-item">
<span class="type-btn blue">较大</span>
<span class="type-num">1234</span>
</div> -->
<div class="type-item">
<span class="type-btn yellow">重大</span>
<span class="type-num">1234</span>
</div>
</div>
<div class="hazard-wrapper">
<div ref="progressChartRef" class="progress-chart"></div>
<div class="progress-legend">
<div class="legend-item"><span class="dot red"></span>已逾期</div>
<div class="legend-item"><span class="dot green"></span>已处理</div>
</div>
<div class="progress-legend">
<div class="legend-item"><span class="dot yellow"></span>待排查</div>
<div class="legend-item"><span class="dot blue"></span>处理中</div>
</div>
</div>
</div>
</div>
<!-- 左下区域 -->
<div class="left-bottom">
<div class="panel-title">
<div class="tabs">
<span class="tab" :class="{ active: activeTab === '消防演练' }" @click="activeTab = '消防演练'">消防演练</span>
<span class="divider">|</span>
<span
class="tab" :class="{ active: activeTab === '安全、保密培训' }"
@click="activeTab = '安全、保密培训'">安全保密培训</span>
</div>
</div>
<img style="margin: 8px 0" src="@/assets/images/title_border_line.png" />
<div class="bottom-card">
<div class="bottom-card-title" style="margin-top: 0;">
<span>各园区统计</span>
<img width="50%" style="margin: 8px 0" src="@/assets/images/line_1.png" />
</div>
</div>
<div class="row-wrapper">
<div class="row-item">
<img src="@/assets/images/screen/participants_icon.png" width="20" />
<span class="row-item-title">累计参与人次</span>
<span class="row-item-number">1234</span>
</div>
<div class="row-item">
<img src="@/assets/images/screen/training_plan_icon.png" width="20" />
<span class="row-item-title">培训/考试计划</span>
<span class="row-item-number">1234</span>
</div>
<div class="row-item">
<img src="@/assets/images/screen/to_completed_icon.png" width="20" />
<span class="row-item-title">待完成人数</span>
<span class="row-item-number">1234</span>
</div>
</div>
<div style="flex: 1; display: flex; width: 100%;">
<AlertList style="margin-left: 1vw;" :list-data="source"/>
</div>
</div>
</div>
<div class="right-wrapper">
<!-- 右上区域 -->
<div class="right-top" style="text-align: right">
<div class="panel-title">安防告警</div>
<div>
<img style="margin: 8px 0" src="@/assets/images/title_border_line_1.png" />
</div>
<div class="tip-container">
<div class="tip-image">
<img src="@/assets/images/screen/circle_image.png" width="80" height="80" />
<span class="number">1234</span>
</div>
<img src="@/assets/images/screen/tip_bg_image.png" width="100%" height="70" />
<div class="tip-content">
<div class="col-item">
<img src="@/assets/images/screen/warning_img.png" width="23" />
<span>告警总数</span>
<span style="font-size: 1.2rem; marker-start: 2vw; color: yellow;">456</span>
</div>
<div class="col-item">
<span>已处理</span>
<span style="font-size: 1.2rem; marker-start: 2vw; color: greenyellow;">456</span>
</div>
<div class="col-item" style=" display: flex;margin-left: 2vw; align-items: center;">
<span>待处理/处理中</span>
<span style="font-size: 1.2rem; marker-start: 2vw; color: yellow;">456</span>
</div>
</div>
</div>
<div style=" display: flex; width: 100%;margin-top: 1vw; flex: 1; justify-content: end;">
<AlertList style="margin-right: 1vw;" title="告警详情" :list-data="source" />
</div>
</div>
<!-- 右下区域 -->
<div class="right-bottom" style="text-align: right">
<div class="panel-title">超时工单</div>
<img style="margin: 8px 0" src="@/assets/images/title_border_line_1.png" />
<div class="tip-container">
<div class="tip-image">
<img src="@/assets/images/screen/circle_image.png" width="80" height="80" />
<span class="number">1234</span>
</div>
<img src="@/assets/images/screen/tip_bg_image.png" width="100%" height="70" />
<div class="tip-content">
<div class="col-item">
<img src="@/assets/images/screen/warning_img.png" width="23" />
<span>超时工单数</span>
<span style="font-size: 1.2rem; marker-start: 2vw; color: red;">10</span>
</div>
</div>
</div>
<div style=" display: flex; width: 100%;margin-top: 1vw; flex: 1; justify-content: end;">
<AlertList style="margin-right: 1vw;" title="工单详情" :list-data="source" />
</div>
</div>
</div>
<div class="center-container">
<!-- 中部区域 -->
<ParkCenter @point-hover="handlePointHover" @point-leave="handlePointLeave" />
</div>
<!-- 点位信息弹窗 -->
<PointInfoPopup
v-if="showPointPopup" :visible="showPointPopup" :point-info="currentPoint"
:park-name="selectedPark || '雄安园区'" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick, onUnmounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import * as echarts from 'echarts'
import ParkCenter from './components/ParkCenter.vue'
import PointInfoPopup from './components/PointInfoPopup.vue'
import WeatherWarning from './components/WeatherWarning.vue'
import AlertList from './components/AlertList.vue'
interface PointPosition {
label: string
x: number
y: number
relativeX: number
relativeY: number
}
const route = useRoute()
const router = useRouter()
const selectedPark = ref('')
const currentTime = ref<string>('')
const activeTab = ref<string>('消防演练')
const showPointPopup = ref(false)
const currentPoint = ref<PointPosition | null>(null)
const progressChart = ref<echarts.ECharts | null>(null)
const donutChart = ref<echarts.ECharts | null>(null)
const progressChartRef = ref<HTMLElement | null>(null)
const donutChartRef = ref<HTMLElement | null>(null)
const source = ref([
{
text: '2024-07-10 上午8:00 雄安园区隐患内容管理 状态 处理',
error: true
},
{
text: '2025-07-09 上海XXX区域A1门禁告警 处理中 紧急',
error: true
},
{
text: '2025-07-09 上海XXX区域A1门禁告警 处理中 紧急',
error: true
},
{
text: '2020-06-18 编辑内容编辑内容编辑内容编辑内容编辑内容 状态 结果',
warn: true
},
{
text: '2020-06-18 编辑内容编辑内容编辑内容编辑内容编辑内容 状态 结果',
warn: true
},
{
text: '2020-06-18 编辑内容编辑内容编辑内容编辑内容编辑内容 状态 结果'
},
{
text: '2020-06-18 编辑内容编辑内容编辑内容编辑内容编辑内容 状态 结果'
},
{
text: '2020-06-18 编辑内容编辑内容编辑内容编辑内容编辑内容 状态 结果'
},
{
text: '2020-06-18 编辑内容编辑内容编辑内容编辑内容编辑内容 状态 结果'
}
])
// 返回上一级:总部
const returnToHeadquarters = () => {
router.back()
}
const handlePointHover = (position: PointPosition) => {
currentPoint.value = position
showPointPopup.value = true
}
const handlePointLeave = () => {
showPointPopup.value = false
currentPoint.value = null
}
// 初始化进度图表
const initProgressChart = () => {
const chartElement = progressChartRef.value
if (chartElement) {
progressChart.value = echarts.init(chartElement)
const width = progressChart.value.getWidth()
const progressOption = {
series: [
{
type: 'pie',
radius: '60%',
center: ['50%', '50%'],
left: 0,
top: 0,
bottom: 0,
data: [
{ value: 20, name: '已逾期', itemStyle: { color: '#ef4444' } },
{ value: 35, name: '已处理', itemStyle: { color: '#10b981' } },
{ value: 25, name: '待排查', itemStyle: { color: '#eab308' } },
{ value: 20, name: '处理中', itemStyle: { color: '#3b82f6' } },
],
label: {
alignTo: 'edge',
formatter: '{time|{c} %}\n',
minMargin: 5,
edgeDistance: 10,
lineHeight: 15,
rich: {
time: {
fontSize: 10,
color: '#fff'
}
}
},
labelLine: {
length: 5,
length2: 0,
maxSurfaceAngle: 10
},
labelLayout: function (params: any) {
const isLeft = params.labelRect.x < width / 2;
const points = params.labelLinePoints;
points[2][0] = isLeft
? params.labelRect.x
: params.labelRect.x + params.labelRect.width;
return {
labelLinePoints: points
};
},
}
]
}
progressChart.value.setOption(progressOption)
}
}
// 初始化环形图表
const initDonutChart = () => {
const chartElement = donutChartRef.value
if (chartElement) {
donutChart.value = echarts.init(chartElement)
const option = {
legend: {
bottom: '10%',
right: '0%',
orient: 'vertical',
textStyle: {
color: '#ffffff',
fontSize: '11px'
}
},
series: [
{
type: 'pie',
radius: ['25%', '55%'],
center: ['50%', '50%'],
data: [
{ value: 12, name: '已完成', itemStyle: { color: '#10b981' } },
{ value: 34, name: '进行中', itemStyle: { color: '#3b82f6' } },
{ value: 18, name: '待开始', itemStyle: { color: '#eab308' } },
{ value: 8, name: '已暂停', itemStyle: { color: '#ef4444' } }
],
label: {
show: true,
position: 'outside',
formatter: function (params: any) {
return params.value + ' ' + params.percent + '%'
},
fontSize: 12,
color: '#ffffff',
fontWeight: 'bold'
},
labelLine: {
show: true,
length: 15,
length2: 10,
lineStyle: {
color: '#64748b',
width: 1
}
}
}
]
}
donutChart.value.setOption(option)
}
}
onMounted(() => {
setInterval(() => {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
const hours = String(now.getHours()).padStart(2, '0')
const minutes = String(now.getMinutes()).padStart(2, '0')
const seconds = String(now.getSeconds()).padStart(2, '0')
currentTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}, 1000)
if (typeof route.query.park === 'string') {
selectedPark.value = route.query.park
}
// 初始化图表
nextTick(() => {
initProgressChart()
initDonutChart()
})
// 监听窗口大小变化,重绘图表
window.addEventListener('resize', () => {
if (progressChart.value) {
progressChart.value.resize()
}
if (donutChart.value) {
donutChart.value.resize()
}
})
})
onUnmounted(() => {
})
</script>
<style scoped lang="scss">
/* 响应式设计 - 自动适应不同屏幕尺寸 */
@media (width <=1200px) {
.dashboard-container {
.content-container {
column-gap: 4vw;
.center-container {
width: 60vh;
height: 60vh;
}
}
}
}
@media (width <=1024px) {
.dashboard-container {
.content-container {
column-gap: 3vw;
padding: 0 8px 15px;
.left-wrapper {
.left-top .hazard-wrapper {
.progress-chart {
min-height: 180px;
}
}
.left-bottom .donut-chart-with-labels {
width: 35vw;
height: 35vh;
}
}
.center-container {
width: 55vh;
height: 55vh;
}
}
}
}
@media (width <=768px) {
.dashboard-container {
.content-container {
column-gap: 2vw;
padding: 0 5px 10px;
.left-wrapper {
.left-top .hazard-wrapper {
.progress-chart {
min-height: 160px;
}
}
.left-bottom .donut-chart-with-labels {
width: 40vw;
height: 40vh;
margin-left: 1vw;
}
}
.center-container {
top: 60%;
width: 50vh;
height: 50vh;
}
}
}
}
@media (width <=480px) {
.dashboard-container {
.content-container {
column-gap: 1vw;
padding: 0 3px 8px;
.left-wrapper {
.left-top .hazard-wrapper {
.progress-chart {
min-height: 140px;
}
}
.left-bottom .donut-chart-with-labels {
width: 45vw;
height: 45vh;
margin-left: 0.5vw;
}
}
.center-container {
top: 65%;
width: 45vh;
height: 45vh;
}
}
}
}
.dot {
width: 6px;
height: 6px;
border-radius: 50%;
}
.dot.red {
background-color: #ef4444;
}
.dot.green {
background-color: #10b981;
}
.dot.yellow {
background-color: #eab308;
}
.dot.blue {
background-color: #3b82f6;
}
.dot.cyan {
background-color: #06b6d4;
}
.dot.purple {
background-color: #8b5cf6;
}
.dot.orange {
background-color: #f59e0b;
}
.dashboard-container {
position: relative;
display: flex;
width: 100vw;
height: 100vh;
font-size: 0.8rem;
color: #fff;
background: url('@/assets/images/v2_rel0n6.png');
background-color: #030e22;
background-size: cover;
flex-direction: column;
.header-container {
display: flex;
height: 80px;
background: url('@/assets/images/top_bg.png') no-repeat;
background-size: 100%;
.header-left {
display: flex;
padding-left: 1vw;
line-height: 80px;
flex: 1;
align-items: center;
.back-img {
height: 3vh;
cursor: pointer;
transition: all 0.3s ease;
}
.back-button {
display: inline-flex;
height: 2vh;
min-width: 6vw;
padding: 4px 16px;
margin-left: 0.5vw;
font-size: 0.9rem;
cursor: pointer;
background: rgb(13 24 84 / 80%);
border: 1px solid rgb(59 130 246 / 40%);
transition: all 0.3s ease;
align-items: center;
justify-content: space-between;
}
.back-button:hover {
background: rgb(59 130 246 / 30%);
border-color: rgb(59 130 246 / 60%);
}
}
.header-right {
margin-right: 20px;
font-size: 0.9rem;
line-height: 80px;
text-align: right;
flex: 1;
}
.header-title {
font-size: 1.3rem;
font-weight: bold;
line-height: 40px;
color: #fff;
text-align: center;
flex: 1;
}
}
.content-container {
position: relative;
display: flex;
width: calc(100vw - 20px);
height: calc(100vh - 105px);
padding: 0 10px 20px;
column-gap: 7vw;
.panel-title {
margin: 4px 20px 0;
font-size: 0.8rem;
font-weight: bold;
color: #fff;
}
.bottom-card {
display: flex;
flex-direction: column;
flex: 1;
width: 67%;
.bottom-card-title {
display: flex;
margin-top: 5px;
flex-direction: column;
align-items: center;
}
.type-wrapper {
display: flex;
justify-content: center;
column-gap: 2vw;
margin-top: 5px;
.type-item {
display: flex;
flex-direction: column;
align-items: center;
column-gap: 4px;
.type-btn {
display: inline-block;
padding: 2px 30px;
font-size: 0.7rem;
cursor: pointer;
background-color: rgb(59 130 246 / 20%);
border-radius: 12px;
user-select: none;
&.blue {
background-color: rgb(32 148 235);
}
&.green {
background-color: rgb(80 139 79);
}
&.yellow {
background-color: rgb(204 96 26);
}
}
.type-num {
margin-top: 0.3vh;
font-size: 0.9rem;
}
}
}
.hazard-wrapper {
flex: 1;
display: flex;
align-items: center;
column-gap: 1.8vw;
.progress-chart {
width: 55%;
min-height: 140px;
}
.progress-legend {
display: flex;
flex-direction: column;
justify-content: center;
gap: 0.5vw;
.legend-item {
display: flex;
align-items: center;
gap: 4px;
font-size: 0.7rem;
}
}
}
}
.left-wrapper {
flex: 1;
display: flex;
flex-direction: column;
row-gap: 1rem;
height: 100%;
.left-top {
padding: 0 5px;
background-image:
url('@/assets/images/screen/left_top_img.png'),
url('@/assets/images/screen/left_center_img.png'),
url('@/assets/images/screen/left_bottom_img.png');
background-position:
top center,
left center,
bottom center;
/* 设置大小,注意中间的背景图应该覆盖整个容器 */
background-repeat: no-repeat, no-repeat, no-repeat;
/* 设置位置 */
background-size:
100% 90px,
cover,
100% 68px;
flex: 1;
/* 设置重复方式 */
.top-card {
display: flex;
padding: 0 20px;
column-gap: 15px;
font-size: 0.8rem;
.top-card-left {
display: flex;
height: 12vh;
min-width: 15vw;
padding: 0 10px;
background-image: url('@/assets/imgs/total_count_card_bg.png');
background-size: cover;
column-gap: 6px;
align-items: center;
.number-wrapper {
display: flex;
align-items: center;
gap: 2px;
font-size: 0.8rem;
color: #fff;
}
.total-number {
display: inline-block;
width: 26px;
height: 50px;
font-size: 0.8rem;
line-height: 50px;
color: #fff;
text-align: center;
background-color: rgb(177 74 201 / 100%);
border-radius: 4px;
}
}
.top-card-right {
display: flex;
height: 12vh;
min-width: 20vw;
background-image: url('@/assets/imgs/staff_types_bg.png');
background-position: top center;
background-size: cover;
flex-direction: column;
justify-content: center;
row-gap: 4px;
.top-card-right-item {
display: flex;
align-items: center;
column-gap: 5px;
padding: 0 10px;
font-size: 0.7rem;
color: #fff;
.type-number-wrapper {
display: flex;
align-items: center;
gap: 2px;
font-size: 0.8rem;
color: #fff;
.type-number {
display: inline-block;
width: 14px;
height: 25px;
font-size: 0.9rem;
line-height: 25px;
color: #fff;
text-align: center;
background-color: #1afb8f;
border-radius: 2px;
}
}
}
}
}
}
.left-bottom {
background-image:
url('@/assets/images/screen/left_top_2_img.png'),
url('@/assets/images/screen/left_center_img.png'),
url('@/assets/images/screen/left_bottom_img.png');
background-position:
top center,
left center,
bottom center;
/* 设置大小,注意中间的背景图应该覆盖整个容器 */
background-repeat: no-repeat, no-repeat, no-repeat;
/* 设置位置 */
background-size:
100% 90px,
cover,
100% 68px;
flex: 1;
/* 设置重复方式 */
.tabs {
display: inline-flex;
align-items: center;
column-gap: 8px;
}
.tab {
padding: 2px 10px;
color: #fff;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s ease-in-out;
user-select: none;
}
.tab:hover {
color: #1afb8f;
}
.tab.active {
color: #1afb8f;
background: rgb(26 251 143 / 12%);
border: 1px solid rgb(26 251 143 / 35%);
}
.divider {
margin: 0 2px;
color: #94a3b8;
}
.donut-chart-with-labels {
width: 30vw;
height: 30vh;
min-height: 200px;
margin-left: 2vw;
}
.row-wrapper {
display: flex;
width: 67%;
column-gap: 1vw;
margin-left: 1vw;
.row-item {
display: flex;
padding: 0.5vh 0;
font-size: 0.7rem;
background-color: rgb(4 35 125 / 50%);
flex-direction: column;
align-items: center;
column-gap: 10px;
flex: 1;
.row-item-title {}
.row-item-number {
font-size: 1rem;
}
}
}
}
}
.right-wrapper {
display: flex;
height: 100%;
flex: 1;
flex-direction: column;
row-gap: 1rem;
.right-top {
/* 设置重复方式 */
display: flex;
background-image:
url('@/assets/images/screen/right_top_img.png'),
url('@/assets/images/screen/right_center_img.png'),
url('@/assets/images/screen/right_bottom_img.png');
background-position:
top center,
right center,
bottom center;
/* 设置大小,注意中间的背景图应该覆盖整个容器 */
background-repeat: no-repeat, no-repeat, no-repeat;
/* 设置位置 */
background-size:
100% 90px,
cover,
100% 68px;
flex: 1;
flex-direction: column;
align-items: flex-end;
.tip-container {
position: relative;
display: flex;
width: 70%;
height: 80px;
padding-right: 20px;
align-items: center;
justify-content: end;
.tip-image {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%) translateX(-50%);
.number {
position: absolute;
top: 50%;
left: 50%;
font-size: 1rem;
color: #fff;
transform: translate(-80%, -50%);
}
}
.tip-content {
position: absolute;
inset: 0% 0 0 6%;
display: flex;
padding-left: 20px;
align-items: center;
.col-item {
display: flex;
margin-left: 1vw;
align-items: center;
span {
margin-right: 10px;
}
}
}
}
}
.right-bottom {
/* 设置重复方式 */
display: flex;
background-image:
url('@/assets/images/screen/right_top_img.png'),
url('@/assets/images/screen/right_center_img.png'),
url('@/assets/images/screen/right_bottom_img.png');
background-position:
top center,
right center,
bottom center;
/* 设置大小,注意中间的背景图应该覆盖整个容器 */
background-repeat: no-repeat, no-repeat, no-repeat;
/* 设置位置 */
background-size:
100% 90px,
cover,
100% 68px;
flex: 1;
flex-direction: column;
align-items: flex-end;
.tip-container {
position: relative;
display: flex;
width: 50%;
height: 80px;
padding-right: 20px;
align-items: center;
justify-content: end;
.tip-image {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%) translateX(-50%);
.number {
position: absolute;
top: 50%;
left: 50%;
font-size: 1rem;
color: #fff;
transform: translate(-80%, -50%);
}
}
.tip-content {
position: absolute;
inset: 0% 0 0 6%;
display: flex;
padding-left: 20px;
align-items: center;
.col-item {
display: flex;
margin-left: 1vw;
align-items: center;
span {
margin-right: 10px;
}
}
}
}
}
}
.center-container {
position: fixed;
top: 55%;
left: 50%;
z-index: 1;
display: flex;
width: 65vh;
height: 65vh;
color: #fff;
background-image: url('@/assets/images/circle_bg.png');
background-size: cover;
transform: translate(-50%, -50%);
align-items: center;
justify-content: center;
}
}
}
</style>