大屏优化

This commit is contained in:
chenlin
2026-01-16 13:12:03 +08:00
parent 96d93c867e
commit c7ff1047e7
4 changed files with 271 additions and 129 deletions

View File

@@ -34,7 +34,10 @@
</div>
<span>总计</span>
<div class="number-wrapper">
<span class="total-number" v-for="(digit, index) in totalCountDigits" :key="index">
<div v-if="isFirstLoading" class="skeleton-number-wrapper">
<div v-for="i in 4" :key="i" class="skeleton-number skeleton-pulse"></div>
</div>
<span v-else class="total-number" v-for="(digit, index) in totalCountDigits" :key="index">
{{ digit }}
</span>
</div>
@@ -47,9 +50,14 @@
<span>正式员工</span>
</div>
<div class="type-number-wrapper" :style="{ width: `${maxNumberWidth}px` }">
<span class="type-number" v-for="(digit, index) in formalEmployeeDigits" :key="index">
{{ digit }}
</span>
<div v-if="isFirstLoading" class="skeleton-type-number-wrapper">
<div v-for="i in formalEmployeeDigits.length" :key="i" class="skeleton-type-number skeleton-pulse"></div>
</div>
<div v-else class="type-number-wrapper">
<span class="type-number" v-for="(digit, index) in formalEmployeeDigits" :key="index">
{{ digit }}
</span>
</div>
</div>
<span></span>
</div>
@@ -59,9 +67,14 @@
<span>外协人员</span>
</div>
<div class="type-number-wrapper" :style="{ width: `${maxNumberWidth}px` }">
<span class="type-number" v-for="(digit, index) in externalStaffDigits" :key="index">
{{ digit }}
</span>
<div v-if="isFirstLoading" class="skeleton-type-number-wrapper">
<div v-for="i in externalStaffDigits.length" :key="i" class="skeleton-type-number skeleton-pulse"></div>
</div>
<div v-else class="type-number-wrapper">
<span class="type-number" v-for="(digit, index) in externalStaffDigits" :key="index">
{{ digit }}
</span>
</div>
</div>
<span></span>
</div>
@@ -71,9 +84,14 @@
<span>访客</span>
</div>
<div class="type-number-wrapper" :style="{ width: `${maxNumberWidth}px` }">
<span class="type-number" v-for="(digit, index) in visitorDigits" :key="index">
{{ digit }}
</span>
<div v-if="isFirstLoading" class="skeleton-type-number-wrapper">
<div v-for="i in visitorDigits.length" :key="i" class="skeleton-type-number skeleton-pulse"></div>
</div>
<div v-else class="type-number-wrapper">
<span class="type-number" v-for="(digit, index) in visitorDigits" :key="index">
{{ digit }}
</span>
</div>
</div>
<span></span>
</div>
@@ -88,12 +106,14 @@
<div class="type-wrapper">
<div class="type-item">
<span class="type-btn yellow">重大</span>
<span class="type-num cursor-pointer" @click="handleSeverityCountClick">{{
<div v-if="isFirstLoading" class="type-num skeleton-pulse"></div>
<span v-else class="type-num cursor-pointer" @click="handleSeverityCountClick">{{
mockData.hiddenDangerData.severityCount }}</span>
</div>
<div class="type-item">
<span class="type-btn green">一般</span>
<span class="type-num cursor-pointer" @click="handleGeneralCountClick">{{
<div v-if="isFirstLoading" class="type-num skeleton-pulse"></div>
<span v-else class="type-num cursor-pointer" @click="handleGeneralCountClick">{{
mockData.hiddenDangerData.generalCount }}</span>
</div>
<!-- <div class="type-item">
@@ -103,6 +123,7 @@
</div>
<div class="hazard-wrapper">
<!-- <div v-if="isFirstLoading" class="skeleton-chart skeleton-pulse"></div> -->
<div ref="progressChartRef" class="progress-chart"></div>
<div class="progress-legend">
<div class="legend-item"><span class="dot red"></span>已逾期</div>
@@ -165,7 +186,12 @@
<img width="50%" style="margin: 8px 0" src="@/assets/images/line_1.png" />
</div>
</div>
<AlertList maxHeight="40vh" style="margin-left: 1vw;" :table-title="tableTitle" :list-data="examList" />
<AlertList
maxHeight="40vh"
style="margin-left: 1vw;"
:table-title="tableTitle"
:list-data="examList"
/>
</template>
<template v-if="activeTab === '应急预案及演练'">
<div class="bottom-card">
@@ -178,7 +204,7 @@
:list-data="drillList" />
</template>
</div> -->
<RiskStatisticsPanel :riskStatistics="riskStatistics" :dangerDetail="dangerDetail" :park="parkValue"
<RiskStatisticsPanel :loading="isFirstLoading" :riskStatistics="riskStatistics" :dangerDetail="dangerDetail" :park="parkValue"
@tab-change="handleRiskTabChange" :campus_id="query.campus_id" />
</div>
</div>
@@ -192,34 +218,39 @@
<div class="tip-container">
<div class="tip-image">
<img src="@/assets/images/screen/circle_image.png" width="80" height="80" />
<span class="number">{{ mockData.alertData.total }}</span>
<div v-if="isFirstLoading" style="width: 20px; height: 20px" class="number skeleton-pulse"></div>
<span v-else class="number">{{ mockData.alertData.total }}</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;">{{ mockData.alertData.total }}</span>
<div v-if="isFirstLoading" style="width: 20px; height: 20px;" class="skeleton-pulse"></div>
<span v-else style="font-size: 1.2rem; marker-start: 2vw; color: yellow;">{{ mockData.alertData.total }}</span>
</div>
<div class="col-item">
<span>已处理</span>
<span style="font-size: 1.2rem; marker-start: 2vw; color: greenyellow;">{{ mockData.alertData.processed
<div v-if="isFirstLoading" style="width: 20px; height: 20px;" class="skeleton-pulse"></div>
<span v-else style="font-size: 1.2rem; marker-start: 2vw; color: greenyellow;">{{ mockData.alertData.processed
}}</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;">{{ mockData.alertData.pending
<div v-if="isFirstLoading" style="width: 20px; height: 20px;" class="skeleton-pulse"></div>
<span v-else style="font-size: 1.2rem; marker-start: 2vw; color: yellow;">{{ mockData.alertData.pending
}}</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;">{{ mockData.alertData.processing
<div v-if="isFirstLoading" style="width: 20px; height: 20px;" class="skeleton-pulse"></div>
<span v-else style="font-size: 1.2rem; marker-start: 2vw; color: yellow;">{{ mockData.alertData.processing
}}</span>
</div>
</div>
</div>
<div style=" display: flex; width: 100%;margin-top: 1vw; flex: 1; justify-content: flex-end;">
<AlertList linkUrl="http://10.0.64.20/security/console/command-center?p=tabl" style="margin-right: 1vw;" title="告警详情" :list-data="mockData.alertData.details" />
<AlertList :loading="isFirstLoading" linkUrl="http://10.0.64.20/security/console/command-center?p=tabl" style="margin-right: 1vw;" title="告警详情" :list-data="mockData.alertData.details" />
</div>
</div>
<!-- 右下区域 -->
@@ -229,20 +260,30 @@
<div class="tip-container">
<div class="tip-image">
<img src="@/assets/images/screen/circle_image.png" width="80" height="80" />
<span class="number">{{ mockData.timeoutWorkOrders.total }}</span>
<div v-if="isFirstLoading" style="width: 20px; height: 20px" class="number skeleton-pulse"></div>
<span v-else class="number">{{ mockData.timeoutWorkOrders.total }}</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;">{{ mockData.timeoutWorkOrders.total
}}</span>
<div v-if="isFirstLoading" style="width: 20px; height: 20px;" class="skeleton-pulse"></div>
<span
v-else
style="font-size: 1.2rem; marker-start: 2vw; color: red;"
>{{ mockData.timeoutWorkOrders.total }}</span>
</div>
</div>
</div>
<div style=" display: flex; width: 100%;margin-top: 1vw; flex: 1; justify-content: flex-end;">
<AlertList linkUrl="http://10.0.64.20/pms/workorder-list" style="margin-right: 1vw;" title="工单详情" :list-data="mockData.timeoutWorkOrders.details" />
<AlertList
:loading="isFirstLoading"
linkUrl="http://10.0.64.20/pms/workorder-list"
style="margin-right: 1vw;"
title="工单详情"
:list-data="mockData.timeoutWorkOrders.details"
/>
</div>
</div>
</div>
@@ -261,6 +302,9 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
// 创建 AbortController 用于取消请求
const abortController = new AbortController()
import * as echarts from 'echarts'
import ParkCenter from './components/ParkCenter.vue'
import PointInfoPopup from './components/PointInfoPopup.vue'
@@ -304,6 +348,9 @@ const currentTime = ref<string>('')
const activeTab = ref<string>('高危作业')
const showPointPopup = ref(false)
const currentPoint = ref<PointPosition | null>(null)
// 首次加载状态
const isFirstLoading = ref<boolean>(true)
const progressChart = ref<echarts.ECharts | null>(null)
const progressChartRef = ref<HTMLElement | null>(null)
const timeUpdateTimerId = ref<ReturnType<typeof setInterval> | null>(null)
@@ -351,11 +398,11 @@ const handleRiskTabChange = async (tab: TabType) => {
// 同时获取维保任务和巡检任务的数据
const [maintenanceResponse, inspectionResponse] = await Promise.all([
getWorkOrderStatistics({ workOrderType, taskType: '维保任务', campus_id: query.campus_id }).catch(error => {
getWorkOrderStatistics({ workOrderType, taskType: '维保任务', campus_id: query.campus_id }, { signal: abortController.signal }).catch(error => {
console.error('获取维保任务数据失败:', error)
return { records: [] }
}),
getWorkOrderStatistics({ workOrderType, taskType: '巡检任务', campus_id: query.campus_id }).catch(error => {
getWorkOrderStatistics({ workOrderType, taskType: '巡检任务', campus_id: query.campus_id }, { signal: abortController.signal }).catch(error => {
console.error('获取巡检任务数据失败:', error)
return { records: [] }
})
@@ -752,55 +799,70 @@ onMounted(async () => {
updateTime()
timeUpdateTimerId.value = setInterval(updateTime, 1000)
if (typeof route.query.parkName === 'string') {
selectedPark.value = route.query.parkName
query.campus_id = route.query.parkCode as string
}
handleRiskTabChange('安全类事项')
// 先检查缓存
const cachedRecords = getCachedRegionOption()
let records = cachedRecords
if (!records || records.length === 0) {
// 缓存不存在或已过期,调用接口
try {
let result = await getTableList('park_info_list')
records = result.records || []
if (records && records.length > 0) {
// 保存到缓存
setCachedRegionOption(records)
}
} catch (error) {
console.error('初始化园区数据失败:', error)
records = []
try {
if (typeof route.query.parkName === 'string') {
selectedPark.value = route.query.parkName
query.campus_id = route.query.parkCode as string
}
}
if (records && records.length > 0) {
// 更新为新的数据格式
regionOption.value = records.map(el => ({
name: el.park_name,
code: el.park_code,
pic_url: el.pic_url
}))
handleRiskTabChange('安全类事项')
for (let i = 0; i < regionOption.value.length; i++) {
const el = regionOption.value[i];
if (el.code == query.campus_id && el.pic_url) {
backgroundImage.value = el.pic_url
// 先检查缓存
const cachedRecords = getCachedRegionOption()
let records = cachedRecords
if (!records || records.length === 0) {
// 缓存不存在或已过期,调用接口
try {
let result = await getTableList('park_info_list', {}, false, { signal: abortController.signal })
records = result.records || []
if (records && records.length > 0) {
// 保存到缓存
setCachedRegionOption(records)
}
} catch (error) {
console.error('初始化园区数据失败:', error)
records = []
}
}
}
// 初始化数据
await loadDashboardData()
if (records && records.length > 0) {
// 更新为新的数据格式
regionOption.value = records.map(el => ({
name: el.park_name,
code: el.park_code,
pic_url: el.pic_url
}))
for (let i = 0; i < regionOption.value.length; i++) {
const el = regionOption.value[i];
if (el.code == query.campus_id && el.pic_url) {
backgroundImage.value = el.pic_url
}
}
}
// 初始化数据
await loadDashboardData()
// 添加周期性动画演示
dashboardTimerId.value = setInterval(async () => {
await loadDashboardData()
try {
await loadDashboardData()
} catch (error) {
console.error('定时数据更新失败:', error)
}
}, 2 * 60 * 1000) // 每2分钟更新一次
} catch (error) {
console.error('页面初始化失败:', error)
// 即使出错也要隐藏骨架屏,避免界面一直处于加载状态
setTimeout(() => {
if (isFirstLoading.value) {
isFirstLoading.value = false
console.log('因错误隐藏骨架屏')
}
}, 1000)
}
})
const loadDashboardData = () => {
@@ -808,7 +870,7 @@ const loadDashboardData = () => {
isFirstLoad.value = false
}
// 获取总体概览数据
getTableList('generalTotal', query).then(generalTotal => {
getTableList('generalTotal', query, false, { signal: abortController.signal }).then(generalTotal => {
if (generalTotal.records && generalTotal.records.length > 0) {
mockData.totalCount = Number(generalTotal.records[0].totalCount)
mockData.formalEmployeeCount = Number(generalTotal.records[0].formalEmployeeCount)
@@ -916,7 +978,7 @@ const loadDashboardData = () => {
handleTabClick("高危作业")
// 获取风险预警数据
getTableList('risk_alert_data', query).then(risk_alert_data => {
getTableList('risk_alert_data', query, false, { signal: abortController.signal }).then(risk_alert_data => {
if (risk_alert_data.records && risk_alert_data.records.length > 0) {
mockData.alertData.total = risk_alert_data.records[0].total
mockData.alertData.processed = risk_alert_data.records[0].processed
@@ -927,7 +989,7 @@ const loadDashboardData = () => {
console.error('获取风险预警数据失败:', error)
})
// 获取风险预警详情数据
getTableList('risk_alert_detail', query).then(risk_alert_detail => {
getTableList('risk_alert_detail', query, false, { signal: abortController.signal }).then(risk_alert_detail => {
if (risk_alert_detail.records && risk_alert_detail.records.length > 0) {
mockData.alertData.details = risk_alert_detail.records
}
@@ -936,7 +998,7 @@ const loadDashboardData = () => {
})
// 获取超期工单数据
getTableList('timeout_work_order', query).then(timeout_work_order => {
getTableList('timeout_work_order', query, false, { signal: abortController.signal }).then(timeout_work_order => {
if (timeout_work_order.records && timeout_work_order.records.length >= 0) {
mockData.timeoutWorkOrders.total = timeout_work_order.records.length
mockData.timeoutWorkOrders.details = timeout_work_order.records
@@ -946,7 +1008,7 @@ const loadDashboardData = () => {
})
// 获取超期工单数据
getTableList('park_build_info', query).then(res => {
getTableList('park_build_info', query, false, { signal: abortController.signal }).then(res => {
if (res.records && res.records.length > 0) {
}
@@ -954,11 +1016,22 @@ const loadDashboardData = () => {
console.error('获取超期工单数据失败:', error)
})
// 调用数据处理函数
handleHiddenDangerPannelData(query)
// 延迟一段时间等待异步操作完成,然后隐藏骨架屏
setTimeout(() => {
if (isFirstLoading.value) {
isFirstLoading.value = false
console.log('隐藏骨架屏')
}
}, 2000) // 2秒后隐藏骨架屏
}
const handleHiddenDangerPannelData = (query) => {
const handleHiddenDangerPannelData = async (query) => {
const promises = []
let _data = {
flag: false,
general: 0,
@@ -978,6 +1051,8 @@ const handleHiddenDangerPannelData = (query) => {
processing: 0,
pending: 0
}
// 初始化图表
progressChart.value = echarts.init(progressChartRef.value)
const width = progressChart.value.getWidth()
const progressOption = {
@@ -1030,12 +1105,12 @@ const handleHiddenDangerPannelData = (query) => {
}
try {
// 获取隐患排查治理数据
getTableList('risk_level_count', query).then(res => {
getTableList('risk_level_count', query, false, { signal: abortController.signal }).then(res => {
if (res.records && res.records.length > 0) {
_data.general = _data.general + Number(res.records[0].general_count)
_data.major = _data.major + Number(res.records[0].major_count)
// 获取隐患排查治理数据
getTableList('risk_status_count', query).then(res => {
getTableList('risk_status_count', query, false, { signal: abortController.signal }).then(res => {
if (res.records && res.records.length > 0) {
// 接口返回的已经是百分比,直接使用
const record = res.records[0]
@@ -1109,13 +1184,13 @@ const handleHiddenDangerPannelData = (query) => {
// 获取隐患排查治理数据
getTableList('hidden_danger_investigation', query).then(res => {
getTableList('hidden_danger_investigation', query, false, { signal: abortController.signal }).then(res => {
if (res.records && res.records.length > 0) {
_data2.general = Number(res.records[0].general)
_data2.major = Number(res.records[0].major)
// 获取隐患排查治理处理进度数据
getTableList('hidden_danger_process_progress', query).then(res => {
getTableList('hidden_danger_process_progress', query, false, { signal: abortController.signal }).then(res => {
// if (res.records && res.records.length > 0) {
// _data2.flag = true
// _data2.overdue = Number(res.records[0].overdue) / 100 * (_data2.general + _data2.major)
@@ -1190,12 +1265,12 @@ const handleTabClick = async (tab: string) => {
code = "fire_drill"
code1 = "fire_drill_detail"
getDangerDetail(query.campus_id).then(res => {
getDangerDetail(query.campus_id, { signal: abortController.signal }).then(res => {
const list = res.records.map((item: any) => ({ description: item.contenttext }))
dangerList.value = list
})
getDangerCount(query.campus_id).then(res => {
getDangerCount(query.campus_id, { signal: abortController.signal }).then(res => {
unfinishedCount.value = res.records[0].wks
inProgressCount.value = res.records[0].jxz
finishedCount.value = res.records[0].ywc
@@ -1206,7 +1281,7 @@ const handleTabClick = async (tab: string) => {
code = 'security_training_count'
code1 = 'security_training_detail'
const res = await getExamDetail(query.campus_id)
const res = await getExamDetail(query.campus_id, { signal: abortController.signal })
examList.value = res.records
@@ -1215,7 +1290,7 @@ const handleTabClick = async (tab: string) => {
code = 'security_training_count'
code1 = 'security_training_detail'
const res1 = await getDrillDetail(query.campus_id)
const res1 = await getDrillDetail(query.campus_id, { signal: abortController.signal })
drillList.value = res1.records
@@ -1363,6 +1438,9 @@ const updateTime = (): void => {
}
onUnmounted(() => {
// 取消所有正在进行的请求
abortController.abort()
if (dashboardTimerId.value) {
clearInterval(dashboardTimerId.value)
dashboardTimerId.value = null
@@ -2075,4 +2153,44 @@ onUnmounted(() => {
}
}
}
// 骨架屏样式
.skeleton-pulse {
animation: skeleton-loading-company 1.5s ease-in-out infinite;
background-color: #444;
border-radius: 4px;
}
.skeleton-number-wrapper {
display: flex;
gap: 2px;
}
.skeleton-number {
width: 14px;
height: 24px;
}
.skeleton-type-number {
width: 14px;
height: 16px;
}
.skeleton-chart {
width: 100%;
height: 100%;
min-height: 200px;
}
@keyframes skeleton-loading-company {
0% {
opacity: 1;
}
50% {
opacity: 0.4;
}
100% {
opacity: 1;
}
}
</style>

View File

@@ -55,6 +55,9 @@
import {getTableList, getTableData, getWorkOrderStatistics} from './report'
import {ref, reactive, onMounted, watch, onUnmounted} from 'vue'
import {useRouter} from 'vue-router'
// 创建 AbortController 用于取消请求
const abortController = new AbortController()
import HeaderSelector from './components/HeaderSelector.vue'
import WeatherWarning from './components/WeatherWarning.vue'
import {getDashboardData, getAlertDetails, type DashboardData} from '@/api/dashboard'
@@ -270,7 +273,7 @@ onMounted(async () => {
if (!records || records.length === 0) {
// 缓存不存在或已过期,调用接口
try {
let result = await getTableList('park_info_list')
let result = await getTableList('park_info_list', {}, false, { signal: abortController.signal })
records = result.records || []
if (records && records.length > 0) {
@@ -320,8 +323,11 @@ onMounted(async () => {
}, 2 * 60 * 1000) // 每2分钟更新一次
})
// 组件卸载时清理定时器
// 组件卸载时清理定时器并取消所有请求
onUnmounted(() => {
// 取消所有正在进行的请求
abortController.abort()
if (dashboardTimerId.value) {
clearInterval(dashboardTimerId.value)
dashboardTimerId.value = null
@@ -356,7 +362,7 @@ const loadDashboardData = async (): Promise<void> => {
// 获取总体概览数据
promises.push(
getTableList('generalTotal', query).then(generalTotal => {
getTableList('generalTotal', query, false, { signal: abortController.signal }).then(generalTotal => {
if (generalTotal.records && generalTotal.records.length > 0) {
dashboardData.value.totalCount = Number(generalTotal.records[0].totalCount)
dashboardData.value.formalEmployeeCount = Number(generalTotal.records[0].formalEmployeeCount)
@@ -376,7 +382,7 @@ const loadDashboardData = async (): Promise<void> => {
// 获取各园区统计数据
promises.push(
getTableList('parkscreen_user_info', query).then(parkscreen_user_info => {
getTableList('parkscreen_user_info', query, false, { signal: abortController.signal }).then(parkscreen_user_info => {
if (parkscreen_user_info.records && parkscreen_user_info.records.length > 0) {
dashboardData.value.parkStatistics = parkscreen_user_info.records.map(el => {
return {
@@ -394,7 +400,7 @@ const loadDashboardData = async (): Promise<void> => {
// 获取风险预警数据
promises.push(
getTableList('risk_alert_data', query).then(risk_alert_data => {
getTableList('risk_alert_data', query, false, { signal: abortController.signal }).then(risk_alert_data => {
if (risk_alert_data.records && risk_alert_data.records.length > 0) {
dashboardData.value.alertData.total = risk_alert_data.records[0].total
dashboardData.value.alertData.processed = risk_alert_data.records[0].processed
@@ -408,7 +414,7 @@ const loadDashboardData = async (): Promise<void> => {
// 获取风险预警详情数据
promises.push(
getTableList('risk_alert_detail', query).then(risk_alert_detail => {
getTableList('risk_alert_detail', query, false, { signal: abortController.signal }).then(risk_alert_detail => {
if (risk_alert_detail.records && risk_alert_detail.records.length > 0) {
dashboardData.value.alertData.details = risk_alert_detail.records
}
@@ -419,7 +425,7 @@ const loadDashboardData = async (): Promise<void> => {
// 获取超期工单数据
promises.push(
getTableList('timeout_work_order', query).then(timeout_work_order => {
getTableList('timeout_work_order', query, false, { signal: abortController.signal }).then(timeout_work_order => {
if (timeout_work_order.records && timeout_work_order.records.length >= 0) {
dashboardData.value.timeoutWorkOrders.total = timeout_work_order.records.length
dashboardData.value.timeoutWorkOrders.details = timeout_work_order.records
@@ -463,12 +469,12 @@ const handleHiddenDangerPannelData = async (query) => {
// 获取隐患排查治理数据 - 系统数据
promises.push(
getTableList('risk_level_count', query).then(res => {
getTableList('risk_level_count', query, false, { signal: abortController.signal }).then(res => {
if (res.records && res.records.length > 0) {
dashboardData.value.hiddenDangerData.general = Number(res.records[0].general_count)
dashboardData.value.hiddenDangerData.major = Number(res.records[0].major_count)
}
return getTableList('risk_status_count', query)
return getTableList('risk_status_count', query, false, { signal: abortController.signal })
}).then(res => {
if (res.records && res.records.length > 0) {
const record = res.records[0]
@@ -486,10 +492,10 @@ const handleHiddenDangerPannelData = async (query) => {
// 获取第三方隐患排查治理数据
promises.push(
getTableList('hidden_danger_investigation', query).then(res => {
getTableList('hidden_danger_investigation', query, false, { signal: abortController.signal }).then(res => {
if (res.records && res.records.length > 0) {
// 获取安全指数
return getTableList('hidden_danger_safety_index', query)
return getTableList('hidden_danger_safety_index', query, false, { signal: abortController.signal })
}
}).then(res => {
if (res.records && res.records.length > 0) {
@@ -502,7 +508,7 @@ const handleHiddenDangerPannelData = async (query) => {
// 获取隐患排查治理TOP3类型数据
promises.push(
getTableList('hidden_danger_top', query).then(hidden_danger_top => {
getTableList('hidden_danger_top', query, false, { signal: abortController.signal }).then(hidden_danger_top => {
if (hidden_danger_top.records && hidden_danger_top.records.length > 0) {
dashboardData.value.hiddenDangerData.top3Types = hidden_danger_top.records
}
@@ -544,7 +550,7 @@ const handleRiskTabChange = async (tab: TabType | '安全类事项' | '工程类
workOrderType,
taskType: '维保任务',
campus_id: query.campus_id
}).catch(error => {
}, { signal: abortController.signal }).catch(error => {
console.error('获取维保任务数据失败:', error)
return {records: []}
}),
@@ -552,7 +558,7 @@ const handleRiskTabChange = async (tab: TabType | '安全类事项' | '工程类
workOrderType,
taskType: '巡检任务',
campus_id: query.campus_id
}).catch(error => {
}, { signal: abortController.signal }).catch(error => {
console.error('获取巡检任务数据失败:', error)
return {records: []}
})

View File

@@ -26,26 +26,29 @@
<!-- 主内容区 -->
<div class="content-container">
<div class="left-wrapper">
<OverviewPanel :totalCount="dashboardData?.totalCount || 0"
<OverviewPanel :loading="isFirstLoading"
:totalCount="dashboardData?.totalCount || 0"
:formalEmployeeCount="dashboardData?.formalEmployeeCount || 0"
:externalStaffCount="dashboardData?.externalStaffCount || 0"
:visitorCount="dashboardData?.visitorCount || 0"
:parkStatistics="dashboardData?.parkStatistics"/>
<RiskStatisticsPanel :riskStatistics="riskStatistics" :dangerDetail="dangerDetail"
<RiskStatisticsPanel :loading="isFirstLoading" :riskStatistics="riskStatistics" :dangerDetail="dangerDetail"
:park="parkValue"
@tab-change="handleRiskTabChange" :campus_id="query.campus_id"/>
</div>
<div class="right-wrapper">
<HighRiskAlertPanel :alertData="dashboardData?.alertData"
<HighRiskAlertPanel :loading="isFirstLoading"
:alertData="dashboardData?.alertData"
:alertDetails="dashboardData?.alertData.details"
linkUrl="http://10.0.64.20/security/console/command-center?p=tabl"
:sourceIndex="sourceIndex"/>
<TimeoutWorkOrderPanel :timeoutWorkOrders="dashboardData?.timeoutWorkOrders"
<TimeoutWorkOrderPanel :loading="isFirstLoading"
:timeoutWorkOrders="dashboardData?.timeoutWorkOrders"
:alertDetails="dashboardData?.timeoutWorkOrders.details"
linkUrl="http://10.0.64.20/pms/workorder-list"
:sourceIndex="sourceIndex"/>
</div>
<HiddenDangerPanel :hiddenDangerData="dashboardData?.hiddenDangerData"/>
<HiddenDangerPanel :loading="isFirstLoading" :hiddenDangerData="dashboardData?.hiddenDangerData"/>
</div>
</div>
@@ -55,6 +58,9 @@
import {getTableList, getTableData, getWorkOrderStatistics} from './report'
import {ref, onMounted, watch, onUnmounted} from 'vue'
import {useRoute, useRouter} from 'vue-router'
// 创建 AbortController 用于取消请求
const abortController = new AbortController()
import HeaderSelector from './components/HeaderSelector.vue'
import WeatherWarning from './components/WeatherWarning.vue'
import {getDashboardData, getAlertDetails, type DashboardData} from '@/api/dashboard'
@@ -106,6 +112,9 @@ const dangerDetail = ref<any>()
const isAnimating = ref<boolean>(false)
const animationDuration = 2000 // 动画持续时间(毫秒)
// 首次加载状态
const isFirstLoading = ref<boolean>(true)
// 数字变化时的闪烁效果
const flashNumbers = () => {
const numberElements = document.querySelectorAll('.total-number, .type-number')
@@ -278,7 +287,7 @@ onMounted(async () => {
if (!records || records.length === 0) {
// 缓存不存在或已过期,调用接口
try {
let result = await getTableList('park_info_list')
let result = await getTableList('park_info_list', {}, false, { signal: abortController.signal })
records = result.records || []
if (records && records.length > 0) {
@@ -327,8 +336,11 @@ onMounted(async () => {
}, 2 * 60 * 1000) // 每2分钟更新一次
})
// 组件卸载时清理定时器
// 组件卸载时清理定时器并取消所有请求
onUnmounted(() => {
// 取消所有正在进行的请求
abortController.abort()
if (dashboardTimerId.value) {
clearInterval(dashboardTimerId.value)
dashboardTimerId.value = null
@@ -355,7 +367,7 @@ const loadDashboardData = async (): Promise<void> => {
try {
// 获取总体概览数据
getTableList('generalTotal', query).then(generalTotal => {
getTableList('generalTotal', query, false, { signal: abortController.signal }).then(generalTotal => {
if (generalTotal.records && generalTotal.records.length > 0) {
dashboardData.value.totalCount = generalTotal.records.reduce((sum, item) => sum + Number(item.totalCount || 0), 0)
dashboardData.value.formalEmployeeCount = generalTotal.records.reduce((sum, item) => sum + Number(item.formalEmployeeCount || 0), 0)
@@ -375,7 +387,7 @@ const loadDashboardData = async (): Promise<void> => {
try {
// 获取各园区统计数据
getTableList('parkscreen_user_info', query).then(parkscreen_user_info => {
getTableList('parkscreen_user_info', query, false, { signal: abortController.signal }).then(parkscreen_user_info => {
if (parkscreen_user_info.records && parkscreen_user_info.records.length > 0) {
dashboardData.value.parkStatistics = parkscreen_user_info.records.map(el => {
return {
@@ -393,7 +405,7 @@ const loadDashboardData = async (): Promise<void> => {
try {
// 获取风险预警数据
getTableList('risk_alert_data', query).then(risk_alert_data => {
getTableList('risk_alert_data', query, false, { signal: abortController.signal }).then(risk_alert_data => {
if (risk_alert_data.records && risk_alert_data.records.length > 0) {
dashboardData.value.alertData.total = risk_alert_data.records.reduce((sum, item) => sum + Number(item.total || 0), 0)
dashboardData.value.alertData.processed = risk_alert_data.records.reduce((sum, item) => sum + Number(item.processed || 0), 0)
@@ -409,7 +421,7 @@ const loadDashboardData = async (): Promise<void> => {
try {
// 获取风险预警详情数据
getTableList('risk_alert_detail', query).then(risk_alert_detail => {
getTableList('risk_alert_detail', query, false, { signal: abortController.signal }).then(risk_alert_detail => {
// if (risk_alert_detail.records && risk_alert_detail.records.length > 0) {
dashboardData.value.alertData.details = risk_alert_detail.records || []
// }
@@ -422,7 +434,7 @@ const loadDashboardData = async (): Promise<void> => {
try {
// 获取超期工单数据
getTableList('timeout_work_order', query).then(timeout_work_order => {
getTableList('timeout_work_order', query, false, { signal: abortController.signal }).then(timeout_work_order => {
if (timeout_work_order.records && timeout_work_order.records.length >= 0) {
dashboardData.value.timeoutWorkOrders.total = timeout_work_order.records.length
dashboardData.value.timeoutWorkOrders.details = timeout_work_order.records
@@ -461,12 +473,12 @@ const handleHiddenDangerPannelData = (query) => {
}
try {
// 获取隐患排查治理数据
getTableList('risk_level_count', query).then(res => {
getTableList('risk_level_count', query, false, { signal: abortController.signal }).then(res => {
if (res.records && res.records.length > 0) {
_data.general = _data.general + Number(res.records[0].general_count)
_data.major = _data.major + Number(res.records[0].major_count)
// 获取隐患排查治理数据
getTableList('risk_status_count', query).then(res => {
getTableList('risk_status_count', query, false, { signal: abortController.signal }).then(res => {
if (res.records && res.records.length > 0) {
// 接口返回的已经是百分比,直接使用
const record = res.records[0]
@@ -533,7 +545,7 @@ const handleHiddenDangerPannelData = (query) => {
// 获取隐患排查治理数据
getTableList('hidden_danger_investigation', query).then(res => {
getTableList('hidden_danger_investigation', query, false, { signal: abortController.signal }).then(res => {
if (res.records && res.records.length > 0) {
_data2.general = Number(res.records[0].general)
_data2.major = Number(res.records[0].major)
@@ -541,7 +553,7 @@ const handleHiddenDangerPannelData = (query) => {
// 安全指数另算,再起一个报表
// dashboardData.value.hiddenDangerData.safetyIndex = res.records[0].safetyIndex
// 在这里添加获取安全指数的逻辑
getTableList('hidden_danger_safety_index', query).then(res => {
getTableList('hidden_danger_safety_index', query, false, { signal: abortController.signal }).then(res => {
if (res.records && res.records.length > 0) {
dashboardData.value.hiddenDangerData.safetyIndex = res.records[0].safetyIndex
}
@@ -550,7 +562,7 @@ const handleHiddenDangerPannelData = (query) => {
})
// 获取隐患排查治理处理进度数据
getTableList('hidden_danger_process_progress', query).then(res => {
getTableList('hidden_danger_process_progress', query, false, { signal: abortController.signal }).then(res => {
// if (res.records && res.records.length > 0) {
// _data2.flag = true
// _data2.overdue = Number(res.records[0].overdue) / 100 * (_data2.general + _data2.major)
@@ -604,7 +616,7 @@ const handleHiddenDangerPannelData = (query) => {
try {
// 获取隐患排查治理TOP3类型数据
getTableList('hidden_danger_top', query).then(res => {
getTableList('hidden_danger_top', query, false, { signal: abortController.signal }).then(res => {
if (res.records && res.records.length > 0) {
dashboardData.value.hiddenDangerData.top3Types = res.records
}
@@ -639,7 +651,7 @@ const handleRiskTabChange = async (tab: TabType) => {
workOrderType,
taskType: '维保任务',
campus_id: query.campus_id
}).catch(error => {
}, { signal: abortController.signal }).catch(error => {
console.error('获取维保任务数据失败:', error)
return {records: []}
}),
@@ -647,7 +659,7 @@ const handleRiskTabChange = async (tab: TabType) => {
workOrderType,
taskType: '巡检任务',
campus_id: query.campus_id
}).catch(error => {
}, { signal: abortController.signal }).catch(error => {
console.error('获取巡检任务数据失败:', error)
return {records: []}
})
@@ -727,6 +739,12 @@ const handleRiskTabChange = async (tab: TabType) => {
} catch (error) {
console.error('获取风险统计数据失败:', error)
}
// 第一次加载完成后,隐藏骨架屏
if (isFirstLoading.value) {
isFirstLoading.value = false
console.log('隐藏骨架屏')
}
}
// 方法定义

View File

@@ -73,24 +73,24 @@ export const exportExcelData = (reportCode, data?) => {
}
//获取报表数据
export const getTableList = (reportCode, data?, isOpen?) => {
return request.post({ url: `/jeelowcode/${isOpen ? 'open/report' : 'report-data'}/list/${reportCode}`, data })
export const getTableList = (reportCode, data?, isOpen?, options = {}) => {
return request.post({ url: `/jeelowcode/${isOpen ? 'open/report' : 'report-data'}/list/${reportCode}`, data, ...options })
}
export const getDangerDetail = (campus_id: string) => {
return request.post({ url: '/jeelowcode/report-data/list/dp_yq_danger_detail', data: {campus_id} })
export const getDangerDetail = (campus_id: string, options = {}) => {
return request.post({ url: '/jeelowcode/report-data/list/dp_yq_danger_detail', data: {campus_id}, ...options })
}
export const getZBDangerSum = (campus_id: string) => {
return request.post({ url: '/jeelowcode/report-data/list/dp_zb_danger_sum', data: {campus_id} })
}
export const getDangerCount = (campus_id: string) => {
return request.post({ url: '/jeelowcode/report-data/list/dp_yq_danger_sum', data: {campus_id} })
export const getDangerCount = (campus_id: string, options = {}) => {
return request.post({ url: '/jeelowcode/report-data/list/dp_yq_danger_sum', data: {campus_id}, ...options })
}
export const getExamDetail = (campus_id: string) => {
return request.post({ url: '/jeelowcode/report-data/list/dp_yq_exam_detail', data: {campus_id} })
export const getExamDetail = (campus_id: string, options = {}) => {
return request.post({ url: '/jeelowcode/report-data/list/dp_yq_exam_detail', data: {campus_id}, ...options })
}
export const getExamSum = (campus_id: string) => {
@@ -101,13 +101,13 @@ export const getDrillSum = (campus_id: string) => {
return request.post({ url: '/jeelowcode/report-data/list/dp_zb_drill_sum', data: {campus_id} })
}
export const getDrillDetail = (campus_id: string) => {
return request.post({ url: '/jeelowcode/report-data/list/dp_yq_drill_detail', data: {campus_id} })
export const getDrillDetail = (campus_id: string, options = {}) => {
return request.post({ url: '/jeelowcode/report-data/list/dp_yq_drill_detail', data: {campus_id}, ...options })
}
//获取报表数据
export const getTableData = (tableId, data?) => {
return request.post({ url: `/jeelowcode/dbform-data/list/${tableId}`, data })
export const getTableData = (tableId, data?, options = {}) => {
return request.post({ url: `/jeelowcode/dbform-data/list/${tableId}`, data, ...options })
}
/**
@@ -135,8 +135,8 @@ export const batchGetTableList = (reportCodes: string, data?) => {
// 参数workOrderType'物业服务-工程'taskType'巡检任务'
// 统计周期 cycle: daymonthyear 对应月日年
export const getWorkOrderStatistics = (data: {workOrderType: string, taskType: string, campus_id?: string}) => {
return request.post({ url: '/jeelowcode/report-data/list/report_work_order_statistics', data: {...data, pageNo: 1, pageSize: 10} })
export const getWorkOrderStatistics = (data: {workOrderType: string, taskType: string, campus_id?: string}, options = {}) => {
return request.post({ url: '/jeelowcode/report-data/list/report_work_order_statistics', data: {...data, pageNo: 1, pageSize: 10}, ...options })
}