diff --git a/src/views/Home/Index10.vue b/src/views/Home/Index10.vue index dc99599..d86df45 100644 --- a/src/views/Home/Index10.vue +++ b/src/views/Home/Index10.vue @@ -2,12 +2,17 @@
-
-
- {{ currentView }} - ··· -
-
+
@@ -38,13 +43,30 @@
-
区域分布
-
-
- - {{ item.region }} - {{ item.count }}人 - ({{ item.percent }}) +
区域人数分布
+
+ +
+
+
+ {{ item.region }} +
+
+ +
+
人数
+
+ {{ item.count }}人 +
+
+ +
+
占比
+
+ {{ item.percent }} +
@@ -66,27 +88,46 @@
区域风险分布
-
- - - - - - - - - - - - - - - - - - - -
区域一般较大重大
{{ item.area }}{{ item.low || '0' }}{{ item.general || '0' }}{{ item.moderate || '0' }}{{ item.major || '0' }}
+
+ +
+
+
+ {{ item.area }} +
+
+ +
+
+
+ {{ item.low || '0' }} +
+
+ +
+
一般
+
+ {{ item.general || '0' }} +
+
+ +
+
较大
+
+ {{ item.moderate || '0' }} +
+
+ +
+
重大
+
+ {{ item.major || '0' }} +
+
@@ -103,11 +144,14 @@
- +
区域整改状态
-
+
+ 暂无数据 +
+
@@ -118,22 +162,25 @@
已逾期
-
- {{ item.overdue }} +
+ {{ item.overdue ?? 0 }}
处理中
-
- {{ item.processing }} +
+ {{ item.processing ?? 0 }}
已处理
-
- {{ item.processed }} +
+ {{ item.processed ?? 0 }}
@@ -251,9 +298,6 @@
- -
@@ -262,16 +306,16 @@ import { ref, computed, onMounted } from 'vue' import { useRouter, useRoute } from 'vue-router' import { Refresh } from '@element-plus/icons-vue' import Echart from '@/components/Echart/src/Echart.vue' -import RegionSelector from '@/views/screen/components/RegionSelector.vue' +import HeaderSelector from '@/views/screen/components/HeaderSelector.vue' import { getTableList } from '@/api/design/report' import type { EChartsOption } from 'echarts' import dayjs from 'dayjs' -import { - getHiddenDangerManagementData, - getHiddenDangerManagementDataWeek, +import { + getHiddenDangerManagementData, + getHiddenDangerManagementDataWeek, getHiddenDangerManagementDataMonth, - getOutsourcingManagementData, - getRiskManagementData, + getOutsourcingManagementData, + getRiskManagementData, getHighRiskManagementData, getEmergencyPlanManagementData, getTrainingManagementData @@ -307,9 +351,7 @@ const route = useRoute() // 区域选择相关 const currentView = ref('集团') -const regionSelectorVisible = ref(false) const selectedRegion = ref('') -const regionOption = ref([]) // 时间选择相关 - 默认当前月起止,如果路由中有日期范围参数则使用 const getCurrentMonthRange = () => { @@ -336,22 +378,23 @@ const dateRange = ref(getInitialDateRange()) // 应急预案:/yayl/table/view/1966394259751907330 // 安全培训:/pxks/table/view/1968225010550091777 const openOutsourcingManagement = () => { - window.open('/person/table/view/1959187451673116674', '_blank') + // 不打开新标签页 + router.push('/person/table/view/1959187451673116674') } const openRiskManagement = () => { - window.open('/fx/table/view/1978723750599790594', '_blank') + router.push('/fx/table/view/1978723750599790594') } const openHiddenDangerManagement = () => { - window.open('/fx/table/view/1963446160885366786', '_blank') + router.push('/fx/table/view/1963446160885366786') } const openHighRiskManagement = () => { - window.open('/low/table/view/1964253329070571521', '_blank') + router.push('/low/table/view/1964253329070571521') } const openEmergencyPlanManagement = () => { - window.open('/yayl/table/view/1966394259751907330', '_blank') + router.push('/yayl/table/view/1966394259751907330') } const openTrainingManagement = () => { - window.open('/pxks/table/view/1968225010550091777', '_blank') + router.push('/pxks/table/view/1968225010550091777') } // 外协管理数据 @@ -370,27 +413,27 @@ const initOutsourcingData = async () => { pageNo: 1, pageSize: 10000 }) - + console.log('外协管理接口返回:', response) - + // axios封装后,response就是 { code: 0, data: {...}, msg: "" } // 所以records在 response.data.records const records = response?.records || [] - + if (records && records.length > 0) { // 计算总人数(从records中累加total) const total = records.reduce((sum: number, item: any) => { return sum + Number(item.total || 0) }, 0) - + outsourcingTotal.value = total - + // 处理区域分布数据 outsourcingDistribution.value = records.map((item: any, index: number) => { const count = Number(item.total || 0) const percent = total > 0 ? ((count / total) * 100).toFixed(1) + '%' : '0%' const color = regionColors[index % regionColors.length] - + return { region: item.name, count, @@ -398,7 +441,7 @@ const initOutsourcingData = async () => { color } }) - + console.log('处理后的外协管理数据:', { total: outsourcingTotal.value, distribution: outsourcingDistribution.value @@ -472,7 +515,7 @@ const outsourcingChartOption = computed(() => { name: item.region, itemStyle: { color: item.color } })) - + // 如果没有数据,显示空状态 if (chartData.length === 0 || outsourcingTotal.value === 0) { return { @@ -480,6 +523,21 @@ const outsourcingChartOption = computed(() => { trigger: 'item', formatter: '{a}
{b}: {c} ({d}%)' }, + graphic: [ + { + type: 'text', + left: 'center', + top: 'center', + style: { + text: `${outsourcingTotal.value}\n外协人员总数`, + fontSize: 16, + fontWeight: 'bold', + fill: '#333', + textAlign: 'center', + textVerticalAlign: 'middle' + } + } + ], series: [ { name: '外协人员', @@ -493,14 +551,7 @@ const outsourcingChartOption = computed(() => { borderWidth: 0 }, label: { - show: true, - position: 'center', - formatter: () => { - return `${outsourcingTotal.value}\n外协人员总数` - }, - fontSize: 16, - fontWeight: 'bold', - color: '#333' + show: false }, data: [ { value: 1, name: '暂无数据', itemStyle: { color: '#e5e7eb' } } @@ -509,12 +560,27 @@ const outsourcingChartOption = computed(() => { ] } } - + return { tooltip: { trigger: 'item', formatter: '{a}
{b}: {c} ({d}%)' }, + graphic: [ + { + type: 'text', + left: 'center', + top: 'center', + style: { + text: `${outsourcingTotal.value}\n外协人员总数`, + fontSize: 16, + fontWeight: 'bold', + fill: '#333', + textAlign: 'center', + textVerticalAlign: 'middle' + } + } + ], series: [ { name: '外协人员', @@ -528,19 +594,12 @@ const outsourcingChartOption = computed(() => { borderWidth: 0 }, label: { - show: true, - position: 'center', - formatter: () => { - return `${outsourcingTotal.value}\n外协人员总数` - }, - fontSize: 16, - fontWeight: 'bold', - color: '#333' + show: false }, emphasis: { label: { show: true, - fontSize: 18, + fontSize: 14, fontWeight: 'bold' } }, @@ -552,40 +611,29 @@ const outsourcingChartOption = computed(() => { // 风险管理环形图配置 const riskChartOption = computed(() => { - const chartData = riskDistribution.value - .filter(item => item.count > 0) // 只显示有数据的风险等级 - .map(item => ({ - value: item.count, - name: item.level, - itemStyle: { color: item.color } - })) - - // 如果没有数据,显示空状态 - if (chartData.length === 0 || riskTotal.value === 0) { - return { - tooltip: { trigger: 'item', formatter: '{a}
{b}: {c} ({d}%)' }, - series: [{ - name: '风险', - type: 'pie', - radius: ['55%', '75%'], - center: ['50%', '50%'], - avoidLabelOverlap: false, - itemStyle: { borderRadius: 0, borderColor: 'transparent', borderWidth: 0 }, - label: { - show: true, - position: 'center', - formatter: () => `${riskTotal.value}\n风险总数`, - fontSize: 16, - fontWeight: 'bold', - color: '#333' - }, - data: [{ value: 1, name: '暂无数据', itemStyle: { color: '#e5e7eb' } }] - }] - } - } + const chartData = riskDistribution.value.map(item => ({ + value: item.count, + name: item.level, + itemStyle: { color: item.color } + })) return { tooltip: { trigger: 'item', formatter: '{a}
{b}: {c} ({d}%)' }, + graphic: [ + { + type: 'text', + left: 'center', + top: 'center', + style: { + text: `${riskTotal.value}\n风险总数`, + fontSize: 16, + fontWeight: 'bold', + fill: '#333', + textAlign: 'center', + textVerticalAlign: 'middle' + } + } + ], series: [{ name: '风险', type: 'pie', @@ -595,33 +643,62 @@ const riskChartOption = computed(() => { itemStyle: { borderRadius: 0, borderColor: 'transparent', borderWidth: 0 }, label: { show: true, - position: 'center', - formatter: () => `${riskTotal.value}\n风险总数`, - fontSize: 16, - fontWeight: 'bold', - color: '#333' + alignTo: 'edge', + formatter: (params: any) => { + const value = Number(params.value) || 0 + const total = Number(riskTotal.value) || 0 + if (total === 0) { + return '0%' + } + const percent = ((value / total) * 100).toFixed(2) + '%' + return percent + } + }, + labelLine: { + show: true, + }, + emphasis: { + label: { + show: true, + fontSize: 14, + fontWeight: 'bold' + } }, - emphasis: { label: { show: true, fontSize: 18, fontWeight: 'bold' } }, data: chartData }] } }) +// 隐患管理图表 key,用于强制刷新图表 +const hiddenDangerChartKey = computed(() => { + // 基于数据长度和内容生成 key,确保数据变化时图表能正确刷新 + const dataLength = hiddenDangerTrend.value.length + const dataHash = dataLength > 0 + ? JSON.stringify(hiddenDangerTrend.value.map(item => `${item.date}-${item.general}-${item.major}`)).slice(0, 50) + : 'empty' + return `hidden-danger-${dataLength}-${dataHash}` +}) + // 隐患管理折线图配置 const hiddenDangerChartOption = computed(() => { // 提取日期和对应的数据 - const dates = hiddenDangerTrend.value.map(item => item.date) - const generalData = hiddenDangerTrend.value.map(item => item.general) - const majorData = hiddenDangerTrend.value.map(item => item.major) - + const dates = hiddenDangerTrend.value.map(item => item.date).filter(date => date) // 过滤掉空日期 + const generalData = hiddenDangerTrend.value.map(item => item.general ?? 0) + const majorData = hiddenDangerTrend.value.map(item => item.major ?? 0) + + // 判断是否为空数据:只有当原始数据数组为空,或者所有数据值都为0时才认为为空 + const totalGeneral = generalData.reduce((sum, v) => sum + v, 0) + const totalMajor = majorData.reduce((sum, v) => sum + v, 0) + const isEmpty = hiddenDangerTrend.value.length === 0 || (totalGeneral === 0 && totalMajor === 0) + // 计算Y轴最大值(向上取整到最近的10的倍数) - const maxValue = Math.max( + const maxValue = isEmpty ? 10 : Math.max( ...generalData, ...majorData, 10 // 最小值为10,避免图表显示过小 ) const yAxisMax = Math.ceil(maxValue / 10) * 10 || 10 - + return { tooltip: { trigger: 'axis', @@ -646,18 +723,42 @@ const hiddenDangerChartOption = computed(() => { xAxis: { type: 'category', boundaryGap: false, - data: dates.length > 0 ? dates : [] + data: isEmpty ? [] : dates, + show: !isEmpty }, yAxis: { type: 'value', - max: yAxisMax + max: isEmpty ? 10 : yAxisMax, + show: !isEmpty }, - series: [ + graphic: isEmpty ? [ + { + type: 'group', + left: 'center', + top: 'center', + children: [ + { + type: 'text', + z: 100, + left: 'center', + top: 'center', + style: { + text: '暂无数据', + fontSize: 16, + fontWeight: 'normal', + fill: '#9ca3af', + textAlign: 'center' + } + } + ] + } + ] : [], + series: isEmpty ? [] : [ { name: '一般隐患', type: 'line', smooth: true, - data: generalData.length > 0 ? generalData : [], + data: generalData, itemStyle: { color: '#f59e0b' }, lineStyle: { color: '#f59e0b', width: 2 }, areaStyle: { @@ -678,7 +779,7 @@ const hiddenDangerChartOption = computed(() => { name: '重大隐患', type: 'line', smooth: true, - data: majorData.length > 0 ? majorData : [], + data: majorData, itemStyle: { color: '#ef4444' }, lineStyle: { color: '#ef4444', width: 2 }, areaStyle: { @@ -708,11 +809,26 @@ const highRiskChartOption = computed(() => { name: item.type, itemStyle: { color: item.color } })) - + // 如果没有数据,显示空状态 if (chartData.length === 0 || highRiskTotal.value === 0) { return { tooltip: { trigger: 'item', formatter: '{a}
{b}: {c} ({d}%)' }, + graphic: [ + { + type: 'text', + left: 'center', + top: 'center', + style: { + text: `${highRiskTotal.value}\n累计作业`, + fontSize: 15, + fontWeight: 'bold', + fill: '#333', + textAlign: 'center', + textVerticalAlign: 'middle' + } + } + ], series: [{ name: '高危作业', type: 'pie', @@ -721,20 +837,30 @@ const highRiskChartOption = computed(() => { avoidLabelOverlap: false, itemStyle: { borderRadius: 0, borderColor: 'transparent', borderWidth: 0 }, label: { - show: true, - position: 'center', - formatter: () => `${highRiskTotal.value}\n累计作业`, - fontSize: 15, - fontWeight: 'bold', - color: '#333' + show: false }, data: [{ value: 1, name: '暂无数据', itemStyle: { color: '#e5e7eb' } }] }] } } - + return { tooltip: { trigger: 'item', formatter: '{a}
{b}: {c} ({d}%)' }, + graphic: [ + { + type: 'text', + left: 'center', + top: 'center', + style: { + text: `${highRiskTotal.value}\n累计作业`, + fontSize: 15, + fontWeight: 'bold', + fill: '#333', + textAlign: 'center', + textVerticalAlign: 'middle' + } + } + ], series: [{ name: '高危作业', type: 'pie', @@ -743,14 +869,9 @@ const highRiskChartOption = computed(() => { avoidLabelOverlap: false, itemStyle: { borderRadius: 0, borderColor: 'transparent', borderWidth: 0 }, label: { - show: true, - position: 'center', - formatter: () => `${highRiskTotal.value}\n累计作业`, - fontSize: 15, - fontWeight: 'bold', - color: '#333' + show: false }, - emphasis: { label: { show: true, fontSize: 20, fontWeight: 'bold' } }, + emphasis: { label: { show: true, fontSize: 16, fontWeight: 'bold' } }, data: chartData }] } @@ -758,10 +879,10 @@ const highRiskChartOption = computed(() => { // 应急预案环形进度图配置 const emergencyPlanChartOption = computed(() => { - const percent = emergencyPlanTotal.value > 0 + const percent = emergencyPlanTotal.value > 0 ? ((emergencyPlanCompleted.value / emergencyPlanTotal.value) * 100).toFixed(0) : 0 - + return { tooltip: { trigger: 'item' }, series: [{ @@ -793,12 +914,12 @@ const safetyTrainingChartOption = computed(() => { const regions = trainingBarData.value.regions || [] const trainingCount = trainingBarData.value.trainingCount || [] const participants = trainingBarData.value.participants || [] - + // 计算堆叠后的最大值(培训次数 + 参与人次) const stackedValues = trainingCount.map((count, index) => count + (participants[index] || 0)) const maxValue = Math.max(...stackedValues, 10) // 最小值为10,避免图表显示过小 const yAxisMax = Math.ceil(maxValue / 10) * 10 || 10 - + return { tooltip: { trigger: 'axis', @@ -932,16 +1053,12 @@ const navigateToModule = (path: string): void => { router.push(path) } -const openRegionSelector = (): void => { - regionSelectorVisible.value = true -} - const onRegionChange = (item: RegionItem): void => { selectedRegion.value = item.name router.push({ path: '/region', - query: { - region: item.name, + query: { + region: item.name, regionCode: item.code, sDate: dateRange.value[0], eDate: dateRange.value[1] @@ -960,28 +1077,6 @@ const refreshData = async () => { initData() } -// 初始化区域数据 -const initRegionData = async () => { - try { - const { records } = await getTableList('park_info_list') - if (records && records.length > 0) { - // 去重region字段,使用Map来确保唯一性 - const regionMap = new Map() - records.forEach((el: any) => { - if (!regionMap.has(el.region)) { - regionMap.set(el.region, { - name: el.region, - code: el.region_id - }) - } - }) - // 转换为数组 - regionOption.value = Array.from(regionMap.values()) - } - } catch (error) { - console.error('初始化区域数据失败:', error) - } -} // 初始化风险管理数据 const initRiskData = async () => { @@ -992,28 +1087,28 @@ const initRiskData = async () => { pageNo: 1, pageSize: 10000 }) - + console.log('风险管理接口返回:', response) - + const records = response?.records || [] - + if (records && records.length > 0) { // 按风险等级分组统计,用于环形图 const levelMap = new Map() - + // 按区域和风险等级分组统计,用于表格 const areaLevelMap = new Map() - + records.forEach((item: any) => { const level = item.name || '' const area = item.area || '' const count = Number(item.total || 0) - + // 统计风险等级分布(用于环形图) if (level) { levelMap.set(level, (levelMap.get(level) || 0) + count) } - + // 统计区域风险分布(用于表格) if (area) { if (!areaLevelMap.has(area)) { @@ -1031,19 +1126,19 @@ const initRiskData = async () => { } } }) - + // 计算总数 const total = Array.from(levelMap.values()).reduce((sum, count) => sum + count, 0) riskTotal.value = total - + // 处理风险等级分布数据(用于环形图) const allLevels = [ - { key: '低', level: '低风险', count: 0, percent: '0%', color: '#10b981' }, - { key: '一般', level: '一般风险', count: 0, percent: '0%', color: '#f59e0b' }, - { key: '较大', level: '较大风险', count: 0, percent: '0%', color: '#ef4444' }, - { key: '重大', level: '重大风险', count: 0, percent: '0%', color: '#dc2626' } + { key: '低', level: '低风险', count: 0, percent: '0%', color: '#117cee' }, + { key: '一般', level: '一般风险', count: 0, percent: '0%', color: '#fbde28' }, + { key: '较大', level: '较大风险', count: 0, percent: '0%', color: '#ed740c' }, + { key: '重大', level: '重大风险', count: 0, percent: '0%', color: '#df2a3f' } ] - + riskDistribution.value = allLevels.map(defaultItem => { const count = levelMap.get(defaultItem.key) || levelMap.get(defaultItem.level) || 0 const percent = total > 0 ? ((count / total) * 100).toFixed(1) + '%' : '0%' @@ -1054,7 +1149,7 @@ const initRiskData = async () => { color: defaultItem.color } }) - + // 处理区域风险分布表 areaRiskDistribution.value = Array.from(areaLevelMap.entries()) .map(([area, data]) => ({ @@ -1070,7 +1165,7 @@ const initRiskData = async () => { const totalB = (typeof b.low === 'number' ? b.low : 0) + b.general + b.moderate + b.major return totalB - totalA }) - + console.log('处理后的风险管理数据:', { total: riskTotal.value, levelDistribution: riskDistribution.value, @@ -1080,10 +1175,10 @@ const initRiskData = async () => { // 如果没有数据,设置为默认值 riskTotal.value = 0 riskDistribution.value = [ - { level: '低风险', count: 0, percent: '0%', color: '#10b981' }, - { level: '一般风险', count: 0, percent: '0%', color: '#f59e0b' }, - { level: '较大风险', count: 0, percent: '0%', color: '#ef4444' }, - { level: '重大风险', count: 0, percent: '0%', color: '#dc2626' } + { level: '低风险', count: 0, percent: '0%', color: '#117cee' }, + { level: '一般风险', count: 0, percent: '0%', color: '#fbde28' }, + { level: '较大风险', count: 0, percent: '0%', color: '#ed740c' }, + { level: '重大风险', count: 0, percent: '0%', color: '#df2a3f' } ] areaRiskDistribution.value = [] console.log('风险管理无数据') @@ -1093,10 +1188,10 @@ const initRiskData = async () => { // 如果接口失败,设置为默认值 riskTotal.value = 0 riskDistribution.value = [ - { level: '低风险', count: 0, percent: '0%', color: '#10b981' }, - { level: '一般风险', count: 0, percent: '0%', color: '#f59e0b' }, - { level: '较大风险', count: 0, percent: '0%', color: '#ef4444' }, - { level: '重大风险', count: 0, percent: '0%', color: '#dc2626' } + { level: '低风险', count: 0, percent: '0%', color: '#117cee' }, + { level: '一般风险', count: 0, percent: '0%', color: '#fbde28' }, + { level: '较大风险', count: 0, percent: '0%', color: '#ed740c' }, + { level: '重大风险', count: 0, percent: '0%', color: '#df2a3f' } ] areaRiskDistribution.value = [] } @@ -1107,7 +1202,7 @@ const getHiddenDangerApi = (startDate: string, endDate: string) => { const start = dayjs(startDate) const end = dayjs(endDate) const daysDiff = end.diff(start, 'day') + 1 // 包含起始和结束日期 - + // 如果日期范围 <= 7天,使用"天"接口 if (daysDiff <= 7) { return getHiddenDangerManagementData @@ -1126,32 +1221,32 @@ const initDangerData = async () => { try { // 根据日期范围选择接口 const apiFunc = getHiddenDangerApi(dateRange.value[0], dateRange.value[1]) - + const response = await apiFunc({ sDate: dateRange.value[0], eDate: dateRange.value[1], pageNo: 1, pageSize: 10000 }) - + console.log('隐患管理接口返回:', response) - + const records = response?.records || [] - + if (records && records.length > 0) { // 按日期和等级分组统计,用于折线图 const trendMap = new Map() - + // 按区域和状态分组统计,用于整改状态表格 const areaStatusMap = new Map() - + records.forEach((item: any) => { const dayname = item.dayName || item.dayname || '' const level = item.name || '' const status = item.status || '' const area = item.area || '' const count = Number(item.total || 0) - + // 统计趋势数据(按日期和等级) if (dayname) { if (!trendMap.has(dayname)) { @@ -1164,7 +1259,7 @@ const initDangerData = async () => { trend.major += count } } - + // 统计区域整改状态数据 if (area) { if (!areaStatusMap.has(area)) { @@ -1180,7 +1275,7 @@ const initDangerData = async () => { } } }) - + // 转换为数组并按日期排序 hiddenDangerTrend.value = Array.from(trendMap.entries()) .map(([date, counts]) => ({ date, ...counts })) @@ -1193,7 +1288,7 @@ const initDangerData = async () => { const dayB = parseInt(b.date.replace('日', '')) || 0 return (numA || dayA) - (numB || dayB) }) - + // 转换为区域整改状态数组 areaRectificationStatus.value = Array.from(areaStatusMap.entries()) .map(([area, status]) => ({ @@ -1208,7 +1303,7 @@ const initDangerData = async () => { const totalB = b.overdue + b.processing + b.processed return totalB - totalA }) - + console.log('处理后的隐患管理数据:', { trend: hiddenDangerTrend.value, areaStatus: areaRectificationStatus.value @@ -1235,38 +1330,38 @@ const initHighRiskData = async () => { pageNo: 1, pageSize: 10000 }) - + console.log('高危作业接口返回:', response) - + const records = response?.records || [] - + if (records && records.length > 0) { // 按作业类型分组统计,用于环形图 const typeMap = new Map() - + // 按区域分组统计,用于区域分布列表 const areaMap = new Map() - + records.forEach((item: any) => { const itemType = item.item || '' const area = item.area || '' const count = Number(item.total || 0) - + // 统计作业类型 if (itemType) { typeMap.set(itemType, (typeMap.get(itemType) || 0) + count) } - + // 统计区域分布 if (area) { areaMap.set(area, (areaMap.get(area) || 0) + count) } }) - + // 计算总数 const total = Array.from(typeMap.values()).reduce((sum, count) => sum + count, 0) highRiskTotal.value = total - + // 处理作业类型分布数据 operationTypeDistribution.value = Array.from(typeMap.entries()) .map(([type, count]) => ({ @@ -1276,7 +1371,7 @@ const initHighRiskData = async () => { color: operationTypeColors[type] || '#9ca3af' // 如果没有配置颜色,使用灰色 })) .sort((a, b) => b.count - a.count) // 按数量降序排序 - + // 处理区域分布数据 operationDistribution.value = Array.from(areaMap.entries()) .map(([region, count]) => ({ @@ -1284,7 +1379,7 @@ const initHighRiskData = async () => { count })) .sort((a, b) => b.count - a.count) // 按数量降序排序 - + console.log('处理后的高危作业数据:', { total: highRiskTotal.value, typeDistribution: operationTypeDistribution.value, @@ -1314,19 +1409,19 @@ const initEmergencyPlanData = async () => { pageNo: 1, pageSize: 10000 }) - + console.log('应急预案接口返回:', response) - + const records = response?.records || [] - + if (records && records.length > 0) { // 统计应完成演练总数(所有记录的total总和) const total = records.reduce((sum: number, item: any) => { return sum + Number(item.total || 0) }, 0) - + emergencyPlanTotal.value = total - + // 统计已完成演练数(根据状态判断) // 可能的完成状态:已完成、已完成演练、已执行等 const completedCount = records.reduce((sum: number, item: any) => { @@ -1338,18 +1433,18 @@ const initEmergencyPlanData = async () => { } return sum }, 0) - + emergencyPlanCompleted.value = completedCount - + // 按区域统计演练完成率 const areaMap = new Map() - + records.forEach((item: any) => { const area = item.area || '' const count = Number(item.total || 0) const status = item.status || '' const isCompleted = status.includes('完成') || status.includes('已执行') - + if (area) { if (!areaMap.has(area)) { areaMap.set(area, { total: 0, completed: 0 }) @@ -1361,11 +1456,11 @@ const initEmergencyPlanData = async () => { } } }) - + // 转换为数组并计算完成率 regionalDrillProgress.value = Array.from(areaMap.entries()) .map(([region, data]) => { - const percent = data.total > 0 + const percent = data.total > 0 ? ((data.completed / data.total) * 100).toFixed(0) + '%' : '0%' return { region, percent } @@ -1376,7 +1471,7 @@ const initEmergencyPlanData = async () => { const percentB = parseFloat(b.percent) return percentB - percentA }) - + console.log('处理后的应急预案数据:', { total: emergencyPlanTotal.value, completed: emergencyPlanCompleted.value, @@ -1406,25 +1501,25 @@ const initTrainingData = async () => { pageNo: 1, pageSize: 10000 }) - + console.log('安全培训接口返回:', response) - + const records = response?.records || [] - + if (records && records.length > 0) { // 按区域分组统计 const areaMap = new Map() - + records.forEach((item: any) => { // 只统计有区域字段的记录 if (!item.area) { return } - + const area = item.area const trainingCount = Number(item.plannum || 0) // 计划数量作为培训次数 const participants = Number(item.exenum || 0) // 执行数量作为参与人次 - + if (!areaMap.has(area)) { areaMap.set(area, { trainingCount: 0, participants: 0 }) } @@ -1432,7 +1527,7 @@ const initTrainingData = async () => { areaData.trainingCount += trainingCount areaData.participants += participants }) - + // 转换为数组并排序 const areaDataArray = Array.from(areaMap.entries()) .map(([region, data]) => ({ @@ -1447,20 +1542,20 @@ const initTrainingData = async () => { // 按培训次数降序排序 return b.trainingCount - a.trainingCount }) - + // 更新柱状图数据 trainingBarData.value = { regions: areaDataArray.map(item => item.region), trainingCount: areaDataArray.map(item => item.trainingCount), participants: areaDataArray.map(item => item.participants) } - + // 更新区域培训完成率数据 regionalTrainingProgress.value = areaDataArray.map(item => ({ region: item.region, percent: item.percent })) - + console.log('处理后的安全培训数据:', { barData: trainingBarData.value, regionalProgress: regionalTrainingProgress.value @@ -1488,9 +1583,8 @@ const initTrainingData = async () => { } const initData = async () => { - await initRegionData() initOutsourcingData() - + initRiskData() initDangerData() initHighRiskData() @@ -1544,15 +1638,15 @@ onMounted(async () => { color: white; user-select: none; font-weight: 600; - span { - margin-left: 8px; - font-size: 18px; - color: #6b7280; - } + gap: 5px; &:hover { - background: #e5e7eb; - border-color: #d1d5db; + background: #2563eb; + } + + span { + font-size: 18px; + line-height: 1; } &:active { @@ -1620,16 +1714,17 @@ onMounted(async () => { } @media (max-width: 1200px) { + .high-risk-top, .emergency-plan-top { gap: 10px; } - + .donut-chart-wrapper-small, .progress-chart-wrapper { min-width: 160px; } - + .operation-type-list, .drill-info { min-width: 140px; @@ -1638,12 +1733,13 @@ onMounted(async () => { } @media (max-width: 768px) { + .high-risk-top, .emergency-plan-top { flex-direction: column; align-items: stretch; } - + .donut-chart-wrapper-small, .progress-chart-wrapper { min-width: 100%; @@ -1651,7 +1747,7 @@ onMounted(async () => { height: 220px; min-height: 220px; } - + .operation-type-list, .drill-info { min-width: 100%; @@ -1842,6 +1938,14 @@ onMounted(async () => { font-weight: 600; color: #374151; margin-bottom: 12px; + display: flex; + align-items: center; + justify-content: space-between; + span { + font-size: 14px; + font-weight: 600; + color: #374151; + } } .distribution-list { @@ -1885,55 +1989,62 @@ onMounted(async () => { // 网格样式 .rectification-status-grid { + .empty-data-tip { + text-align: center; + padding: 40px 0; + color: #9ca3af; + font-size: 14px; + } + .grid-wrapper { display: flex; gap: 0; justify-content: flex-start; } - + .grid-column { display: flex; flex-direction: column; align-items: center; min-width: 0; - + &:first-child { align-items: flex-start; margin-right: 20px; flex: 0 0 auto; min-width: 80px; } - + &:not(:first-child) { flex: 1; min-width: 60px; } } - + .grid-header { font-size: 14px; font-weight: bold; margin-bottom: 12px; height: 20px; line-height: 20px; - + &.empty-header { visibility: hidden; } - + &.status-overdue { color: #ef4444; } - + &.status-processing { color: #f59e0b; } - + &.status-processed { color: #10b981; } } - + .grid-park-name { font-size: 13px; color: #333; @@ -1941,12 +2052,12 @@ onMounted(async () => { text-align: left; height: 24px; line-height: 24px; - + &:last-child { margin-bottom: 0; } } - + .grid-number { font-size: 16px; font-weight: 500; @@ -1954,51 +2065,188 @@ onMounted(async () => { text-align: center; height: 24px; line-height: 24px; - + &:last-child { margin-bottom: 0; } - + &.status-overdue { color: #ef4444; } - + &.status-processing { color: #f59e0b; } - + &.status-processed { color: #10b981; } } } -.table-wrapper { - overflow-x: auto; +// 风险管理网格样式(与隐患管理一致) +.risk-distribution-table { + .grid-wrapper { + display: flex; + gap: 0; + justify-content: flex-start; + } + + .grid-column { + display: flex; + flex-direction: column; + align-items: center; + min-width: 0; + + &:first-child { + align-items: flex-start; + margin-right: 20px; + flex: 0 0 auto; + min-width: 80px; + } + + &:not(:first-child) { + flex: 1; + min-width: 60px; + } + } + + .grid-header { + font-size: 14px; + font-weight: bold; + margin-bottom: 12px; + height: 20px; + line-height: 20px; + + &.empty-header { + visibility: hidden; + } + + &.status-low { + color: #117cee; + } + + &.status-general { + color: #fbde28; + } + + &.status-moderate { + color: #ed740c; + } + + &.status-major { + color: #df2a3f; + } + } + + .grid-park-name { + font-size: 13px; + color: #333; + margin-bottom: 8px; + text-align: left; + height: 24px; + line-height: 24px; + + &:last-child { + margin-bottom: 0; + } + } + + .grid-number { + font-size: 16px; + font-weight: 500; + margin-bottom: 8px; + text-align: center; + height: 24px; + line-height: 24px; + + &:last-child { + margin-bottom: 0; + } + + &.status-low { + color: #117cee; + } + + &.status-general { + color: #fbde28; + } + + &.status-moderate { + color: #ed740c; + } + + &.status-major { + color: #df2a3f; + } + } } -.risk-table { - width: 100%; - border-collapse: collapse; - font-size: 13px; - - thead { - background-color: #f9fafb; +// 外协管理网格样式(与隐患管理一致) +.region-distribution { + .grid-wrapper { + display: flex; + gap: 0; + justify-content: flex-start; } - - th, td { - padding: 8px; - text-align: left; - border-bottom: 1px solid #e5e7eb; + + .grid-column { + display: flex; + flex-direction: column; + align-items: center; + min-width: 0; + + &:first-child { + align-items: flex-start; + margin-right: 20px; + flex: 0 0 auto; + min-width: 80px; + } + + &:not(:first-child) { + flex: 1; + min-width: 60px; + } } - - th { + + .grid-header { + font-size: 14px; font-weight: bold; - color: #333; + margin-bottom: 12px; + height: 20px; + line-height: 20px; + color: #374151; + + &.empty-header { + visibility: hidden; + } } - - td { - color: #666; + + .grid-park-name { + font-size: 13px; + color: #333; + margin-bottom: 8px; + text-align: left; + height: 24px; + line-height: 24px; + + &:last-child { + margin-bottom: 0; + } + } + + .grid-number { + font-size: 16px; + font-weight: 500; + margin-bottom: 8px; + text-align: center; + height: 24px; + line-height: 24px; + color: #1f2937; + + &:last-child { + margin-bottom: 0; + } } } @@ -2069,7 +2317,7 @@ onMounted(async () => { box-shadow: 0 2px 4px rgba(139, 92, 246, 0.3); position: relative; overflow: hidden; - + &::after { content: ''; position: absolute; @@ -2077,12 +2325,10 @@ onMounted(async () => { left: 0; bottom: 0; right: 0; - background: linear-gradient( - 90deg, - transparent 0%, - rgba(255, 255, 255, 0.3) 50%, - transparent 100% - ); + background: linear-gradient(90deg, + transparent 0%, + rgba(255, 255, 255, 0.3) 50%, + transparent 100%); animation: shimmer 2s infinite; } } @@ -2101,6 +2347,7 @@ onMounted(async () => { 0% { transform: translateX(-100%); } + 100% { transform: translateX(100%); } diff --git a/src/views/Home/Index12.vue b/src/views/Home/Index12.vue index f531164..8a90408 100644 --- a/src/views/Home/Index12.vue +++ b/src/views/Home/Index12.vue @@ -2,15 +2,19 @@
-
- - - -
- {{ selectedRegion }} - ··· -
-
+
@@ -41,16 +45,33 @@
-
园区分布
-
-
- - {{ item.region }} - {{ item.count }}人 - ({{ item.percent }}) -
+
园区人数分布
+
+ +
+
+
+ {{ item.region }}
+
+ +
+
人数
+
+ {{ item.count }}人
+
+ +
+
占比
+
+ {{ item.percent }} +
+
+
+
@@ -69,28 +90,47 @@
园区风险分布
-
- - - - - - - - - - - - - - - - - - - -
园区一般较大重大
{{ item.park }}{{ item.low || '' }}{{ item.general }}{{ item.moderate }}{{ item.major }}
-
+
+ +
+
+
+ {{ item.park }} +
+
+ +
+
+
+ {{ item.low || '0' }} +
+
+ +
+
一般
+
+ {{ item.general || '0' }} +
+
+ +
+
较大
+
+ {{ item.moderate || '0' }} +
+
+ +
+
重大
+
+ {{ item.major || '0' }} +
+
+
@@ -106,11 +146,14 @@
- -
+ +
园区整改状态
-
+
+ 暂无数据 +
+
@@ -122,21 +165,21 @@
已逾期
- {{ item.overdue }} + {{ item.overdue ?? 0 }}
处理中
- {{ item.processing }} + {{ item.processing ?? 0 }}
已处理
- {{ item.processed }} + {{ item.processed ?? 0 }}
@@ -254,16 +297,13 @@
- -
+ + + diff --git a/src/views/screen/components/HiddenDangerPanel.vue b/src/views/screen/components/HiddenDangerPanel.vue index d44b3d6..b048e5b 100644 --- a/src/views/screen/components/HiddenDangerPanel.vue +++ b/src/views/screen/components/HiddenDangerPanel.vue @@ -1,5 +1,5 @@ diff --git a/src/views/screen/components/HighRiskAlertPanel.vue b/src/views/screen/components/HighRiskAlertPanel.vue index cc59a32..cb588fb 100644 --- a/src/views/screen/components/HighRiskAlertPanel.vue +++ b/src/views/screen/components/HighRiskAlertPanel.vue @@ -4,29 +4,37 @@
+ + +
- {{ alertData?.total || 0 }} +
+ {{ alertData?.total || 0 }}
告警总数 - {{ alertData?.total || 0 }} +
+ {{ alertData?.total || 0 }}
已处理 - {{ alertData?.processed || 0 }} +
+ {{ alertData?.processed || 0 }}
待处理 - {{ alertData?.pending || 0 }} +
+ {{ alertData?.pending || 0 }}
处理中 - {{ alertData?.processing }} +
+ {{ alertData?.processing }}
@@ -45,7 +53,7 @@
--> - +
@@ -73,6 +81,7 @@ interface Props { alertDetails?: AlertItem[] sourceIndex?: number linkUrl?: string + loading?: boolean } // 默认值 @@ -84,7 +93,8 @@ const props = withDefaults(defineProps(), { processing: 0 }), alertDetails: () => [], - sourceIndex: 1 + sourceIndex: 1, + loading: false }) @@ -265,5 +275,104 @@ const props = withDefaults(defineProps(), { } } } + + // 骨架屏样式 + .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; + } + } } diff --git a/src/views/screen/components/OverviewPanel.vue b/src/views/screen/components/OverviewPanel.vue index a29c5d6..8ddf39b 100644 --- a/src/views/screen/components/OverviewPanel.vue +++ b/src/views/screen/components/OverviewPanel.vue @@ -3,7 +3,34 @@
人员管理
-
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
@@ -19,9 +46,11 @@
- - 正式员工 -
+
+ + 正式员工 +
+
{{ digit }} @@ -30,9 +59,11 @@
- - 外协人员 -
+
+ + 外协人员 +
+
{{ digit }} @@ -41,9 +72,11 @@
- - 访客 -
+
+ + 访客 +
+
{{ digit }} @@ -58,6 +91,7 @@ 各园区统计
+
@@ -68,6 +102,7 @@ import { ref, onMounted, watch, computed } from 'vue' import { rgbToHex } from '@/utils/color' interface Props { + loading?: boolean totalCount: number formalEmployeeCount: number externalStaffCount: number @@ -87,6 +122,17 @@ const formalEmployeeDigits = computed(() => String(props.formalEmployeeCount).sp const externalStaffDigits = computed(() => String(props.externalStaffCount).split('').map(Number)) const visitorDigits = computed(() => String(props.visitorCount).split('').map(Number)) +// 计算数字区域的最大宽度 +// 每个数字框宽度 14px,gap 2px +const maxNumberWidth = computed(() => { + const formalLen = formalEmployeeDigits.value.length + const externalLen = externalStaffDigits.value.length + const visitorLen = visitorDigits.value.length + const maxLen = Math.max(formalLen, externalLen, visitorLen) + // 宽度 = 数字个数 * 14px + (数字个数 - 1) * 2px + return maxLen > 0 ? maxLen * 14 + (maxLen - 1) * 2 : 0 +}) + // 图表引用 const barChartOption = ref({ legend: { @@ -243,17 +289,25 @@ onMounted(() => { .top-card-right-item { display: flex; align-items: center; - column-gap: 5px; padding: 0 10px; font-size: 0.7rem; color: #fff; + > :first-child { + display: flex; + align-items: center; + column-gap: 5px; + width: 100px; + } + .type-number-wrapper { display: flex; align-items: center; + justify-content: flex-start; gap: 2px; font-size: 0.8rem; color: #fff; + margin-left: 8px; .type-number { display: inline-block; @@ -268,6 +322,10 @@ onMounted(() => { transition: all 0.3s ease; } } + + > span:last-child { + margin-left: 4px; + } } } } @@ -290,5 +348,141 @@ onMounted(() => { 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; + } + } } diff --git a/src/views/screen/components/RiskStatisticsPanel.vue b/src/views/screen/components/RiskStatisticsPanel.vue index 6216185..f43cdb6 100644 --- a/src/views/screen/components/RiskStatisticsPanel.vue +++ b/src/views/screen/components/RiskStatisticsPanel.vue @@ -2,14 +2,38 @@
- 安全类 + 安全类事项 | - 工程类 + 工程类事项
-
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
{{ item.title }}
@@ -40,7 +64,7 @@
-
+
@@ -49,7 +73,7 @@ import { computed, ref, watch } from 'vue' import { ElTooltip } from 'element-plus' import type { EChartsOption } from 'echarts' -type TabType = '安全类' | '工程类' +type TabType = '安全类事项' | '工程类事项' type StatusKey = 'notStarted' | 'inProgress' | 'done' | 'voided' interface ChartItem { @@ -67,12 +91,12 @@ const statusList: { key: StatusKey; label: string; color: string }[] = [ ] const defaultChart: ChartItem[] = [ - { title: '每日检查(维保类)', total: 6, rate: 0, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } }, - { title: '每月检查(维保类)', total: 6, rate: 0, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } }, - { title: '每年检查(维保类)', total: 6, rate: 0, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } }, - { title: '每日检查(巡检类)', total: 6, rate: 0, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } }, - { title: '每月检查(巡检类)', total: 6, rate: 0, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } }, - { title: '每年检查(巡检类)', total: 6, rate: 0, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } } + { title: '当日维保', total: 6, rate: 0, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } }, + { title: '本月维保', total: 6, rate: 0, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } }, + { title: '本年维保', total: 6, rate: 0, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } }, + { title: '当日巡检', total: 6, rate: 0, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } }, + { title: '本月巡检', total: 6, rate: 0, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } }, + { title: '本年巡检', total: 6, rate: 0, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } } ] const handleChartTitleClick = () => { @@ -80,23 +104,24 @@ const handleChartTitleClick = () => { } const tabCharts = ref>({ - 安全类: [...defaultChart], - 工程类: [...defaultChart] + 安全类事项: [...defaultChart], + 工程类事项: [...defaultChart] }) const props = defineProps<{ + loading?: boolean riskStatistics?: Record }>() -const activeTab = ref('安全类') +const activeTab = ref('安全类事项') const emit = defineEmits<{ tabChange: [tab: TabType] }>() // 监听props变化,更新图表数据 watch(() => props.riskStatistics, (newData) => { if (newData) { tabCharts.value = { - 安全类: newData['安全类'] ? JSON.parse(JSON.stringify(newData['安全类'])) : [...defaultChart], - 工程类: newData['工程类'] ? JSON.parse(JSON.stringify(newData['工程类'])) : [...defaultChart] + 安全类事项: newData['安全类事项'] ? JSON.parse(JSON.stringify(newData['安全类事项'])) : [...defaultChart], + 工程类事项: newData['工程类事项'] ? JSON.parse(JSON.stringify(newData['工程类事项'])) : [...defaultChart] } } }, { deep: true, immediate: true }) @@ -415,5 +440,122 @@ const handleTabClick = (tab: TabType) => { // 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; + } +} diff --git a/src/views/screen/components/TimeoutWorkOrderPanel.vue b/src/views/screen/components/TimeoutWorkOrderPanel.vue index 8dbbd71..860a3d2 100644 --- a/src/views/screen/components/TimeoutWorkOrderPanel.vue +++ b/src/views/screen/components/TimeoutWorkOrderPanel.vue @@ -3,17 +3,21 @@
超时工单
+ +
- {{ timeoutWorkOrders?.total || 0 }} +
+ {{ timeoutWorkOrders?.total || 0 }}
超时工单数 - {{ timeoutWorkOrders?.total || 0 }} +
+ {{ timeoutWorkOrders?.total || 0 }}
@@ -32,7 +36,7 @@
--> - +
@@ -52,6 +56,7 @@ interface TimeoutWorkOrders { // Props定义 interface Props { + loading?: boolean timeoutWorkOrders?: TimeoutWorkOrders alertDetails?: AlertItem[] sourceIndex?: number @@ -247,5 +252,104 @@ const props = withDefaults(defineProps(), { } } } + + // 骨架屏样式 + .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; + } + } } diff --git a/src/views/screen/mainScreen.vue b/src/views/screen/mainScreen.vue index 5d13d27..ddfbcca 100644 --- a/src/views/screen/mainScreen.vue +++ b/src/views/screen/mainScreen.vue @@ -2,11 +2,16 @@
-
-
集团 - ··· -
-
+

总部综合监控大屏

{{ currentDate }} @@ -19,39 +24,41 @@
- -
- -
- +
- -