|
@@ -1,333 +1,559 @@
|
|
|
<template>
|
|
|
<div class="fault-statistics-board">
|
|
|
- <!-- 看板标题 -->
|
|
|
+ <!-- 看板标题区域 -->
|
|
|
<div class="board-header">
|
|
|
- <h2>故障部件统计综合看板</h2>
|
|
|
- <el-tag type="info" size="small">{{ updateTime }}</el-tag>
|
|
|
+ <div class="header-content">
|
|
|
+ <h2>故障部件统计综合看板</h2>
|
|
|
+ <p class="time-desc">
|
|
|
+ 数据更新时间:<span class="update-time">{{ updateTime || '--' }}</span>
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ <el-tag type="info" size="small" v-if="timeRangesVisible">
|
|
|
+ <template v-if="activeTimeRange === 'nextMonth'">
|
|
|
+ 近三年下月周期:{{ threeYearsNextMonthRange }}
|
|
|
+ </template>
|
|
|
+ <template v-else-if="activeTimeRange === 'sixMonths'">
|
|
|
+ 近半年周期:{{ sixMonthsRange }}
|
|
|
+ </template>
|
|
|
+ </el-tag>
|
|
|
</div>
|
|
|
-
|
|
|
- <!-- 数据卡片区域 -->
|
|
|
- <div class="statistic-cards">
|
|
|
- <el-card class="stat-card">
|
|
|
- <div slot="header">近三年下月故障总次数</div>
|
|
|
- <div class="stat-value">{{ totalNextMonthFaults }}</div>
|
|
|
- <div class="stat-desc">统计周期:{{ threeYearsRange }}</div>
|
|
|
- </el-card>
|
|
|
-
|
|
|
- <el-card class="stat-card">
|
|
|
- <div slot="header">近半年故障总次数</div>
|
|
|
- <div class="stat-value">{{ totalSixMonthsFaults }}</div>
|
|
|
- <div class="stat-desc">统计周期:{{ sixMonthsRange }}</div>
|
|
|
- </el-card>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 图表区域(响应式布局) -->
|
|
|
+ <!-- 图表区域 -->
|
|
|
<div class="charts-container">
|
|
|
- <!-- 近三年下月故障部件TOP5 -->
|
|
|
- <el-card class="chart-card">
|
|
|
- <div slot="header">近三年下月故障最多的5类部件</div>
|
|
|
+ <!-- 加载状态 -->
|
|
|
+ <el-loading
|
|
|
+ v-if="loading"
|
|
|
+ :fullscreen="false"
|
|
|
+ target=".charts-container"
|
|
|
+ text="正在加载数据..."
|
|
|
+ spinner="el-icon-loading"
|
|
|
+ ></el-loading>
|
|
|
+
|
|
|
+ <div class="charts-row">
|
|
|
+ <el-card class="chart-card first-two-charts" shadow="hover" @mouseenter="activeTimeRange = 'nextMonth'">
|
|
|
+ <div slot="header">基于节气的部件故障预测</div>
|
|
|
+ <div class="chart-wrapper">
|
|
|
+ <div ref="nextMonthChart" class="chart-dom"></div>
|
|
|
+ <div class="empty-state" v-if="!loading && nextMonthData.length === 0">
|
|
|
+ <el-icon class="empty-icon">
|
|
|
+ <warning/>
|
|
|
+ </el-icon>
|
|
|
+ <p>暂无相关数据</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ <el-card class="chart-card first-two-charts" shadow="hover" @mouseenter="activeTimeRange = 'sixMonths'">
|
|
|
+ <div slot="header">基于历史数据的部件故障预测</div>
|
|
|
+ <div class="chart-wrapper">
|
|
|
+ <div ref="sixMonthsChart" class="chart-dom"></div>
|
|
|
+ <div class="empty-state" v-if="!loading && sixMonthsData.length === 0">
|
|
|
+ <el-icon class="empty-icon">
|
|
|
+ <warning />
|
|
|
+ </el-icon>
|
|
|
+ <p>暂无相关数据</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 3. 合并数据统计 -->
|
|
|
+ <el-card class="chart-card merged-chart" shadow="hover">
|
|
|
+ <div slot="header">部件综合故障预测</div>
|
|
|
<div class="chart-wrapper">
|
|
|
- <div ref="nextMonthChart" class="chart-dom"></div>
|
|
|
- </div>
|
|
|
- </el-card>
|
|
|
-
|
|
|
- <!-- 近半年故障部件TOP5 -->
|
|
|
- <el-card class="chart-card">
|
|
|
- <div slot="header">近半年故障最多的5类部件</div>
|
|
|
- <div class="chart-wrapper">
|
|
|
- <div ref="sixMonthsChart" class="chart-dom"></div>
|
|
|
+ <div ref="mergedChart" class="chart-dom"></div>
|
|
|
+ <div class="empty-state" v-if="!loading && mergedData.length === 0">
|
|
|
+ <el-icon class="empty-icon">
|
|
|
+ <warning/>
|
|
|
+ </el-icon>
|
|
|
+ <p>暂无相关数据</p>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</div>
|
|
|
-
|
|
|
-<!-- <!– 加载状态 –>-->
|
|
|
-<!-- <el-loading-->
|
|
|
-<!-- v-loading="loading"-->
|
|
|
-<!-- text="正在加载统计数据..."-->
|
|
|
-<!-- background="rgba(255, 255, 255, 0.8)"-->
|
|
|
-<!-- ></el-loading>-->
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import * as echarts from 'echarts'
|
|
|
-import { getTop5NextMonth, getTop5SixMonths } from '@/api/faultStatistics' // 引入API
|
|
|
-import { formatDate } from '@/utils/dataUtils' // 日期工具函数
|
|
|
+import { formatDate } from '@/utils/dataUtils'
|
|
|
+import { debounce } from '@/utils/tools' // 引入防抖工具函数
|
|
|
|
|
|
export default {
|
|
|
name: 'FaultStatisticsBoard',
|
|
|
data() {
|
|
|
return {
|
|
|
- // 数据
|
|
|
- nextMonthData: [], // 近三年下月TOP5数据
|
|
|
- sixMonthsData: [], // 近半年TOP5数据
|
|
|
- totalNextMonthFaults: 0, // 近三年下月总故障数
|
|
|
- totalSixMonthsFaults: 0, // 近半年总故障数
|
|
|
-
|
|
|
- // 时间信息
|
|
|
- updateTime: '', // 数据更新时间
|
|
|
- threeYearsRange: '', // 近三年时间范围
|
|
|
- sixMonthsRange: '', // 近半年时间范围
|
|
|
-
|
|
|
- // 状态
|
|
|
- loading: true,
|
|
|
+ // 原始数据
|
|
|
+ nextMonthData: [], // 近三年下月数据
|
|
|
+ sixMonthsData: [], // 近半年数据
|
|
|
+ mergedData: [], // 合并后数据
|
|
|
+
|
|
|
+ // 总计数据
|
|
|
+ totalNextMonthFaults: 0,
|
|
|
+ totalSixMonthsFaults: 0,
|
|
|
+ totalMergedFaults: 0,
|
|
|
+
|
|
|
+ // 时间范围文本
|
|
|
+ updateTime: '',
|
|
|
+ threeYearsNextMonthRange: '', // 近三年下月周期
|
|
|
+ sixMonthsRange: '', // 近半年周期
|
|
|
+ activeTimeRange: '', // 当前激活的时间范围(用于显示标签)
|
|
|
+ timeRangesVisible: false, // 时间范围标签是否显示
|
|
|
+
|
|
|
// 图表实例
|
|
|
nextMonthChartIns: null,
|
|
|
- sixMonthsChartIns: null
|
|
|
+ sixMonthsChartIns: null,
|
|
|
+ mergedChartIns: null,
|
|
|
+
|
|
|
+ // 状态控制
|
|
|
+ loading: false
|
|
|
}
|
|
|
},
|
|
|
mounted() {
|
|
|
- // 初始化图表
|
|
|
this.initCharts()
|
|
|
- // 加载数据
|
|
|
this.loadStatisticsData()
|
|
|
- // 监听窗口大小变化,重绘图表
|
|
|
- window.addEventListener('resize', this.handleResize)
|
|
|
+ // 防抖处理窗口resize事件(300ms延迟)
|
|
|
+ window.addEventListener('resize', debounce(this.handleResize, 300))
|
|
|
},
|
|
|
beforeDestroy() {
|
|
|
- // 销毁图表实例,释放资源
|
|
|
- if (this.nextMonthChartIns) this.nextMonthChartIns.dispose()
|
|
|
- if (this.sixMonthsChartIns) this.sixMonthsChartIns.dispose()
|
|
|
+ // 销毁所有图表实例
|
|
|
+ this.destroyCharts()
|
|
|
window.removeEventListener('resize', this.handleResize)
|
|
|
},
|
|
|
methods: {
|
|
|
- // 初始化图表实例
|
|
|
+ // 初始化三个图表实例
|
|
|
initCharts() {
|
|
|
- this.nextMonthChartIns = echarts.init(this.$refs.nextMonthChart)
|
|
|
- this.sixMonthsChartIns = echarts.init(this.$refs.sixMonthsChart)
|
|
|
+ // 确保DOM元素已渲染
|
|
|
+ this.$nextTick(() => {
|
|
|
+ console.log('图表容器是否存在:', this.$refs.nextMonthChart); // 检查是否为null
|
|
|
+ this.nextMonthChartIns = echarts.init(this.$refs.nextMonthChart)
|
|
|
+ this.sixMonthsChartIns = echarts.init(this.$refs.sixMonthsChart)
|
|
|
+ this.mergedChartIns = echarts.init(this.$refs.mergedChart)
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 销毁图表实例
|
|
|
+ destroyCharts() {
|
|
|
+ this.nextMonthChartIns?.dispose()
|
|
|
+ this.sixMonthsChartIns?.dispose()
|
|
|
+ this.mergedChartIns?.dispose()
|
|
|
},
|
|
|
|
|
|
- // 加载统计数据
|
|
|
+ // 加载并处理数据
|
|
|
async loadStatisticsData() {
|
|
|
+
|
|
|
+ await this.$nextTick();
|
|
|
this.loading = true
|
|
|
try {
|
|
|
- // 并行请求两个接口
|
|
|
- const [nextMonthRes, sixMonthsRes] = await Promise.all([
|
|
|
- getTop5NextMonth(),
|
|
|
- getTop5SixMonths()
|
|
|
- ])
|
|
|
-
|
|
|
- // 处理近三年下月数据
|
|
|
- this.nextMonthData = nextMonthRes.data || []
|
|
|
- this.totalNextMonthFaults = this.nextMonthData.reduce(
|
|
|
- (sum, item) => sum + item.count, 0
|
|
|
- )
|
|
|
-
|
|
|
- // 处理近半年数据
|
|
|
- this.sixMonthsData = sixMonthsRes.data || []
|
|
|
- this.totalSixMonthsFaults = this.sixMonthsData.reduce(
|
|
|
- (sum, item) => sum + item.count, 0
|
|
|
- )
|
|
|
-
|
|
|
- // 设置时间信息
|
|
|
+ // 实际项目中启用API请求
|
|
|
+ // const [nextMonthRes, sixMonthsRes] = await Promise.all([
|
|
|
+ // getTop5NextMonth(),
|
|
|
+ // getTop5SixMonths()
|
|
|
+ // ])
|
|
|
+ // this.nextMonthData = nextMonthRes.data || []
|
|
|
+ // this.sixMonthsData = sixMonthsRes.data || []
|
|
|
+
|
|
|
+ // 模拟数据(已修正排序问题)
|
|
|
+ this.sixMonthsData = [
|
|
|
+ { partName: '滚动轴承', count: 88 },
|
|
|
+ { partName: '液压泵', count: 72 },
|
|
|
+ { partName: '密封件', count: 59 },
|
|
|
+ { partName: '驱动电机', count: 48 },
|
|
|
+ { partName: '电磁阀', count: 36 }
|
|
|
+ ]
|
|
|
+ this.nextMonthData = [
|
|
|
+ { partName: '驱动电机', count: 128 },
|
|
|
+ { partName: '滚动轴承', count: 95 },
|
|
|
+ { partName: '温度传感器', count: 76 },
|
|
|
+ { partName: '液压泵', count: 62 },
|
|
|
+ { partName: '控制模块', count: 45 }
|
|
|
+ ]
|
|
|
+
|
|
|
+ // 处理数据
|
|
|
+ this.calculateTotals() // 计算总计
|
|
|
+ this.mergedData = this.mergeAndSortData() // 合并排序数据
|
|
|
+ this.setTimeRangeText() // 设置时间范围
|
|
|
this.updateTime = formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss')
|
|
|
- this.setTimeRangeText()
|
|
|
-
|
|
|
+ this.timeRangesVisible = true
|
|
|
// 渲染图表
|
|
|
- this.renderNextMonthChart()
|
|
|
- this.renderSixMonthsChart()
|
|
|
-
|
|
|
+ this.renderAllCharts()
|
|
|
} catch (error) {
|
|
|
- this.$message.error('数据加载失败:' + (error.message || '未知错误'))
|
|
|
+ this.$message.error(`数据加载失败:${error.message || '未知错误'}`)
|
|
|
+ console.error('数据加载错误:', error)
|
|
|
} finally {
|
|
|
this.loading = false
|
|
|
}
|
|
|
},
|
|
|
|
|
|
+ // 计算各统计项总计
|
|
|
+ calculateTotals() {
|
|
|
+ this.totalNextMonthFaults = this.nextMonthData.reduce((sum, item) => sum + item.count, 0)
|
|
|
+ this.totalSixMonthsFaults = this.sixMonthsData.reduce((sum, item) => sum + item.count, 0)
|
|
|
+ this.totalMergedFaults = this.mergedData.reduce((sum, item) => sum + item.count, 0)
|
|
|
+ },
|
|
|
+
|
|
|
+ // 合并并排序数据(相同部件累加,按count升序)
|
|
|
+ mergeAndSortData() {
|
|
|
+ const mergedMap = new Map()
|
|
|
+
|
|
|
+ // 累加近三年下月数据
|
|
|
+ this.nextMonthData.forEach(item => {
|
|
|
+ mergedMap.set(item.partName, (mergedMap.get(item.partName) || 0) + item.count)
|
|
|
+ })
|
|
|
+
|
|
|
+ // 累加近半年数据
|
|
|
+ this.sixMonthsData.forEach(item => {
|
|
|
+ mergedMap.set(item.partName, (mergedMap.get(item.partName) || 0) + item.count)
|
|
|
+ })
|
|
|
+
|
|
|
+ // 转换为数组并按count升序排序
|
|
|
+ return Array.from(mergedMap.entries())
|
|
|
+ .map(([partName, count]) => ({ partName, count }))
|
|
|
+ .sort((a, b) => a.count - b.count)
|
|
|
+ },
|
|
|
+
|
|
|
// 设置时间范围文本
|
|
|
setTimeRangeText() {
|
|
|
const now = new Date()
|
|
|
- // 近三年范围:例如 2022-08 至 2025-08
|
|
|
- const threeYearsAgo = new Date(now.getFullYear() - 3, now.getMonth(), 1)
|
|
|
- this.threeYearsRange = `${formatDate(threeYearsAgo, 'yyyy-MM')} 至 ${formatDate(now, 'yyyy-MM')}`
|
|
|
+ const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1)
|
|
|
+ const month = String(nextMonth.getMonth() + 1).padStart(2, '0') // 补零处理
|
|
|
+
|
|
|
+ // 近三年下月周期(如:2022-08、2023-08、2024-08)
|
|
|
+ const years = [now.getFullYear() - 3, now.getFullYear() - 2, now.getFullYear() - 1]
|
|
|
+ this.threeYearsNextMonthRange = years.map(year => `${year}-${month}`).join('、')
|
|
|
|
|
|
- // 近半年范围:例如 2025-02 至 2025-08
|
|
|
+ // 近半年周期(如:2025-02 至 2025-07)
|
|
|
const sixMonthsAgo = new Date(now.getFullYear(), now.getMonth() - 5, 1)
|
|
|
this.sixMonthsRange = `${formatDate(sixMonthsAgo, 'yyyy-MM')} 至 ${formatDate(now, 'yyyy-MM')}`
|
|
|
},
|
|
|
|
|
|
- // 渲染近三年下月故障部件图表
|
|
|
+ // 渲染所有图表
|
|
|
+ renderAllCharts() {
|
|
|
+ this.renderNextMonthChart()
|
|
|
+ this.renderSixMonthsChart()
|
|
|
+ this.renderMergedChart()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 渲染近三年下月图表
|
|
|
renderNextMonthChart() {
|
|
|
- const parts = this.nextMonthData.map(item => item.partName)
|
|
|
- const counts = this.nextMonthData.map(item => item.count)
|
|
|
+ const { parts, counts } = this.extractChartData(this.nextMonthData)
|
|
|
+ console.log('近三年下月图表数据:', parts, counts); // 检查是否有值
|
|
|
+ this.nextMonthChartIns.setOption(this.getBarChartOption(parts, counts, '故障次数'))
|
|
|
+ },
|
|
|
|
|
|
- this.nextMonthChartIns.setOption({
|
|
|
+ // 渲染近半年图表
|
|
|
+ renderSixMonthsChart() {
|
|
|
+ const { parts, counts } = this.extractChartData(this.sixMonthsData)
|
|
|
+ console.log('渲染近半年图表:', parts, counts); // 检查是否有值
|
|
|
+ this.sixMonthsChartIns.setOption(this.getBarChartOption(parts, counts, '故障次数'))
|
|
|
+ },
|
|
|
+
|
|
|
+ // 渲染合并数据图表
|
|
|
+ renderMergedChart() {
|
|
|
+ const { parts, counts } = this.extractChartData(this.mergedData)
|
|
|
+ console.log('渲染合并数据图表:', parts, counts); // 检查是否有值
|
|
|
+ this.mergedChartIns.setOption(this.getBarChartOption(parts, counts, '总故障次数', 'horizontal'))
|
|
|
+ },
|
|
|
+
|
|
|
+ // 提取图表数据(部件名和数量)
|
|
|
+ extractChartData(data) {
|
|
|
+ return {
|
|
|
+ parts: data.map(item => item.partName),
|
|
|
+ counts: data.map(item => item.count)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 柱状图通用配置
|
|
|
+ getBarChartOption(xData, yData, seriesName, layout = 'vertical') {
|
|
|
+ const isHorizontal = layout === 'horizontal'
|
|
|
+ const themeColor = {
|
|
|
+ start: '#409EFF',
|
|
|
+ end: '#67C23A'
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
tooltip: {
|
|
|
trigger: 'axis',
|
|
|
axisPointer: { type: 'shadow' },
|
|
|
- formatter: '{b}: {c} 次' // 提示框格式:部件名: 次数
|
|
|
- },
|
|
|
- grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
|
|
- xAxis: {
|
|
|
- type: 'value',
|
|
|
- name: '故障次数',
|
|
|
- axisLabel: { formatter: '{value}' }
|
|
|
- },
|
|
|
- yAxis: {
|
|
|
- type: 'category',
|
|
|
- data: parts,
|
|
|
- axisLabel: {
|
|
|
- // 部件名过长时自动换行
|
|
|
- interval: 0,
|
|
|
- formatter: function(value) {
|
|
|
- return value.length > 6 ? value.substring(0, 6) + '...' : value
|
|
|
- }
|
|
|
- }
|
|
|
+ backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
|
|
+ borderColor: '#eee',
|
|
|
+ borderWidth: 1,
|
|
|
+ textStyle: { color: '#333' },
|
|
|
+ formatter: '{b}: {c} 次'
|
|
|
},
|
|
|
- series: [{
|
|
|
- name: '故障次数',
|
|
|
- type: 'bar',
|
|
|
- data: counts,
|
|
|
- itemStyle: {
|
|
|
- // 柱状图颜色渐变
|
|
|
- color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
|
|
- { offset: 0, color: '#409EFF' },
|
|
|
- { offset: 1, color: '#67C23A' }
|
|
|
- ])
|
|
|
+ grid: { left: '3%', right: '4%', bottom: '8%', top: '5%', containLabel: true },
|
|
|
+ xAxis: isHorizontal
|
|
|
+ ? {
|
|
|
+ type: 'value',
|
|
|
+ name: seriesName,
|
|
|
+ axisLabel: { formatter: '{value}' },
|
|
|
+ splitLine: { lineStyle: { type: 'dashed', color: '#f0f0f0' } }
|
|
|
+ } // <-- 这里补充了缺少的右括号
|
|
|
+ : {
|
|
|
+ type: 'category',
|
|
|
+ data: xData,
|
|
|
+ axisLabel: { interval: 0, rotate: 30 },
|
|
|
+ axisLine: { lineStyle: { color: '#ddd' } }
|
|
|
},
|
|
|
- label: {
|
|
|
- show: true,
|
|
|
- position: 'right',
|
|
|
- formatter: '{c} 次'
|
|
|
+ yAxis: isHorizontal
|
|
|
+ ? {
|
|
|
+ type: 'category',
|
|
|
+ data: xData,
|
|
|
+ axisLabel: { interval: 0 },
|
|
|
+ axisLine: { lineStyle: { color: '#ddd' } }
|
|
|
}
|
|
|
- }]
|
|
|
- })
|
|
|
- },
|
|
|
-
|
|
|
- // 渲染近半年故障部件图表
|
|
|
- renderSixMonthsChart() {
|
|
|
- const parts = this.sixMonthsData.map(item => item.partName)
|
|
|
- const counts = this.sixMonthsData.map(item => item.count)
|
|
|
-
|
|
|
- this.sixMonthsChartIns.setOption({
|
|
|
- tooltip: {
|
|
|
- trigger: 'item',
|
|
|
- formatter: '{a} <br/>{b}: {c} 次 ({d}%)'
|
|
|
- },
|
|
|
- legend: {
|
|
|
- orient: 'vertical',
|
|
|
- left: 10,
|
|
|
- data: parts
|
|
|
- },
|
|
|
+ : {
|
|
|
+ type: 'value',
|
|
|
+ name: seriesName,
|
|
|
+ axisLabel: { formatter: '{value}' },
|
|
|
+ splitLine: { lineStyle: { type: 'dashed', color: '#f0f0f0' } }
|
|
|
+ },
|
|
|
series: [{
|
|
|
- name: '故障部件',
|
|
|
- type: 'pie',
|
|
|
- radius: ['40%', '70%'], // 环形图
|
|
|
- avoidLabelOverlap: false,
|
|
|
+ name: seriesName,
|
|
|
+ type: 'bar',
|
|
|
+ data: yData,
|
|
|
+ barWidth: isHorizontal ? '60%' : '40%',
|
|
|
itemStyle: {
|
|
|
- borderRadius: 4,
|
|
|
- borderColor: '#fff',
|
|
|
- borderWidth: 2
|
|
|
+ color: new echarts.graphic.LinearGradient(
|
|
|
+ isHorizontal ? 0 : 0,
|
|
|
+ 0,
|
|
|
+ isHorizontal ? 1 : 0,
|
|
|
+ 1,
|
|
|
+ [
|
|
|
+ { offset: 0, color: themeColor.start },
|
|
|
+ { offset: 1, color: themeColor.end }
|
|
|
+ ]
|
|
|
+ ),
|
|
|
+ borderRadius: 4
|
|
|
},
|
|
|
label: {
|
|
|
show: false,
|
|
|
- position: 'center'
|
|
|
- },
|
|
|
- emphasis: {
|
|
|
- label: {
|
|
|
- show: true,
|
|
|
- fontSize: '16',
|
|
|
- fontWeight: 'bold'
|
|
|
- }
|
|
|
- },
|
|
|
- labelLine: {
|
|
|
- show: false
|
|
|
- },
|
|
|
- data: this.sixMonthsData.map((item, index) => ({
|
|
|
- name: parts[index],
|
|
|
- value: item.count
|
|
|
- }))
|
|
|
+ position: isHorizontal ? 'right' : 'top',
|
|
|
+ textStyle: { fontWeight: 'bold' }
|
|
|
+ }
|
|
|
}]
|
|
|
- })
|
|
|
+ }
|
|
|
},
|
|
|
|
|
|
- // 窗口大小变化时重绘图表
|
|
|
- handleResize() {
|
|
|
- this.nextMonthChartIns.resize()
|
|
|
- this.sixMonthsChartIns.resize()
|
|
|
+ // 窗口resize时重绘图表
|
|
|
+ handleResize()
|
|
|
+ {
|
|
|
+ this.nextMonthChartIns?.resize()
|
|
|
+ this.sixMonthsChartIns?.resize()
|
|
|
+ this.mergedChartIns?.resize()
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
-}
|
|
|
</script>
|
|
|
|
|
|
-<style scoped>
|
|
|
+<style scoped lang="scss">
|
|
|
+// 变量定义
|
|
|
+$primary-color: #409EFF;
|
|
|
+$text-primary: #333;
|
|
|
+$text-secondary: #666;
|
|
|
+$text-light: #999;
|
|
|
+$bg-card: #fff;
|
|
|
+$border-radius: 6px;
|
|
|
+$spacing-sm: 8px;
|
|
|
+$spacing-md: 16px;
|
|
|
+$spacing-lg: 20px;
|
|
|
+
|
|
|
.fault-statistics-board {
|
|
|
- padding: 20px;
|
|
|
+ padding: $spacing-lg;
|
|
|
background-color: #f5f7fa;
|
|
|
min-height: 100%;
|
|
|
+ box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
+// 标题区域
|
|
|
.board-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 20px;
|
|
|
+ align-items: flex-start;
|
|
|
+ margin-bottom: $spacing-lg;
|
|
|
+ padding-bottom: $spacing-md;
|
|
|
+ border-bottom: 1px solid #eee;
|
|
|
+
|
|
|
+ .header-content {
|
|
|
+ h2 {
|
|
|
+ margin: 0 0 $spacing-sm 0;
|
|
|
+ color: $text-primary;
|
|
|
+ font-size: 1.5rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .time-desc {
|
|
|
+ margin: 0;
|
|
|
+ color: $text-secondary;
|
|
|
+ font-size: 0.9rem;
|
|
|
+
|
|
|
+ .update-time {
|
|
|
+ color: $primary-color;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+// 数据卡片区域
|
|
|
.statistic-cards {
|
|
|
display: flex;
|
|
|
- gap: 20px;
|
|
|
- margin-bottom: 20px;
|
|
|
+ gap: $spacing-lg;
|
|
|
+ margin-bottom: $spacing-lg;
|
|
|
flex-wrap: wrap;
|
|
|
-}
|
|
|
|
|
|
-.stat-card {
|
|
|
- flex: 1;
|
|
|
- min-width: 280px;
|
|
|
-}
|
|
|
+ .stat-card {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 280px;
|
|
|
+ background-color: $bg-card;
|
|
|
+ border-radius: $border-radius;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ transform: translateY(-3px);
|
|
|
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.05);
|
|
|
+ }
|
|
|
|
|
|
-.stat-value {
|
|
|
- font-size: 28px;
|
|
|
- font-weight: bold;
|
|
|
- color: #1890ff;
|
|
|
- margin: 15px 0;
|
|
|
-}
|
|
|
+ .card-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ font-weight: 500;
|
|
|
+
|
|
|
+ .info-icon {
|
|
|
+ color: $text-light;
|
|
|
+ cursor: help;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-value {
|
|
|
+ font-size: 2rem;
|
|
|
+ font-weight: bold;
|
|
|
+ color: $primary-color;
|
|
|
+ margin: $spacing-lg 0;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
|
|
|
-.stat-desc {
|
|
|
- color: #666;
|
|
|
- font-size: 14px;
|
|
|
+ .stat-footer {
|
|
|
+ text-align: right;
|
|
|
+ padding: 0 $spacing-md $spacing-md;
|
|
|
+
|
|
|
+ .range-text {
|
|
|
+ color: $text-light;
|
|
|
+ font-size: 0.85rem;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+// 图表区域
|
|
|
.charts-container {
|
|
|
display: flex;
|
|
|
- gap: 20px;
|
|
|
- flex-wrap: wrap;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: $spacing-lg;
|
|
|
+ width: 100%;
|
|
|
+ box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
-.chart-card {
|
|
|
- flex: 1;
|
|
|
- min-width: 450px;
|
|
|
- height: 450px;
|
|
|
+.charts-row {
|
|
|
+ display: flex;
|
|
|
+ gap: $spacing-lg;
|
|
|
+ width: 100%;
|
|
|
+ height: 400px; // 固定第一行高度
|
|
|
}
|
|
|
|
|
|
-.chart-wrapper {
|
|
|
- width: 100%;
|
|
|
- height: calc(100% - 44px); /* 减去卡片头部高度 */
|
|
|
+.chart-card {
|
|
|
+ background-color: $bg-card;
|
|
|
+ border-radius: $border-radius;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ &.first-two-charts {
|
|
|
+ width: 50%;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.merged-chart {
|
|
|
+ width: 100%;
|
|
|
+ height: 500px; // 合并图表更高一些
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-wrapper {
|
|
|
+ width: 100%;
|
|
|
+ flex: 1;
|
|
|
+ position: relative; // 用于空状态绝对定位
|
|
|
+
|
|
|
+ .chart-dom {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%; /* 保留百分比的同时,强制最小高度 */
|
|
|
+ min-height: 300px; /* 新增:确保至少有高度 */
|
|
|
+ }
|
|
|
+
|
|
|
+ .empty-state {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ color: $text-light;
|
|
|
+
|
|
|
+ .empty-icon {
|
|
|
+ font-size: 2rem;
|
|
|
+ margin-bottom: $spacing-sm;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-.chart-dom {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
+// 响应式调整
|
|
|
+@media (max-width: 1200px) {
|
|
|
+ .chart-card {
|
|
|
+ &.merged-chart {
|
|
|
+ height: 450px;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-/* 响应式调整 */
|
|
|
@media (max-width: 992px) {
|
|
|
- .charts-container {
|
|
|
- flex-direction: column;
|
|
|
+ .charts-row {
|
|
|
+ height: 350px;
|
|
|
}
|
|
|
+
|
|
|
.chart-card {
|
|
|
- height: 400px;
|
|
|
+ &.merged-chart {
|
|
|
+ height: 400px;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-@media (max-width: 576px) {
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .fault-statistics-board {
|
|
|
+ padding: $spacing-md;
|
|
|
+ }
|
|
|
+
|
|
|
+ .charts-row {
|
|
|
+ flex-direction: column;
|
|
|
+ height: auto;
|
|
|
+ gap: $spacing-md;
|
|
|
+ }
|
|
|
+
|
|
|
.chart-card {
|
|
|
- min-width: 100%;
|
|
|
- height: 350px;
|
|
|
+ &.first-two-charts {
|
|
|
+ width: 100%;
|
|
|
+ height: 350px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.merged-chart {
|
|
|
+ height: 380px;
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
.statistic-cards {
|
|
|
- flex-direction: column;
|
|
|
+ gap: $spacing-md;
|
|
|
}
|
|
|
+
|
|
|
.stat-card {
|
|
|
min-width: 100%;
|
|
|
}
|