Files
lc_frontend/src/views/screen/components/RiskStatisticsPanel.vue
2025-12-15 21:28:16 +08:00

435 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="left-bottom">
<div class="panel-title">
<div class="tabs">
<span class="tab" :class="{ active: activeTab === '安全类' }" @click="handleTabClick('安全类')">安全类</span>
<span class="divider">|</span>
<span class="tab" :class="{ active: activeTab === '工程类' }" @click="handleTabClick('工程类')">工程类</span>
</div>
</div>
<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>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { ElTooltip } from 'element-plus'
import type { EChartsOption } from 'echarts'
type TabType = '安全类' | '工程类'
type StatusKey = 'notStarted' | 'inProgress' | 'done' | 'voided'
interface ChartItem {
title: string
total: number
status: Record<StatusKey, number>
}
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' }
]
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 } }
]
const tabCharts = ref<Record<TabType, ChartItem[]>>({
安全类: [...defaultChart],
工程类: [...defaultChart]
})
const props = defineProps<{
riskStatistics?: Record<TabType, ChartItem[]>
}>()
const activeTab = ref<TabType>('安全类')
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]
}
}
}, { deep: true, immediate: true })
const currentCharts = computed(() => tabCharts.value[activeTab.value])
const buildOption = (item: ChartItem): EChartsOption => ({
backgroundColor: 'transparent',
tooltip: { show: false },
series: [
{
type: 'pie' as const,
radius: ['70%', '90%'],
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 }
}
]
})
const handleTabClick = (tab: TabType) => {
if (activeTab.value === tab) return
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;
}
.title-line {
margin: 8px 0;
}
.chart-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
padding: 0 12px 12px;
width: 70%;
// 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 0;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 4px 12px rgb(0 0 0 / 25%);
}
.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 默认高度,避免过高
}
.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);
}
.legend {
// display: flex;
// flex-direction: column;
// gap: 6px;
// margin-top: 0;
// width: auto;
// justify-content: space-between;
}
.legend-label-row {
flex-wrap: nowrap;
align-items: center;
color: #cbd5e1;
font-size: 12px;
display: flex;
justify-content: space-between;
padding: 0 2px;
}
.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;
}
}
/* 简化版尺寸适配:只控制字体大小和图表尺寸 */
@media (width <= 1680px) {
.left-bottom {
.panel-title {
font-size: 0.75rem;
}
.tab {
padding: 2px 8px;
font-size: 0.78rem;
}
.chart-title {
font-size: 12px;
}
.chart-wrapper {
flex: 0 0 80px;
width: 80px;
height: 80px;
}
.legend-label-row {
font-size: 11px;
}
}
}
@media (width <= 1400px) {
.left-bottom {
.panel-title {
font-size: 0.7rem;
}
.tab {
padding: 1px 6px;
font-size: 0.72rem;
}
.chart-title {
font-size: 11px;
}
.chart-wrapper {
flex: 0 0 72px;
width: 72px;
height: 72px;
}
.legend-label-row {
font-size: 10px;
}
}
}
@media (width <= 1200px) {
.left-bottom {
.panel-title {
font-size: 0.65rem;
}
.tab {
padding: 1px 4px;
font-size: 0.68rem;
}
.chart-title {
font-size: 10px;
}
.chart-wrapper {
flex: 0 0 64px;
width: 64px;
height: 64px;
}
.legend-label-row {
font-size: 9px;
}
}
}
// @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-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: 60px;
width: 60px;
height: 60px;
}
// }
// @media (width <=1024px) {
// .left-bottom .chart-grid {
// grid-template-columns: repeat(2, minmax(0, 1fr));
// }
// .left-bottom .donut-chart {
// height: 80px !important;
// }
// }
// @media (width <=768px) {
// .left-bottom .chart-grid {
// grid-template-columns: repeat(1, minmax(0, 1fr));
// }
// .left-bottom .chart-card {
// padding: 5px;
// }
// }
</style>