-

-
外协人员
-
+
+

+
外协人员
+
+
{{ 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 @@