435 lines
10 KiB
Vue
435 lines
10 KiB
Vue
<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>
|
||
|