挂架平效果
This commit is contained in:
@@ -5,6 +5,32 @@
|
|||||||
<img width="50%" src="@/assets/images/line_1.png" />
|
<img width="50%" src="@/assets/images/line_1.png" />
|
||||||
</div>
|
</div>
|
||||||
<div class="list" :style="{ maxHeight: maxHeight }">
|
<div class="list" :style="{ maxHeight: maxHeight }">
|
||||||
|
<!-- 骨架屏 -->
|
||||||
|
<template v-if="props.loading">
|
||||||
|
<!-- 表格模式骨架屏 -->
|
||||||
|
<template v-if="tableTitle && tableTitle.length > 0">
|
||||||
|
<div class="table-header">
|
||||||
|
<div class="header-item skeleton-pulse" v-for="(title, index) in tableTitle" :key="`header-skeleton-${index}`"></div>
|
||||||
|
</div>
|
||||||
|
<div class="list-wrapper">
|
||||||
|
<div class="table-row skeleton-row" v-for="i in 10" :key="`table-skeleton-${i}`">
|
||||||
|
<div class="table-cell skeleton-pulse" v-for="(title, cellIndex) in tableTitle" :key="`cell-skeleton-${i}-${cellIndex}`"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 列表模式骨架屏 -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="list-wrapper">
|
||||||
|
<div class="list-item skeleton-item" v-for="i in 10" :key="`list-skeleton-${i}`">
|
||||||
|
<div class="alert-text skeleton-pulse"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 实际内容 -->
|
||||||
|
<template v-else>
|
||||||
<!-- 表格头部 -->
|
<!-- 表格头部 -->
|
||||||
<div v-if="tableTitle && tableTitle.length > 0" class="table-header">
|
<div v-if="tableTitle && tableTitle.length > 0" class="table-header">
|
||||||
<div class="header-item" v-for="(title, index) in tableTitle" :key="index">
|
<div class="header-item" v-for="(title, index) in tableTitle" :key="index">
|
||||||
@@ -31,6 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -51,6 +78,7 @@ interface TableTitle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
loading?: boolean
|
||||||
title?: string
|
title?: string
|
||||||
listData: AlertItem[]
|
listData: AlertItem[]
|
||||||
maxHeight?: string
|
maxHeight?: string
|
||||||
@@ -313,4 +341,31 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 骨架屏样式
|
||||||
|
.skeleton-pulse {
|
||||||
|
animation: skeleton-loading-alertlist 1.5s ease-in-out infinite;
|
||||||
|
background-color: #444;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-row {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-item {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes skeleton-loading-alertlist {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -10,11 +10,13 @@
|
|||||||
<div class="type-wrapper">
|
<div class="type-wrapper">
|
||||||
<div class="type-item">
|
<div class="type-item">
|
||||||
<span class="type-btn">重大</span>
|
<span class="type-btn">重大</span>
|
||||||
<span class="type-num cursor-pointer" @click="handleMajorClick">{{ hiddenDangerData?.major || 0 }}</span>
|
<div style="width: 20px; height: 20px" v-if="props.loading" class="type-num skeleton-pulse"></div>
|
||||||
|
<span v-else class="type-num cursor-pointer" @click="handleMajorClick">{{ hiddenDangerData?.major || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="type-item">
|
<div class="type-item">
|
||||||
<span class="type-btn active">一般</span>
|
<span class="type-btn active">一般</span>
|
||||||
<span class="type-num cursor-pointer" @click="handleMajorClick">{{ hiddenDangerData?.general || 0 }}</span>
|
<div style="width: 20px; height: 20px" v-if="props.loading" class="type-num skeleton-pulse"></div>
|
||||||
|
<span v-else class="type-num cursor-pointer" @click="handleMajorClick">{{ hiddenDangerData?.general || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -30,7 +32,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="echart-wrapper">
|
<div class="echart-wrapper">
|
||||||
<div class="lf-rt">
|
<div class="lf-rt">
|
||||||
<Echart :options="progressChartOption" class="progress-chart" height="80%" />
|
<div v-if="props.loading" class="skeleton-chart-circle skeleton-pulse"></div>
|
||||||
|
<Echart v-else :options="progressChartOption" class="progress-chart" height="80%" />
|
||||||
<div class="progress-legend">
|
<div class="progress-legend">
|
||||||
<div class="legend-item"><span class="dot red"></span>已逾期</div>
|
<div class="legend-item"><span class="dot red"></span>已逾期</div>
|
||||||
<div class="legend-item"><span class="dot green"></span>已处理</div>
|
<div class="legend-item"><span class="dot green"></span>已处理</div>
|
||||||
@@ -41,7 +44,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lf-rt">
|
<div class="lf-rt">
|
||||||
<Echart :options="top3TypesChartOption" class="progress-chart" height="80%" />
|
<div v-if="props.loading" class="skeleton-chart-circle skeleton-pulse"></div>
|
||||||
|
<Echart v-else :options="top3TypesChartOption" class="progress-chart" height="80%" />
|
||||||
<div class="progress-legend-column">
|
<div class="progress-legend-column">
|
||||||
<div class="legend-item">
|
<div class="legend-item">
|
||||||
<span class="dot blue"></span>
|
<span class="dot blue"></span>
|
||||||
@@ -65,7 +69,8 @@
|
|||||||
<p class="safe-tooltip" title="安全指数 = (1-逾期隐患百分比) × 40% + 安全考核通过率 × 10% + 安全培训完成率 × 10%
|
<p class="safe-tooltip" title="安全指数 = (1-逾期隐患百分比) × 40% + 安全考核通过率 × 10% + 安全培训完成率 × 10%
|
||||||
+ 安全类工单完成率 × 20% + 工程类工单完成率 × 20%"></p>
|
+ 安全类工单完成率 × 20% + 工程类工单完成率 × 20%"></p>
|
||||||
</span>
|
</span>
|
||||||
<span class="pending-count">{{ hiddenDangerData?.safetyIndex || 0 }}</span>
|
<span v-if="props.loading" class="pending-count skeleton-pulse"></span>
|
||||||
|
<span v-else class="pending-count">{{ hiddenDangerData?.safetyIndex || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -77,6 +82,7 @@ import echarts from '@/plugins/echarts'
|
|||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
loading?: boolean
|
||||||
hiddenDangerData?: {
|
hiddenDangerData?: {
|
||||||
general: number
|
general: number
|
||||||
major: number
|
major: number
|
||||||
@@ -936,4 +942,32 @@ watch(() => props.hiddenDangerData?.top3Types, (newVal) => {
|
|||||||
.dot.blue {
|
.dot.blue {
|
||||||
background-color: #3b82f6;
|
background-color: #3b82f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 骨架屏动画
|
||||||
|
.skeleton-pulse {
|
||||||
|
animation: skeleton-loading-hiddendanger 1.5s ease-in-out infinite;
|
||||||
|
background-color: #444;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 圆形饼图骨架屏
|
||||||
|
.skeleton-chart-circle {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: 30px auto 50px auto;
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes skeleton-loading-hiddendanger {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,29 +4,37 @@
|
|||||||
<div>
|
<div>
|
||||||
<img style="margin: 8px 0" src="@/assets/images/title_border_line_1.png" />
|
<img style="margin: 8px 0" src="@/assets/images/title_border_line_1.png" />
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 骨架屏 -->
|
||||||
|
|
||||||
|
<!-- 实际内容 -->
|
||||||
<div class="tip-container">
|
<div class="tip-container">
|
||||||
<div class="tip-image">
|
<div class="tip-image">
|
||||||
<img src="@/assets/images/screen/circle_image.png" width="80" height="80" />
|
<img src="@/assets/images/screen/circle_image.png" width="80" height="80" />
|
||||||
<span class="number">{{ alertData?.total || 0 }}</span>
|
<div v-if="props.loading" style="width: 20px; height: 20px;" class="number skeleton-pulse"></div>
|
||||||
|
<span v-else class="number">{{ alertData?.total || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<img src="@/assets/images/screen/tip_bg_image.png" width="100%" height="70" />
|
<img src="@/assets/images/screen/tip_bg_image.png" width="100%" height="70" />
|
||||||
<div class="tip-content">
|
<div class="tip-content">
|
||||||
<div class="col-item">
|
<div class="col-item">
|
||||||
<img src="@/assets/images/screen/warning_img.png" width="23" />
|
<img src="@/assets/images/screen/warning_img.png" width="23" />
|
||||||
<span>告警总数</span>
|
<span>告警总数</span>
|
||||||
<span style="font-size: 1.2rem; marker-start: 2vw; color: yellow;">{{ alertData?.total || 0 }}</span>
|
<div v-if="props.loading" style="width: 20px; height: 20px;" class="skeleton-pulse"></div>
|
||||||
|
<span v-else style="font-size: 1.2rem; marker-start: 2vw; color: yellow;">{{ alertData?.total || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-item">
|
<div class="col-item">
|
||||||
<span>已处理</span>
|
<span>已处理</span>
|
||||||
<span style="font-size: 1.2rem; marker-start: 2vw; color: greenyellow;">{{ alertData?.processed || 0 }}</span>
|
<div v-if="props.loading" style="width: 20px; height: 20px;" class="skeleton-pulse"></div>
|
||||||
|
<span v-else style="font-size: 1.2rem; marker-start: 2vw; color: greenyellow;">{{ alertData?.processed || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-item" style="display: flex; margin-left: 1vw; align-items: center;">
|
<div class="col-item" style="display: flex; margin-left: 1vw; align-items: center;">
|
||||||
<span>待处理</span>
|
<span>待处理</span>
|
||||||
<span style="font-size: 1.2rem; marker-start: 2vw; color: yellow;">{{ alertData?.pending || 0 }}</span>
|
<div v-if="props.loading" style="width: 20px; height: 20px;" class="skeleton-pulse"></div>
|
||||||
|
<span v-else style="font-size: 1.2rem; marker-start: 2vw; color: yellow;">{{ alertData?.pending || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-item" style="display: flex; margin-left: 1vw; align-items: center;">
|
<div class="col-item" style="display: flex; margin-left: 1vw; align-items: center;">
|
||||||
<span>处理中</span>
|
<span>处理中</span>
|
||||||
<span style="font-size: 1.2rem; marker-start: 2vw; color: yellow;">{{ alertData?.processing }}</span>
|
<div v-if="props.loading" style="width: 20px; height: 20px;" class="skeleton-pulse"></div>
|
||||||
|
<span v-else style="font-size: 1.2rem; marker-start: 2vw; color: yellow;">{{ alertData?.processing }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +53,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div> -->
|
||||||
<AlertList style="margin-right: 1vw;" title="告警详情" :list-data="alertDetails" :linkUrl="linkUrl"></AlertList>
|
<AlertList style="margin-right: 1vw;" title="告警详情" :list-data="alertDetails" :linkUrl="linkUrl" :loading="props.loading"></AlertList>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -73,6 +81,7 @@ interface Props {
|
|||||||
alertDetails?: AlertItem[]
|
alertDetails?: AlertItem[]
|
||||||
sourceIndex?: number
|
sourceIndex?: number
|
||||||
linkUrl?: string
|
linkUrl?: string
|
||||||
|
loading?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认值
|
// 默认值
|
||||||
@@ -84,7 +93,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
processing: 0
|
processing: 0
|
||||||
}),
|
}),
|
||||||
alertDetails: () => [],
|
alertDetails: () => [],
|
||||||
sourceIndex: 1
|
sourceIndex: 1,
|
||||||
|
loading: false
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -265,5 +275,104 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 骨架屏样式
|
||||||
|
.skeleton-container {
|
||||||
|
.skeleton-tip-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 70px;
|
||||||
|
|
||||||
|
.skeleton-tip-image {
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
right: 10px;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
.skeleton-circle {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-number {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 30px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-bg {
|
||||||
|
width: 100%;
|
||||||
|
height: 70px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-tip-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
padding: 8px 15px;
|
||||||
|
|
||||||
|
.skeleton-col-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.skeleton-icon {
|
||||||
|
width: 23px;
|
||||||
|
height: 23px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-text {
|
||||||
|
flex: 1;
|
||||||
|
height: 16px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-value {
|
||||||
|
width: 40px;
|
||||||
|
height: 18px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 骨架屏动画
|
||||||
|
.skeleton-pulse {
|
||||||
|
animation: skeleton-loading-highrisk 1.5s ease-in-out infinite;
|
||||||
|
background-color: #444;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes skeleton-loading-highrisk {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,7 +3,34 @@
|
|||||||
<div class="panel-title">人员管理</div>
|
<div class="panel-title">人员管理</div>
|
||||||
<img style="margin: 8px 0" src="@/assets/images/title_border_line_1.png" />
|
<img style="margin: 8px 0" src="@/assets/images/title_border_line_1.png" />
|
||||||
|
|
||||||
<div class="top-card">
|
<!-- 骨架屏 -->
|
||||||
|
<div v-if="props.loading" class="skeleton-container">
|
||||||
|
<div class="skeleton-card">
|
||||||
|
<div class="skeleton-left">
|
||||||
|
<div class="skeleton-icon skeleton-pulse"></div>
|
||||||
|
<div class="skeleton-text skeleton-pulse"></div>
|
||||||
|
<div class="skeleton-numbers">
|
||||||
|
<div class="skeleton-number skeleton-pulse" v-for="i in 6" :key="i"></div>
|
||||||
|
</div>
|
||||||
|
<div class="skeleton-text skeleton-pulse"></div>
|
||||||
|
</div>
|
||||||
|
<div class="skeleton-right">
|
||||||
|
<div class="skeleton-item" v-for="i in 3" :key="i">
|
||||||
|
<div class="skeleton-row">
|
||||||
|
<div class="skeleton-icon-small skeleton-pulse"></div>
|
||||||
|
<div class="skeleton-text skeleton-pulse"></div>
|
||||||
|
</div>
|
||||||
|
<div class="skeleton-numbers">
|
||||||
|
<div class="skeleton-number-small skeleton-pulse" v-for="j in 4" :key="j"></div>
|
||||||
|
</div>
|
||||||
|
<div class="skeleton-text skeleton-pulse"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 实际内容 -->
|
||||||
|
<div v-else class="top-card">
|
||||||
<div class="top-card-left">
|
<div class="top-card-left">
|
||||||
<div>
|
<div>
|
||||||
<img width="33px" src="@/assets/images/1_224520_821.png" />
|
<img width="33px" src="@/assets/images/1_224520_821.png" />
|
||||||
@@ -64,6 +91,7 @@
|
|||||||
<span>各园区统计</span>
|
<span>各园区统计</span>
|
||||||
<img width="50%" style="margin: 8px 0" src="@/assets/images/line_1.png" />
|
<img width="50%" style="margin: 8px 0" src="@/assets/images/line_1.png" />
|
||||||
</div>
|
</div>
|
||||||
|
<!-- <div v-if="props.loading" class="skeleton-chart-circle skeleton-pulse"></div> -->
|
||||||
<Echart :options="barChartOption" class="bar-chart" height="17.5vh" />
|
<Echart :options="barChartOption" class="bar-chart" height="17.5vh" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,6 +102,7 @@ import { ref, onMounted, watch, computed } from 'vue'
|
|||||||
import { rgbToHex } from '@/utils/color'
|
import { rgbToHex } from '@/utils/color'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
loading?: boolean
|
||||||
totalCount: number
|
totalCount: number
|
||||||
formalEmployeeCount: number
|
formalEmployeeCount: number
|
||||||
externalStaffCount: number
|
externalStaffCount: number
|
||||||
@@ -319,5 +348,141 @@ onMounted(() => {
|
|||||||
min-height: 17.5vh;
|
min-height: 17.5vh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 骨架屏样式
|
||||||
|
.skeleton-container {
|
||||||
|
.skeleton-card {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 20px;
|
||||||
|
column-gap: 15px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
|
||||||
|
.skeleton-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;
|
||||||
|
|
||||||
|
.skeleton-icon {
|
||||||
|
width: 33px;
|
||||||
|
height: 33px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-text {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-numbers {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
|
||||||
|
.skeleton-number {
|
||||||
|
width: 26px;
|
||||||
|
height: 50px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-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;
|
||||||
|
|
||||||
|
.skeleton-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 5px;
|
||||||
|
padding: 0 10px;
|
||||||
|
|
||||||
|
.skeleton-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.skeleton-icon-small {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-text {
|
||||||
|
height: 16px;
|
||||||
|
width: 60px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-numbers {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
|
||||||
|
.skeleton-number-small {
|
||||||
|
width: 14px;
|
||||||
|
height: 25px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 骨架屏动画
|
||||||
|
.skeleton-pulse {
|
||||||
|
animation: skeleton-loading-overview 1.5s ease-in-out infinite;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 圆形饼图骨架屏
|
||||||
|
.skeleton-chart-circle {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: 30px auto 50px auto;
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 柱状图骨架屏
|
||||||
|
// .skeleton-chart-bar {
|
||||||
|
// width: 100%;
|
||||||
|
// height: 17.5vh;
|
||||||
|
// background-color: #444;
|
||||||
|
// border-radius: 8px;
|
||||||
|
// }
|
||||||
|
|
||||||
|
@keyframes skeleton-loading-overview {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -9,7 +9,31 @@
|
|||||||
</div>
|
</div>
|
||||||
<img class="title-line" src="@/assets/images/title_border_line.png" />
|
<img class="title-line" src="@/assets/images/title_border_line.png" />
|
||||||
|
|
||||||
<div class="chart-grid">
|
<!-- 骨架屏 -->
|
||||||
|
<div v-if="props.loading" class="skeleton-container">
|
||||||
|
<div class="skeleton-grid">
|
||||||
|
<div class="skeleton-card" v-for="i in 6" :key="i">
|
||||||
|
<div class="skeleton-title skeleton-pulse"></div>
|
||||||
|
<div class="skeleton-chart">
|
||||||
|
<div class="skeleton-chart-circle skeleton-pulse"></div>
|
||||||
|
<div class="skeleton-chart-center">
|
||||||
|
<div class="skeleton-text-small skeleton-pulse"></div>
|
||||||
|
<div class="skeleton-text-large skeleton-pulse"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="skeleton-legend">
|
||||||
|
<div class="skeleton-legend-item" v-for="j in 4" :key="j">
|
||||||
|
<div class="skeleton-dot skeleton-pulse"></div>
|
||||||
|
<div class="skeleton-text skeleton-pulse"></div>
|
||||||
|
<div class="skeleton-value skeleton-pulse"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 实际内容 -->
|
||||||
|
<div v-else class="chart-grid">
|
||||||
<div class="chart-card" v-for="item in currentCharts" :key="`${activeTab}-${item.title}`">
|
<div class="chart-card" v-for="item in currentCharts" :key="`${activeTab}-${item.title}`">
|
||||||
<div class="chart-title" @click="handleChartTitleClick()">{{ item.title }}</div>
|
<div class="chart-title" @click="handleChartTitleClick()">{{ item.title }}</div>
|
||||||
<div class="chart-content">
|
<div class="chart-content">
|
||||||
@@ -85,6 +109,7 @@ const tabCharts = ref<Record<TabType, ChartItem[]>>({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
loading?: boolean
|
||||||
riskStatistics?: Record<TabType, ChartItem[]>
|
riskStatistics?: Record<TabType, ChartItem[]>
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
@@ -415,5 +440,122 @@ const handleTabClick = (tab: TabType) => {
|
|||||||
// padding: 5px;
|
// padding: 5px;
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// 骨架屏样式
|
||||||
|
.skeleton-container {
|
||||||
|
.skeleton-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.skeleton-card {
|
||||||
|
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;
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.skeleton-title {
|
||||||
|
width: 120px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-chart {
|
||||||
|
position: relative;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.skeleton-chart-circle {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-chart-center {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.skeleton-text-small {
|
||||||
|
width: 40px;
|
||||||
|
height: 12px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-text-large {
|
||||||
|
width: 30px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-legend {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.skeleton-legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
.skeleton-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-text {
|
||||||
|
flex: 1;
|
||||||
|
height: 12px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-value {
|
||||||
|
width: 20px;
|
||||||
|
height: 12px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 骨架屏动画
|
||||||
|
.skeleton-pulse {
|
||||||
|
animation: skeleton-loading-riskstats 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes skeleton-loading-riskstats {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -3,17 +3,21 @@
|
|||||||
<div class="panel-title">超时工单</div>
|
<div class="panel-title">超时工单</div>
|
||||||
<img style="margin: 8px 0" src="@/assets/images/title_border_line_1.png" />
|
<img style="margin: 8px 0" src="@/assets/images/title_border_line_1.png" />
|
||||||
|
|
||||||
|
<!-- 骨架屏 -->
|
||||||
|
<!-- 实际内容 -->
|
||||||
<div class="tip-container">
|
<div class="tip-container">
|
||||||
<div class="tip-image">
|
<div class="tip-image">
|
||||||
<img src="@/assets/images/screen/circle_image.png" width="80" height="80" />
|
<img src="@/assets/images/screen/circle_image.png" width="80" height="80" />
|
||||||
<span class="number">{{ timeoutWorkOrders?.total || 0 }}</span>
|
<div v-if="props.loading" style="width: 20px; height: 20px;" class="number skeleton-pulse"></div>
|
||||||
|
<span v-else class="number">{{ timeoutWorkOrders?.total || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<img src="@/assets/images/screen/tip_bg_image.png" width="100%" height="70" />
|
<img src="@/assets/images/screen/tip_bg_image.png" width="100%" height="70" />
|
||||||
<div class="tip-content">
|
<div class="tip-content">
|
||||||
<div class="col-item">
|
<div class="col-item">
|
||||||
<img src="@/assets/images/screen/warning_img.png" width="23" />
|
<img src="@/assets/images/screen/warning_img.png" width="23" />
|
||||||
<span>超时工单数</span>
|
<span>超时工单数</span>
|
||||||
<span style="font-size: 1.2rem; marker-start: 2vw; color: red;">{{ timeoutWorkOrders?.total || 0 }}</span>
|
<div v-if="props.loading" style="width: 20px; height: 20px;" class="skeleton-pulse"></div>
|
||||||
|
<span v-else style="font-size: 1.2rem; marker-start: 2vw; color: red;">{{ timeoutWorkOrders?.total || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -32,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div> -->
|
||||||
<AlertList :linkUrl="linkUrl" style="margin-right: 1vw;" title="工单详情" :list-data="alertDetails" ></AlertList>
|
<AlertList :linkUrl="linkUrl" style="margin-right: 1vw;" title="工单详情" :list-data="alertDetails" :loading="props.loading" ></AlertList>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -52,6 +56,7 @@ interface TimeoutWorkOrders {
|
|||||||
|
|
||||||
// Props定义
|
// Props定义
|
||||||
interface Props {
|
interface Props {
|
||||||
|
loading?: boolean
|
||||||
timeoutWorkOrders?: TimeoutWorkOrders
|
timeoutWorkOrders?: TimeoutWorkOrders
|
||||||
alertDetails?: AlertItem[]
|
alertDetails?: AlertItem[]
|
||||||
sourceIndex?: number
|
sourceIndex?: number
|
||||||
@@ -247,5 +252,104 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 骨架屏样式
|
||||||
|
.skeleton-container {
|
||||||
|
.skeleton-tip-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 70px;
|
||||||
|
|
||||||
|
.skeleton-tip-image {
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
right: 10px;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
.skeleton-circle {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-number {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 30px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-bg {
|
||||||
|
width: 100%;
|
||||||
|
height: 70px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-tip-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8px 15px;
|
||||||
|
|
||||||
|
.skeleton-col-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.skeleton-icon {
|
||||||
|
width: 23px;
|
||||||
|
height: 23px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-text {
|
||||||
|
flex: 1;
|
||||||
|
height: 16px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-value {
|
||||||
|
width: 40px;
|
||||||
|
height: 18px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 骨架屏动画
|
||||||
|
.skeleton-pulse {
|
||||||
|
animation: skeleton-loading-timeout 1.5s ease-in-out infinite;
|
||||||
|
background-color: #444;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes skeleton-loading-timeout {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -24,25 +24,28 @@
|
|||||||
<!-- 主内容区 -->
|
<!-- 主内容区 -->
|
||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
<div class="left-wrapper">
|
<div class="left-wrapper">
|
||||||
<OverviewPanel :totalCount="dashboardData?.totalCount || 0"
|
<OverviewPanel :loading="isFirstLoading"
|
||||||
|
:totalCount="dashboardData?.totalCount || 0"
|
||||||
:formalEmployeeCount="dashboardData?.formalEmployeeCount || 0"
|
:formalEmployeeCount="dashboardData?.formalEmployeeCount || 0"
|
||||||
:externalStaffCount="dashboardData?.externalStaffCount || 0"
|
:externalStaffCount="dashboardData?.externalStaffCount || 0"
|
||||||
:visitorCount="dashboardData?.visitorCount || 0"
|
:visitorCount="dashboardData?.visitorCount || 0"
|
||||||
:parkStatistics="dashboardData?.parkStatistics"/>
|
:parkStatistics="dashboardData?.parkStatistics"/>
|
||||||
<RiskStatisticsPanel :riskStatistics="riskStatistics" :dangerDetail="dangerDetail"
|
<RiskStatisticsPanel :loading="isFirstLoading" :riskStatistics="riskStatistics" :dangerDetail="dangerDetail"
|
||||||
@tab-change="handleRiskTabChange" :campus_id="query.campus_id"/>
|
@tab-change="handleRiskTabChange" :campus_id="query.campus_id"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-wrapper">
|
<div class="right-wrapper">
|
||||||
<HighRiskAlertPanel :alertData="dashboardData?.alertData"
|
<HighRiskAlertPanel :loading="isFirstLoading"
|
||||||
|
:alertData="dashboardData?.alertData"
|
||||||
:alertDetails="dashboardData?.alertData.details"
|
:alertDetails="dashboardData?.alertData.details"
|
||||||
linkUrl="http://10.0.64.20/security/console/command-center?p=tabl"
|
linkUrl="http://10.0.64.20/security/console/command-center?p=tabl"
|
||||||
:sourceIndex="sourceIndex"/>
|
:sourceIndex="sourceIndex"/>
|
||||||
<TimeoutWorkOrderPanel :timeoutWorkOrders="dashboardData?.timeoutWorkOrders"
|
<TimeoutWorkOrderPanel :loading="isFirstLoading"
|
||||||
|
:timeoutWorkOrders="dashboardData?.timeoutWorkOrders"
|
||||||
:alertDetails="dashboardData?.timeoutWorkOrders.details"
|
:alertDetails="dashboardData?.timeoutWorkOrders.details"
|
||||||
linkUrl="http://10.0.64.20/pms/workorder-list"
|
linkUrl="http://10.0.64.20/pms/workorder-list"
|
||||||
:sourceIndex="sourceIndex"/>
|
:sourceIndex="sourceIndex"/>
|
||||||
</div>
|
</div>
|
||||||
<HiddenDangerPanel :hiddenDangerData="dashboardData?.hiddenDangerData"/>
|
<HiddenDangerPanel :loading="isFirstLoading" :hiddenDangerData="dashboardData?.hiddenDangerData"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -100,6 +103,9 @@ const riskStatistics = ref<any>({
|
|||||||
})
|
})
|
||||||
const dangerDetail = ref<any>()
|
const dangerDetail = ref<any>()
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
const isFirstLoading = ref<boolean>(true)
|
||||||
|
|
||||||
// 动画相关的状态
|
// 动画相关的状态
|
||||||
const isAnimating = ref<boolean>(false)
|
const isAnimating = ref<boolean>(false)
|
||||||
const animationDuration = 2000 // 动画持续时间(毫秒)
|
const animationDuration = 2000 // 动画持续时间(毫秒)
|
||||||
@@ -332,6 +338,11 @@ onUnmounted(() => {
|
|||||||
let isFirstLoad = ref<boolean>(true)
|
let isFirstLoad = ref<boolean>(true)
|
||||||
// 数据初始化方法
|
// 数据初始化方法
|
||||||
const loadDashboardData = async (): Promise<void> => {
|
const loadDashboardData = async (): Promise<void> => {
|
||||||
|
// 第一次加载时显示骨架屏
|
||||||
|
if (isFirstLoading.value) {
|
||||||
|
console.log('第一次加载,显示骨架屏');
|
||||||
|
}
|
||||||
|
|
||||||
const data = await getDashboardData()
|
const data = await getDashboardData()
|
||||||
if (isFirstLoad.value) {
|
if (isFirstLoad.value) {
|
||||||
console.log('第一次加载');
|
console.log('第一次加载');
|
||||||
@@ -340,8 +351,11 @@ const loadDashboardData = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
console.log('dashboardData.value>>>>>>>>>>', dashboardData.value);
|
console.log('dashboardData.value>>>>>>>>>>', dashboardData.value);
|
||||||
|
|
||||||
try {
|
// 收集所有异步请求
|
||||||
|
const promises = []
|
||||||
|
|
||||||
// 获取总体概览数据
|
// 获取总体概览数据
|
||||||
|
promises.push(
|
||||||
getTableList('generalTotal', query).then(generalTotal => {
|
getTableList('generalTotal', query).then(generalTotal => {
|
||||||
if (generalTotal.records && generalTotal.records.length > 0) {
|
if (generalTotal.records && generalTotal.records.length > 0) {
|
||||||
dashboardData.value.totalCount = Number(generalTotal.records[0].totalCount)
|
dashboardData.value.totalCount = Number(generalTotal.records[0].totalCount)
|
||||||
@@ -355,13 +369,13 @@ const loadDashboardData = async (): Promise<void> => {
|
|||||||
external: Number(dashboardData.value.externalStaffCount),
|
external: Number(dashboardData.value.externalStaffCount),
|
||||||
visitor: Number(dashboardData.value.visitorCount)
|
visitor: Number(dashboardData.value.visitorCount)
|
||||||
})
|
})
|
||||||
})
|
}).catch(error => {
|
||||||
} catch (error) {
|
|
||||||
console.error('获取总体概览数据失败:', error)
|
console.error('获取总体概览数据失败:', error)
|
||||||
}
|
})
|
||||||
|
)
|
||||||
|
|
||||||
try {
|
|
||||||
// 获取各园区统计数据
|
// 获取各园区统计数据
|
||||||
|
promises.push(
|
||||||
getTableList('parkscreen_user_info', query).then(parkscreen_user_info => {
|
getTableList('parkscreen_user_info', query).then(parkscreen_user_info => {
|
||||||
if (parkscreen_user_info.records && parkscreen_user_info.records.length > 0) {
|
if (parkscreen_user_info.records && parkscreen_user_info.records.length > 0) {
|
||||||
dashboardData.value.parkStatistics = parkscreen_user_info.records.map(el => {
|
dashboardData.value.parkStatistics = parkscreen_user_info.records.map(el => {
|
||||||
@@ -373,13 +387,13 @@ const loadDashboardData = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
}).catch(error => {
|
||||||
} catch (error) {
|
|
||||||
console.error('获取各园区统计数据失败:', error)
|
console.error('获取各园区统计数据失败:', error)
|
||||||
}
|
})
|
||||||
|
)
|
||||||
|
|
||||||
try {
|
|
||||||
// 获取风险预警数据
|
// 获取风险预警数据
|
||||||
|
promises.push(
|
||||||
getTableList('risk_alert_data', query).then(risk_alert_data => {
|
getTableList('risk_alert_data', query).then(risk_alert_data => {
|
||||||
if (risk_alert_data.records && risk_alert_data.records.length > 0) {
|
if (risk_alert_data.records && risk_alert_data.records.length > 0) {
|
||||||
dashboardData.value.alertData.total = risk_alert_data.records[0].total
|
dashboardData.value.alertData.total = risk_alert_data.records[0].total
|
||||||
@@ -390,12 +404,10 @@ const loadDashboardData = async (): Promise<void> => {
|
|||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error('获取风险预警数据失败:', error)
|
console.error('获取风险预警数据失败:', error)
|
||||||
})
|
})
|
||||||
} catch (error) {
|
)
|
||||||
console.error('获取风险预警数据失败:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 获取风险预警详情数据
|
// 获取风险预警详情数据
|
||||||
|
promises.push(
|
||||||
getTableList('risk_alert_detail', query).then(risk_alert_detail => {
|
getTableList('risk_alert_detail', query).then(risk_alert_detail => {
|
||||||
if (risk_alert_detail.records && risk_alert_detail.records.length > 0) {
|
if (risk_alert_detail.records && risk_alert_detail.records.length > 0) {
|
||||||
dashboardData.value.alertData.details = risk_alert_detail.records
|
dashboardData.value.alertData.details = risk_alert_detail.records
|
||||||
@@ -403,12 +415,10 @@ const loadDashboardData = async (): Promise<void> => {
|
|||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error('获取风险预警详情数据失败:', error)
|
console.error('获取风险预警详情数据失败:', error)
|
||||||
})
|
})
|
||||||
} catch (error) {
|
)
|
||||||
console.error('获取风险预警详情数据失败:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 获取超期工单数据
|
// 获取超期工单数据
|
||||||
|
promises.push(
|
||||||
getTableList('timeout_work_order', query).then(timeout_work_order => {
|
getTableList('timeout_work_order', query).then(timeout_work_order => {
|
||||||
if (timeout_work_order.records && timeout_work_order.records.length >= 0) {
|
if (timeout_work_order.records && timeout_work_order.records.length >= 0) {
|
||||||
dashboardData.value.timeoutWorkOrders.total = timeout_work_order.records.length
|
dashboardData.value.timeoutWorkOrders.total = timeout_work_order.records.length
|
||||||
@@ -417,180 +427,81 @@ const loadDashboardData = async (): Promise<void> => {
|
|||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error('获取超期工单数据失败:', error)
|
console.error('获取超期工单数据失败:', error)
|
||||||
})
|
})
|
||||||
} catch (error) {
|
)
|
||||||
console.error('获取超期工单数据失败:', error)
|
|
||||||
}
|
// 处理风险统计和隐患数据(这些是异步的)
|
||||||
handleRiskTabChange('安全类事项')
|
promises.push(
|
||||||
|
Promise.all([
|
||||||
|
handleRiskTabChange('安全类事项'),
|
||||||
handleHiddenDangerPannelData(query)
|
handleHiddenDangerPannelData(query)
|
||||||
console.log('dashboardData.value>>>>>>>>>>', dashboardData.value);
|
]).catch(error => {
|
||||||
|
console.error('处理风险统计和隐患数据失败:', error)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// 等待所有异步操作完成
|
||||||
|
try {
|
||||||
|
await Promise.all(promises)
|
||||||
|
console.log('所有数据加载完成')
|
||||||
|
|
||||||
|
// 第一次加载完成后,隐藏骨架屏
|
||||||
|
if (isFirstLoading.value) {
|
||||||
|
isFirstLoading.value = false
|
||||||
|
console.log('隐藏骨架屏')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('数据加载过程中出现错误:', error)
|
||||||
|
// 即使出错也要隐藏骨架屏,避免界面一直处于加载状态
|
||||||
|
if (isFirstLoading.value) {
|
||||||
|
isFirstLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleHiddenDangerPannelData = (query) => {
|
const handleHiddenDangerPannelData = async (query) => {
|
||||||
let _data = {
|
const promises = []
|
||||||
flag: false,
|
|
||||||
general: 0,
|
|
||||||
major: 0,
|
|
||||||
overdue: 0,
|
|
||||||
processed: 0,
|
|
||||||
processing: 0,
|
|
||||||
pending: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
let _data2 = {
|
// 获取隐患排查治理数据 - 系统数据
|
||||||
flag: false,
|
promises.push(
|
||||||
general: 0,
|
|
||||||
major: 0,
|
|
||||||
overdue: 0,
|
|
||||||
processed: 0,
|
|
||||||
processing: 0,
|
|
||||||
pending: 0
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// 获取隐患排查治理数据
|
|
||||||
getTableList('risk_level_count', query).then(res => {
|
getTableList('risk_level_count', query).then(res => {
|
||||||
if (res.records && res.records.length > 0) {
|
if (res.records && res.records.length > 0) {
|
||||||
_data.general = _data.general + Number(res.records[0].general_count)
|
dashboardData.value.hiddenDangerData.general = Number(res.records[0].general_count)
|
||||||
_data.major = _data.major + Number(res.records[0].major_count)
|
dashboardData.value.hiddenDangerData.major = Number(res.records[0].major_count)
|
||||||
// 获取隐患排查治理数据
|
}
|
||||||
getTableList('risk_status_count', query).then(res => {
|
return getTableList('risk_status_count', query)
|
||||||
|
}).then(res => {
|
||||||
if (res.records && res.records.length > 0) {
|
if (res.records && res.records.length > 0) {
|
||||||
// 接口返回的已经是百分比,直接使用
|
|
||||||
const record = res.records[0]
|
const record = res.records[0]
|
||||||
_data.overdue = Number(record.overdueCnt) || 0
|
|
||||||
_data.processed = Number(record.processedCnt) || 0
|
|
||||||
_data.processing = Number(record.processingCnt) || 0
|
|
||||||
_data.pending = 0 // 接口没有返回pending,设为0
|
|
||||||
_data.flag = true
|
|
||||||
|
|
||||||
console.log('risk_status_count 接口返回数据:', record)
|
|
||||||
console.log('处理后的 _data:', _data)
|
|
||||||
|
|
||||||
_data2.flag = false
|
|
||||||
if (_data2.flag) {
|
|
||||||
// 合并数据
|
|
||||||
console.log("请求系统和第三方成功,合并数据", _data, _data2);
|
|
||||||
let generalCnt = _data.general + _data2.general
|
|
||||||
let majorCnt = _data.major + _data2.major
|
|
||||||
dashboardData.value.hiddenDangerData.general = generalCnt
|
|
||||||
dashboardData.value.hiddenDangerData.major = majorCnt
|
|
||||||
|
|
||||||
// 如果第三方数据也是百分比,需要合并;否则使用系统数据
|
|
||||||
// 这里假设系统数据是百分比,第三方数据可能是数量或百分比
|
|
||||||
let overdueCnt, processedCnt, processingCnt, pendingCnt
|
|
||||||
if (_data2.overdue > 1 || _data2.processed > 1 || _data2.processing > 1) {
|
|
||||||
// 第三方数据可能是百分比,直接使用系统数据(因为系统数据更准确)
|
|
||||||
overdueCnt = _data.overdue.toFixed(2)
|
|
||||||
processedCnt = _data.processed.toFixed(2)
|
|
||||||
processingCnt = _data.processing.toFixed(2)
|
|
||||||
pendingCnt = _data.pending.toFixed(2)
|
|
||||||
} else {
|
|
||||||
// 第三方数据可能是数量,需要计算百分比
|
|
||||||
let totalCnt = generalCnt + majorCnt
|
|
||||||
overdueCnt = totalCnt > 0 ? ((_data.overdue + _data2.overdue) / totalCnt * 100).toFixed(2) : '0.00'
|
|
||||||
processedCnt = totalCnt > 0 ? ((_data.processed + _data2.processed) / totalCnt * 100).toFixed(2) : '0.00'
|
|
||||||
processingCnt = totalCnt > 0 ? ((_data.processing + _data2.processing) / totalCnt * 100).toFixed(2) : '0.00'
|
|
||||||
pendingCnt = totalCnt > 0 ? ((_data.pending + _data2.pending) / totalCnt * 100).toFixed(2) : '0.00'
|
|
||||||
}
|
|
||||||
|
|
||||||
dashboardData.value.hiddenDangerData.progress = {
|
dashboardData.value.hiddenDangerData.progress = {
|
||||||
overdue: overdueCnt,
|
overdue: Number(record.overdueCnt).toFixed(2),
|
||||||
processed: processedCnt,
|
processed: Number(record.processedCnt).toFixed(2),
|
||||||
processing: processingCnt,
|
processing: Number(record.processingCnt).toFixed(2),
|
||||||
pending: pendingCnt,
|
pending: '0.00'
|
||||||
}
|
|
||||||
console.log('合并后的 progress:', dashboardData.value.hiddenDangerData.progress)
|
|
||||||
} else {
|
|
||||||
console.log("请求系统成功,展示数据", _data, _data2);
|
|
||||||
dashboardData.value.hiddenDangerData.general = _data.general
|
|
||||||
dashboardData.value.hiddenDangerData.major = _data.major
|
|
||||||
|
|
||||||
// 接口返回的已经是百分比,直接使用
|
|
||||||
dashboardData.value.hiddenDangerData.progress = {
|
|
||||||
overdue: _data.overdue.toFixed(2),
|
|
||||||
processed: _data.processed.toFixed(2),
|
|
||||||
processing: _data.processing.toFixed(2),
|
|
||||||
pending: _data.pending.toFixed(2),
|
|
||||||
}
|
|
||||||
console.log('系统数据 progress:', dashboardData.value.hiddenDangerData.progress)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('获取系统隐患数据失败:', error)
|
||||||
})
|
})
|
||||||
}
|
)
|
||||||
})
|
|
||||||
|
|
||||||
|
// 获取第三方隐患排查治理数据
|
||||||
// 获取隐患排查治理数据
|
promises.push(
|
||||||
getTableList('hidden_danger_investigation', query).then(res => {
|
getTableList('hidden_danger_investigation', query).then(res => {
|
||||||
if (res.records && res.records.length > 0) {
|
if (res.records && res.records.length > 0) {
|
||||||
_data2.general = Number(res.records[0].general)
|
// 获取安全指数
|
||||||
_data2.major = Number(res.records[0].major)
|
return getTableList('hidden_danger_safety_index', query)
|
||||||
|
}
|
||||||
// 安全指数另算,再起一个报表
|
}).then(res => {
|
||||||
// dashboardData.value.hiddenDangerData.safetyIndex = res.records[0].safetyIndex
|
|
||||||
// 在这里添加获取安全指数的逻辑
|
|
||||||
getTableList('hidden_danger_safety_index', query).then(res => {
|
|
||||||
if (res.records && res.records.length > 0) {
|
if (res.records && res.records.length > 0) {
|
||||||
dashboardData.value.hiddenDangerData.safetyIndex = res.records[0].safetyIndex
|
dashboardData.value.hiddenDangerData.safetyIndex = res.records[0].safetyIndex
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error('获取隐患排查治理数据失败:', error)
|
console.error('获取第三方隐患数据失败:', error)
|
||||||
})
|
})
|
||||||
|
)
|
||||||
|
|
||||||
// 获取隐患排查治理处理进度数据
|
|
||||||
getTableList('hidden_danger_process_progress', query).then(res => {
|
|
||||||
// if (res.records && res.records.length > 0) {
|
|
||||||
// _data2.flag = true
|
|
||||||
// _data2.overdue = Number(res.records[0].overdue) / 100 * (_data2.general + _data2.major)
|
|
||||||
// _data2.processed = Number(res.records[0].processed) / 100 * (_data2.general + _data2.major)
|
|
||||||
// _data2.processing = Number(res.records[0].processing) / 100 * (_data2.general + _data2.major)
|
|
||||||
// _data2.pending = Number(res.records[0].pending) / 100 * (_data2.general + _data2.major)
|
|
||||||
|
|
||||||
// if (_data.flag) {
|
|
||||||
// console.log("请求第三方和系统成功,合并数据", _data, _data2);
|
|
||||||
// // 合并数据
|
|
||||||
// let generalCnt = _data.general + _data2.general
|
|
||||||
// let majorCnt = _data.major + _data2.major
|
|
||||||
// dashboardData.value.hiddenDangerData.general = generalCnt
|
|
||||||
// dashboardData.value.hiddenDangerData.major = majorCnt
|
|
||||||
|
|
||||||
// let totalCnt = generalCnt + majorCnt
|
|
||||||
// let overdueCnt = ((_data.overdue + _data2.overdue) / totalCnt * 100).toFixed(2)
|
|
||||||
// let processedCnt = ((_data.processed + _data2.processed) / totalCnt * 100).toFixed(2)
|
|
||||||
// let processingCnt = ((_data.processing + _data2.processing) / totalCnt * 100).toFixed(2)
|
|
||||||
// let pendingCnt = ((_data.pending + _data2.pending) / totalCnt * 100).toFixed(2)
|
|
||||||
// dashboardData.value.hiddenDangerData.progress = {
|
|
||||||
// overdue: overdueCnt,
|
|
||||||
// processed: processedCnt,
|
|
||||||
// processing: processingCnt,
|
|
||||||
// pending: pendingCnt,
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// //显示三方数据
|
|
||||||
// console.log("请求第三方成功,展示数据", _data, _data2);
|
|
||||||
// dashboardData.value.hiddenDangerData.general = _data2.general
|
|
||||||
// dashboardData.value.hiddenDangerData.major = _data2.major
|
|
||||||
|
|
||||||
// dashboardData.value.hiddenDangerData.progress = {
|
|
||||||
// overdue: res.records[0].overdue,
|
|
||||||
// processed: res.records[0].processed,
|
|
||||||
// processing: res.records[0].processing,
|
|
||||||
// pending: res.records[0].pending,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('获取隐患排查治理处理进度数据失败:', error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('获取隐患排查治理数据失败:', error)
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取隐患排查治理数据失败:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 获取隐患排查治理TOP3类型数据
|
// 获取隐患排查治理TOP3类型数据
|
||||||
|
promises.push(
|
||||||
getTableList('hidden_danger_top', query).then(hidden_danger_top => {
|
getTableList('hidden_danger_top', query).then(hidden_danger_top => {
|
||||||
if (hidden_danger_top.records && hidden_danger_top.records.length > 0) {
|
if (hidden_danger_top.records && hidden_danger_top.records.length > 0) {
|
||||||
dashboardData.value.hiddenDangerData.top3Types = hidden_danger_top.records
|
dashboardData.value.hiddenDangerData.top3Types = hidden_danger_top.records
|
||||||
@@ -598,9 +509,9 @@ const handleHiddenDangerPannelData = (query) => {
|
|||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error('获取隐患排查治理TOP3类型数据失败:', error)
|
console.error('获取隐患排查治理TOP3类型数据失败:', error)
|
||||||
})
|
})
|
||||||
} catch (error) {
|
)
|
||||||
console.error('获取隐患排查治理TOP3类型数据失败:', error)
|
|
||||||
}
|
return Promise.all(promises)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理风险统计tab切换
|
// 处理风险统计tab切换
|
||||||
|
|||||||
Reference in New Issue
Block a user