|
@@ -0,0 +1,335 @@
|
|
|
+<template>
|
|
|
+ <div class="fault-statistics-board">
|
|
|
+ <!-- 看板标题 -->
|
|
|
+ <div class="board-header">
|
|
|
+ <h2>故障部件统计综合看板</h2>
|
|
|
+ <el-tag type="info" size="small">{{ updateTime }}</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>
|
|
|
+ <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>
|
|
|
+ </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' // 日期工具函数
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'FaultStatisticsBoard',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ // 数据
|
|
|
+ nextMonthData: [], // 近三年下月TOP5数据
|
|
|
+ sixMonthsData: [], // 近半年TOP5数据
|
|
|
+ totalNextMonthFaults: 0, // 近三年下月总故障数
|
|
|
+ totalSixMonthsFaults: 0, // 近半年总故障数
|
|
|
+
|
|
|
+ // 时间信息
|
|
|
+ updateTime: '', // 数据更新时间
|
|
|
+ threeYearsRange: '', // 近三年时间范围
|
|
|
+ sixMonthsRange: '', // 近半年时间范围
|
|
|
+
|
|
|
+ // 状态
|
|
|
+ loading: true,
|
|
|
+ // 图表实例
|
|
|
+ nextMonthChartIns: null,
|
|
|
+ sixMonthsChartIns: null
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ // 初始化图表
|
|
|
+ this.initCharts()
|
|
|
+ // 加载数据
|
|
|
+ this.loadStatisticsData()
|
|
|
+ // 监听窗口大小变化,重绘图表
|
|
|
+ window.addEventListener('resize', this.handleResize)
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ // 销毁图表实例,释放资源
|
|
|
+ if (this.nextMonthChartIns) this.nextMonthChartIns.dispose()
|
|
|
+ if (this.sixMonthsChartIns) this.sixMonthsChartIns.dispose()
|
|
|
+ window.removeEventListener('resize', this.handleResize)
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ // 初始化图表实例
|
|
|
+ initCharts() {
|
|
|
+ this.nextMonthChartIns = echarts.init(this.$refs.nextMonthChart)
|
|
|
+ this.sixMonthsChartIns = echarts.init(this.$refs.sixMonthsChart)
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载统计数据
|
|
|
+ async loadStatisticsData() {
|
|
|
+ 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
|
|
|
+ )
|
|
|
+
|
|
|
+ // 设置时间信息
|
|
|
+ this.updateTime = formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss')
|
|
|
+ this.setTimeRangeText()
|
|
|
+
|
|
|
+ // 渲染图表
|
|
|
+ this.renderNextMonthChart()
|
|
|
+ this.renderSixMonthsChart()
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error('数据加载失败:' + (error.message || '未知错误'))
|
|
|
+ } finally {
|
|
|
+ this.loading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 设置时间范围文本
|
|
|
+ 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')}`
|
|
|
+
|
|
|
+ // 近半年范围:例如 2025-02 至 2025-08
|
|
|
+ const sixMonthsAgo = new Date(now.getFullYear(), now.getMonth() - 5, 1)
|
|
|
+ this.sixMonthsRange = `${formatDate(sixMonthsAgo, 'yyyy-MM')} 至 ${formatDate(now, 'yyyy-MM')}`
|
|
|
+ },
|
|
|
+
|
|
|
+ // 渲染近三年下月故障部件图表
|
|
|
+ renderNextMonthChart() {
|
|
|
+ const parts = this.nextMonthData.map(item => item.partName)
|
|
|
+ const counts = this.nextMonthData.map(item => item.count)
|
|
|
+
|
|
|
+ this.nextMonthChartIns.setOption({
|
|
|
+ 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
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ name: '故障次数',
|
|
|
+ type: 'bar',
|
|
|
+ data: counts,
|
|
|
+ itemStyle: {
|
|
|
+ // 柱状图颜色渐变
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
|
|
+ { offset: 0, color: '#409EFF' },
|
|
|
+ { offset: 1, color: '#67C23A' }
|
|
|
+ ])
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'right',
|
|
|
+ formatter: '{c} 次'
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 渲染近半年故障部件图表
|
|
|
+ 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
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ name: '故障部件',
|
|
|
+ type: 'pie',
|
|
|
+ radius: ['40%', '70%'], // 环形图
|
|
|
+ avoidLabelOverlap: false,
|
|
|
+ itemStyle: {
|
|
|
+ borderRadius: 4,
|
|
|
+ borderColor: '#fff',
|
|
|
+ borderWidth: 2
|
|
|
+ },
|
|
|
+ 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
|
|
|
+ }))
|
|
|
+ }]
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 窗口大小变化时重绘图表
|
|
|
+ handleResize() {
|
|
|
+ this.nextMonthChartIns.resize()
|
|
|
+ this.sixMonthsChartIns.resize()
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.fault-statistics-board {
|
|
|
+ padding: 20px;
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ min-height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.board-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.statistic-cards {
|
|
|
+ display: flex;
|
|
|
+ gap: 20px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 280px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value {
|
|
|
+ font-size: 28px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #1890ff;
|
|
|
+ margin: 15px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-desc {
|
|
|
+ color: #666;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.charts-container {
|
|
|
+ display: flex;
|
|
|
+ gap: 20px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-card {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 450px;
|
|
|
+ height: 450px;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-wrapper {
|
|
|
+ width: 100%;
|
|
|
+ height: calc(100% - 44px); /* 减去卡片头部高度 */
|
|
|
+}
|
|
|
+
|
|
|
+.chart-dom {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式调整 */
|
|
|
+@media (max-width: 992px) {
|
|
|
+ .charts-container {
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+ .chart-card {
|
|
|
+ height: 400px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 576px) {
|
|
|
+ .chart-card {
|
|
|
+ min-width: 100%;
|
|
|
+ height: 350px;
|
|
|
+ }
|
|
|
+ .statistic-cards {
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+ .stat-card {
|
|
|
+ min-width: 100%;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|