2025-10-17 10:31:13 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="left-bottom">
|
|
|
|
|
|
<div class="panel-title">
|
|
|
|
|
|
<div class="tabs">
|
2025-12-15 20:55:27 +08:00
|
|
|
|
<span class="tab" :class="{ active: activeTab === '安全类' }" @click="handleTabClick('安全类')">安全类</span>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
<span class="divider">|</span>
|
2025-12-15 20:55:27 +08:00
|
|
|
|
<span class="tab" :class="{ active: activeTab === '工程类' }" @click="handleTabClick('工程类')">工程类</span>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-12-15 20:55:27 +08:00
|
|
|
|
<img class="title-line" src="@/assets/images/title_border_line.png" />
|
|
|
|
|
|
|
|
|
|
|
|
<div class="chart-grid">
|
|
|
|
|
|
<div class="chart-card" v-for="item in currentCharts" :key="item.title">
|
|
|
|
|
|
<div class="chart-title">{{ item.title }}</div>
|
|
|
|
|
|
<div class="chart-content">
|
|
|
|
|
|
<div class="chart-wrapper">
|
|
|
|
|
|
<Echart class="donut-chart" :options="buildOption(item)" />
|
|
|
|
|
|
<div class="chart-center">
|
|
|
|
|
|
<div class="center-title">总数</div>
|
|
|
|
|
|
<div class="center-value">{{ item.total }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="legend">
|
|
|
|
|
|
<div class="legend-label-row">
|
|
|
|
|
|
<ElTooltip v-for="status in statusList" :key="status.key" :content="status.label" effect="dark" placement="right">
|
|
|
|
|
|
<div class="legend-item">
|
|
|
|
|
|
<span class="legend-dot" :style="{ background: status.color }"></span>
|
|
|
|
|
|
<span class="legend-label">{{ status.label }}</span>
|
|
|
|
|
|
<div class="legend-value">
|
|
|
|
|
|
{{ item.status[status.key] }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</ElTooltip>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="legend-value-row">
|
|
|
|
|
|
<div class="legend-value" v-for="status in statusList" :key="`${status.key}-value`">
|
|
|
|
|
|
{{ item.status[status.key] }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2025-12-15 20:55:27 +08:00
|
|
|
|
import { computed, ref, watch } from 'vue'
|
|
|
|
|
|
import { ElTooltip } from 'element-plus'
|
|
|
|
|
|
import type { EChartsOption } from 'echarts'
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
type TabType = '安全类' | '工程类'
|
|
|
|
|
|
type StatusKey = 'notStarted' | 'inProgress' | 'done' | 'voided'
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
interface ChartItem {
|
|
|
|
|
|
title: string
|
|
|
|
|
|
total: number
|
|
|
|
|
|
status: Record<StatusKey, number>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
const statusList: { key: StatusKey; label: string; color: string }[] = [
|
|
|
|
|
|
{ key: 'notStarted', label: '未开始', color: '#2a59ff' },
|
|
|
|
|
|
{ key: 'inProgress', label: '进行中', color: '#ff8a00' },
|
|
|
|
|
|
{ key: 'done', label: '已完成', color: '#1bd9ff' },
|
|
|
|
|
|
{ key: 'voided', label: '已作废', color: '#9fa0a6' }
|
|
|
|
|
|
]
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
const defaultChart: ChartItem[] = [
|
|
|
|
|
|
{ title: '每日检查(维保类)', total: 6, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } },
|
|
|
|
|
|
{ title: '每月检查(维保类)', total: 6, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } },
|
|
|
|
|
|
{ title: '每年检查(维保类)', total: 6, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } },
|
|
|
|
|
|
{ title: '每日检查(巡检类)', total: 6, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } },
|
|
|
|
|
|
{ title: '每月检查(巡检类)', total: 6, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } },
|
|
|
|
|
|
{ title: '每年检查(巡检类)', total: 6, status: { notStarted: 3, inProgress: 0, done: 3, voided: 0 } }
|
|
|
|
|
|
]
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
const tabCharts = ref<Record<TabType, ChartItem[]>>({
|
|
|
|
|
|
安全类: [...defaultChart],
|
|
|
|
|
|
工程类: [...defaultChart]
|
2025-10-17 10:31:13 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
const props = defineProps<{
|
|
|
|
|
|
riskStatistics?: Record<TabType, ChartItem[]>
|
|
|
|
|
|
}>()
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
const activeTab = ref<TabType>('安全类')
|
|
|
|
|
|
const emit = defineEmits<{ tabChange: [tab: TabType] }>()
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
// 监听props变化,更新图表数据
|
|
|
|
|
|
watch(() => props.riskStatistics, (newData) => {
|
|
|
|
|
|
if (newData) {
|
|
|
|
|
|
tabCharts.value = {
|
|
|
|
|
|
安全类: newData['安全类'] ? JSON.parse(JSON.stringify(newData['安全类'])) : [...defaultChart],
|
|
|
|
|
|
工程类: newData['工程类'] ? JSON.parse(JSON.stringify(newData['工程类'])) : [...defaultChart]
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
2025-12-15 20:55:27 +08:00
|
|
|
|
}, { deep: true, immediate: true })
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
const currentCharts = computed(() => tabCharts.value[activeTab.value])
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
const buildOption = (item: ChartItem): EChartsOption => ({
|
|
|
|
|
|
backgroundColor: 'transparent',
|
|
|
|
|
|
tooltip: { show: false },
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'pie' as const,
|
|
|
|
|
|
radius: ['40%', '60%'],
|
|
|
|
|
|
center: ['50%', '50%'],
|
|
|
|
|
|
startAngle: 90,
|
|
|
|
|
|
avoidLabelOverlap: false,
|
|
|
|
|
|
label: { show: false },
|
|
|
|
|
|
labelLine: { show: false },
|
|
|
|
|
|
data: [
|
|
|
|
|
|
{ value: item.status.notStarted, name: '未开始', itemStyle: { color: '#2a59ff' }, label: { show: false } },
|
|
|
|
|
|
{ value: item.status.inProgress, name: '进行中', itemStyle: { color: '#ff8a00' }, label: { show: false } },
|
|
|
|
|
|
{ value: item.status.done, name: '已完成', itemStyle: { color: '#1bd9ff' }, label: { show: false } },
|
|
|
|
|
|
{ value: item.status.voided, name: '已作废', itemStyle: { color: '#9fa0a6' }, label: { show: false } }
|
|
|
|
|
|
],
|
|
|
|
|
|
emphasis: { scale: true, scaleSize: 4 }
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
2025-12-15 20:55:27 +08:00
|
|
|
|
]
|
|
|
|
|
|
})
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
const handleTabClick = (tab: TabType) => {
|
|
|
|
|
|
if (activeTab.value === tab) return
|
2025-10-17 10:31:13 +08:00
|
|
|
|
activeTab.value = tab
|
|
|
|
|
|
emit('tabChange', tab)
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
.left-bottom {
|
|
|
|
|
|
background-image: url('@/assets/images/screen/left_top_2_img.png'), url('@/assets/images/screen/left_center_img.png'), url('@/assets/images/screen/left_bottom_img.png');
|
|
|
|
|
|
background-position: top center, left center, bottom center;
|
|
|
|
|
|
background-repeat: no-repeat, no-repeat, no-repeat;
|
|
|
|
|
|
background-size: 100% 90px, cover, 100% 68px;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
|
|
|
|
|
|
.panel-title {
|
|
|
|
|
|
margin: 4px 20px 0;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tabs {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
column-gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tab {
|
|
|
|
|
|
padding: 2px 10px;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
transition: all 0.2s ease-in-out;
|
|
|
|
|
|
user-select: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tab:hover {
|
|
|
|
|
|
color: #1afb8f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tab.active {
|
|
|
|
|
|
color: #1afb8f;
|
|
|
|
|
|
background: rgb(26 251 143 / 12%);
|
|
|
|
|
|
border: 1px solid rgb(26 251 143 / 35%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.divider {
|
|
|
|
|
|
margin: 0 2px;
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
.title-line {
|
|
|
|
|
|
margin: 8px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chart-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
padding: 0 12px 12px;
|
|
|
|
|
|
width: 66%;
|
|
|
|
|
|
// margin: 0 auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chart-card {
|
|
|
|
|
|
background: rgba(18, 34, 63, 0.65);
|
|
|
|
|
|
border: 1px solid rgba(58, 112, 179, 0.55);
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
padding: 5px;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2025-12-15 20:55:27 +08:00
|
|
|
|
align-items: center;
|
|
|
|
|
|
box-shadow: 0 4px 12px rgb(0 0 0 / 25%);
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
.chart-title {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chart-content {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
// justify-content: flex-start;
|
|
|
|
|
|
// gap: 10px;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chart-wrapper {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
flex: 0 0 90px;
|
|
|
|
|
|
width: 90px;
|
|
|
|
|
|
height: 90px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.donut-chart {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100% !important; // 强制覆盖 EChart 默认高度,避免过高
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
.chart-center {
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
inset: 0;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
text-shadow: 0 2px 6px rgba(0, 0, 0, 0.45);
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
.legend {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
margin-top: 0;
|
|
|
|
|
|
width: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.legend-label-row {
|
|
|
|
|
|
flex-wrap: nowrap;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
color: #cbd5e1;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.legend-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.legend-dot {
|
|
|
|
|
|
width: 10px;
|
|
|
|
|
|
height: 10px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
// display: inline-block;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.legend-label {
|
|
|
|
|
|
color: #cbd5e1;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.legend-value-row {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
gap: 20px;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.legend-value {
|
|
|
|
|
|
min-width: 14px;
|
|
|
|
|
|
text-align: center;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
@media (width <=1280px) {
|
|
|
|
|
|
.left-bottom .legend-label-row {
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.left-bottom .legend-label {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.left-bottom .legend-item {
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
.left-bottom .chart-wrapper {
|
|
|
|
|
|
flex-basis: 80px;
|
|
|
|
|
|
width: 80px;
|
|
|
|
|
|
height: 80px;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
@media (width <=3000px) {
|
|
|
|
|
|
.left-bottom .legend-label {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
@media (width >3000px) {
|
|
|
|
|
|
.left-bottom .chart-content {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.left-bottom .legend {
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.left-bottom .legend-label {
|
|
|
|
|
|
display: inline;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.left-bottom .legend-label-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 18px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.left-bottom .legend-item {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.left-bottom .legend-label-row .legend-value {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.left-bottom .legend-value-row {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.left-bottom .chart-wrapper {
|
|
|
|
|
|
flex-basis: 110px;
|
|
|
|
|
|
width: 110px;
|
|
|
|
|
|
height: 110px;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
@media (width <=1024px) {
|
|
|
|
|
|
.left-bottom .chart-grid {
|
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.left-bottom .donut-chart {
|
|
|
|
|
|
height: 80px !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-12-15 20:55:27 +08:00
|
|
|
|
@media (width <=768px) {
|
|
|
|
|
|
.left-bottom .chart-grid {
|
|
|
|
|
|
grid-template-columns: repeat(1, minmax(0, 1fr));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.left-bottom .chart-card {
|
|
|
|
|
|
padding: 5px;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
2025-12-15 20:55:27 +08:00
|
|
|
|
|