gaoluyang
2025-09-26 dcb2c6fd035e12f203a731f8052a1ecfb0cfbe89
KPL监控页面
已修改1个文件
699 ■■■■■ 文件已修改
src/views/equipmentManagement/kplMonitor/index.vue 699 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/kplMonitor/index.vue
@@ -1,7 +1,11 @@
<template>
  <div class="kpl-monitor-container">
    <!-- 页面头部 -->
    <div class="page-header">
      <h1>KPL监控分析</h1>
      <div class="header-content">
        <h1>KPL监控分析</h1>
        <p>设备关键性能指标监控与维保策略优化</p>
      </div>
      <div class="time-range">
        <el-date-picker
          v-model="timeRange"
@@ -15,89 +19,90 @@
      </div>
    </div>
    <!-- 关键指标卡片 -->
    <div class="metrics-cards">
      <div class="metric-card">
        <div class="metric-header">
          <span class="metric-title">MTBF</span>
          <span class="metric-trend" :class="mtbfTrendClass">
            {{ mtbfTrendText }}
          </span>
        </div>
        <div class="metric-value">{{ currentMTBF }} 小时</div>
        <div class="metric-change">
          较上月: {{ mtbfChange }}%
    <!-- 关键指标概览 -->
    <div class="metrics-overview">
      <div class="metric-card mtbf-card">
        <div class="metric-icon">⏱️</div>
        <div class="metric-content">
          <div class="metric-title">MTBF</div>
          <div class="metric-subtitle">平均无故障时间</div>
          <div class="metric-value">{{ currentMTBF }}<span class="unit">小时</span></div>
          <div class="metric-trend" :class="mtbfTrendClass">
            <span class="trend-icon">{{ mtbfTrendText }}</span>
            <span class="trend-text">{{ Math.abs(mtbfChange) }}%</span>
          </div>
        </div>
      </div>
      <div class="metric-card">
        <div class="metric-header">
          <span class="metric-title">MTTR</span>
          <span class="metric-trend" :class="mttrTrendClass">
            {{ mttrTrendText }}
          </span>
        </div>
        <div class="metric-value">{{ currentMTTR }} 小时</div>
        <div class="metric-change">
          较上月: {{ mttrChange }}%
      <div class="metric-card mttr-card">
        <div class="metric-icon">🔧</div>
        <div class="metric-content">
          <div class="metric-title">MTTR</div>
          <div class="metric-subtitle">平均修复时间</div>
          <div class="metric-value">{{ currentMTTR }}<span class="unit">小时</span></div>
          <div class="metric-trend" :class="mttrTrendClass">
            <span class="trend-icon">{{ mttrTrendText }}</span>
            <span class="trend-text">{{ Math.abs(mttrChange) }}%</span>
          </div>
        </div>
      </div>
      <div class="metric-card">
        <div class="metric-header">
          <span class="metric-title">设备可用率</span>
          <span class="metric-trend" :class="availabilityTrendClass">
            {{ availabilityTrendText }}
          </span>
        </div>
        <div class="metric-value">{{ currentAvailability }}%</div>
        <div class="metric-change">
          较上月: {{ availabilityChange }}%
        </div>
      </div>
      <div class="metric-card">
        <div class="metric-header">
          <span class="metric-title">故障次数</span>
          <span class="metric-trend" :class="failureTrendClass">
            {{ failureTrendText }}
          </span>
        </div>
        <div class="metric-value">{{ currentFailures }} 次</div>
        <div class="metric-change">
          较上月: {{ failureChange }}%
      <div class="metric-card availability-card">
        <div class="metric-icon">📊</div>
        <div class="metric-content">
          <div class="metric-title">设备可用率</div>
          <div class="metric-subtitle">运行效率指标</div>
          <div class="metric-value">{{ currentAvailability }}<span class="unit">%</span></div>
          <div class="metric-trend" :class="availabilityTrendClass">
            <span class="trend-icon">{{ availabilityTrendText }}</span>
            <span class="trend-text">{{ Math.abs(availabilityChange) }}%</span>
          </div>
        </div>
      </div>
    </div>
    <!-- 趋势图表 -->
    <!-- 趋势分析图表 -->
    <div class="charts-section">
      <div class="chart-card">
        <div class="chart-header">MTBF趋势分析</div>
        <div class="chart-container">
          <div ref="mtbfChart" class="chart"></div>
      <div class="chart-container">
        <div class="chart-header">
          <h3>MTBF & MTTR 趋势分析</h3>
          <div class="chart-legend">
            <span class="legend-item mtbf-legend">
              <span class="legend-color"></span>
              MTBF (平均无故障时间)
            </span>
            <span class="legend-item mttr-legend">
              <span class="legend-color"></span>
              MTTR (平均修复时间)
            </span>
          </div>
        </div>
      </div>
      <div class="chart-card">
        <div class="chart-header">MTTR趋势分析</div>
        <div class="chart-container">
          <div ref="mttrChart" class="chart"></div>
        <div class="chart-wrapper">
          <div ref="trendChart" class="chart"></div>
        </div>
      </div>
    </div>
    <!-- 优化建议 -->
    <div class="recommendation-card">
      <div class="card-header">维保策略优化建议</div>
      <div class="recommendation-content">
    <!-- 维保策略建议 -->
    <div class="recommendations-section">
      <div class="section-header">
        <h3>维保策略优化建议</h3>
        <p>基于当前指标分析,为您提供针对性的优化建议</p>
      </div>
      <div class="recommendations-grid">
        <div 
          v-for="(recommendation, index) in recommendations" 
          :key="index"
          class="recommendation-item"
          class="recommendation-card"
        >
          <div class="recommendation-icon">💡</div>
          <div class="recommendation-text">{{ recommendation }}</div>
          <div class="recommendation-icon">{{ recommendation.icon }}</div>
          <div class="recommendation-content">
            <h4>{{ recommendation.title }}</h4>
            <p>{{ recommendation.description }}</p>
            <div class="recommendation-priority" :class="recommendation.priority">
              {{ recommendation.priorityText }}
            </div>
          </div>
        </div>
      </div>
    </div>
@@ -108,7 +113,7 @@
import { ref, reactive, onMounted, computed, nextTick } from 'vue'
import * as echarts from 'echarts'
// 模拟数据
// 生成模拟数据
const generateMockData = () => {
  const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
  const mtbfData = months.map(() => Math.floor(Math.random() * 200 + 300)) // 300-500小时
@@ -128,13 +133,12 @@
const mockData = reactive(generateMockData())
// 计算当前值(最新月份的数据)
// 计算当前指标值
const currentMTBF = computed(() => mockData.mtbfData[mockData.mtbfData.length - 1])
const currentMTTR = computed(() => mockData.mttrData[mockData.mttrData.length - 1])
const currentAvailability = computed(() => 
  Math.round((currentMTBF.value / (currentMTBF.value + currentMTTR.value)) * 100)
)
const currentFailures = computed(() => Math.floor(Math.random() * 10 + 5))
// 计算变化趋势
const mtbfChange = computed(() => {
@@ -158,114 +162,177 @@
  return Math.round(((current - previous) / previous) * 100)
})
const failureChange = computed(() => {
  const current = currentFailures.value
  const previous = Math.floor(Math.random() * 10 + 5)
  return Math.round(((current - previous) / previous) * 100)
})
// 趋势判断
// 趋势样式和文本
const mtbfTrendClass = computed(() => mtbfChange.value >= 0 ? 'trend-up' : 'trend-down')
const mttrTrendClass = computed(() => mttrChange.value <= 0 ? 'trend-up' : 'trend-down')
const availabilityTrendClass = computed(() => availabilityChange.value >= 0 ? 'trend-up' : 'trend-down')
const failureTrendClass = computed(() => failureChange.value <= 0 ? 'trend-up' : 'trend-down')
const mtbfTrendText = computed(() => mtbfChange.value >= 0 ? '↑' : '↓')
const mttrTrendText = computed(() => mttrChange.value <= 0 ? '↑' : '↓')
const availabilityTrendText = computed(() => availabilityChange.value >= 0 ? '↑' : '↓')
const failureTrendText = computed(() => failureChange.value <= 0 ? '↑' : '↓')
const mtbfTrendText = computed(() => mtbfChange.value >= 0 ? '↗' : '↘')
const mttrTrendText = computed(() => mttrChange.value <= 0 ? '↗' : '↘')
const availabilityTrendText = computed(() => availabilityChange.value >= 0 ? '↗' : '↘')
// 优化建议
// 智能维保建议
const recommendations = computed(() => {
  const suggestions = []
  
  // MTBF相关建议
  if (currentMTBF.value < 400) {
    suggestions.push('MTBF较低,建议加强预防性维护,定期检查关键部件')
    suggestions.push({
      icon: '🔧',
      title: '提升MTBF',
      description: '当前MTBF较低,建议加强预防性维护,定期检查关键部件,延长设备无故障运行时间',
      priority: 'high',
      priorityText: '高优先级'
    })
  }
  
  // MTTR相关建议
  if (currentMTTR.value > 8) {
    suggestions.push('MTTR较高,建议优化维修流程,提高维修效率')
    suggestions.push({
      icon: '⚡',
      title: '优化MTTR',
      description: '当前MTTR较高,建议优化维修流程,提高维修人员技能,缩短故障修复时间',
      priority: 'high',
      priorityText: '高优先级'
    })
  }
  
  // 可用率相关建议
  if (currentAvailability.value < 95) {
    suggestions.push('设备可用率有待提升,建议优化维保计划安排')
    suggestions.push({
      icon: '📈',
      title: '提升可用率',
      description: '设备可用率有待提升,建议优化维保计划安排,减少计划外停机时间',
      priority: 'medium',
      priorityText: '中优先级'
    })
  }
  
  if (currentFailures.value > 8) {
    suggestions.push('故障次数较多,建议加强设备日常巡检')
  // 综合建议
  if (currentMTBF.value >= 400 && currentMTTR.value <= 8 && currentAvailability.value >= 95) {
    suggestions.push({
      icon: '✅',
      title: '运行状况良好',
      description: '当前设备运行状况良好,各项指标均达到预期,建议继续保持现有维保策略',
      priority: 'low',
      priorityText: '低优先级'
    })
  }
  
  if (suggestions.length === 0) {
    suggestions.push('当前设备运行状况良好,继续保持现有维保策略')
  }
  // 预防性建议
  suggestions.push({
    icon: '📋',
    title: '预防性维护',
    description: '建议建立设备健康档案,定期分析设备运行数据,提前识别潜在故障风险',
    priority: 'medium',
    priorityText: '中优先级'
  })
  
  return suggestions
})
// 图表实例
let mtbfChart = null
let mttrChart = null
let trendChart = null
const initCharts = () => {
const initChart = () => {
  nextTick(() => {
    // MTBF图表
    mtbfChart = echarts.init(document.querySelector('.chart-card:first-child .chart'))
    mtbfChart.setOption({
    trendChart = echarts.init(document.querySelector('.chart'))
    trendChart.setOption({
      tooltip: {
        trigger: 'axis'
        trigger: 'axis',
        axisPointer: {
          type: 'cross'
        }
      },
      legend: {
        data: ['MTBF', 'MTTR'],
        top: 10
      },
      grid: {
        left: '3%',
        right: '4%',
        bottom: '3%',
        containLabel: true
      },
      xAxis: {
        type: 'category',
        data: mockData.months
      },
      yAxis: {
        type: 'value',
        name: '小时'
      },
      series: [{
        data: mockData.mtbfData,
        type: 'line',
        smooth: true,
        lineStyle: {
          color: '#1890ff'
        },
        areaStyle: {
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: 'rgba(24, 144, 255, 0.3)' },
            { offset: 1, color: 'rgba(24, 144, 255, 0.1)' }
          ])
        data: mockData.months,
        axisLine: {
          lineStyle: {
            color: '#e0e0e0'
          }
        }
      }]
    })
    // MTTR图表
    mttrChart = echarts.init(document.querySelector('.chart-card:last-child .chart'))
    mttrChart.setOption({
      tooltip: {
        trigger: 'axis'
      },
      xAxis: {
        type: 'category',
        data: mockData.months
      },
      yAxis: {
        type: 'value',
        name: '小时'
      },
      series: [{
        data: mockData.mttrData,
        type: 'line',
        smooth: true,
        lineStyle: {
          color: '#52c41a'
      yAxis: [
        {
          type: 'value',
          name: 'MTBF (小时)',
          position: 'left',
          axisLine: {
            lineStyle: {
              color: '#1890ff'
            }
          },
          axisLabel: {
            formatter: '{value}h'
          }
        },
        areaStyle: {
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: 'rgba(82, 196, 26, 0.3)' },
            { offset: 1, color: 'rgba(82, 196, 26, 0.1)' }
          ])
        {
          type: 'value',
          name: 'MTTR (小时)',
          position: 'right',
          axisLine: {
            lineStyle: {
              color: '#52c41a'
            }
          },
          axisLabel: {
            formatter: '{value}h'
          }
        }
      }]
      ],
      series: [
        {
          name: 'MTBF',
          type: 'line',
          yAxisIndex: 0,
          data: mockData.mtbfData,
          smooth: true,
          lineStyle: {
            color: '#1890ff',
            width: 3
          },
          itemStyle: {
            color: '#1890ff'
          },
          areaStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: 'rgba(24, 144, 255, 0.3)' },
              { offset: 1, color: 'rgba(24, 144, 255, 0.1)' }
            ])
          }
        },
        {
          name: 'MTTR',
          type: 'line',
          yAxisIndex: 1,
          data: mockData.mttrData,
          smooth: true,
          lineStyle: {
            color: '#52c41a',
            width: 3
          },
          itemStyle: {
            color: '#52c41a'
          },
          areaStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: 'rgba(82, 196, 26, 0.3)' },
              { offset: 1, color: 'rgba(82, 196, 26, 0.1)' }
            ])
          }
        }
      ]
    })
  })
}
@@ -275,34 +342,29 @@
  Object.assign(mockData, generateMockData())
  
  // 重新渲染图表
  if (mtbfChart && mttrChart) {
    mtbfChart.setOption({
  if (trendChart) {
    trendChart.setOption({
      xAxis: {
        data: mockData.months
      },
      series: [{
        data: mockData.mtbfData
      }]
    })
    mttrChart.setOption({
      xAxis: {
        data: mockData.months
      },
      series: [{
        data: mockData.mttrData
      }]
      series: [
        {
          data: mockData.mtbfData
        },
        {
          data: mockData.mttrData
        }
      ]
    })
  }
}
onMounted(() => {
  initCharts()
  initChart()
  
  // 监听窗口大小变化,重新调整图表大小
  window.addEventListener('resize', () => {
    if (mtbfChart) mtbfChart.resize()
    if (mttrChart) mttrChart.resize()
    if (trendChart) trendChart.resize()
  })
})
</script>
@@ -310,109 +372,197 @@
<style scoped>
.kpl-monitor-container {
  min-height: 100vh;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  padding: 20px;
  background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
  padding: 24px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* 页面头部 */
.page-header {
  background: rgba(255, 255, 255, 0.95);
  border-radius: 16px;
  padding: 20px;
  margin-bottom: 20px;
  background: white;
  border-radius: 12px;
  padding: 24px;
  margin-bottom: 24px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.page-header h1 {
.header-content h1 {
  margin: 0 0 8px 0;
  color: #1f2937;
  font-size: 28px;
  font-weight: 700;
}
.header-content p {
  margin: 0;
  color: #2c3e50;
  font-size: 24px;
  color: #6b7280;
  font-size: 16px;
}
.time-range {
  display: flex;
  align-items: center;
  gap: 10px;
  gap: 12px;
}
.metrics-cards {
/* 指标概览 */
.metrics-overview {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 20px;
  margin-bottom: 20px;
  margin-bottom: 24px;
}
.metric-card {
  background: rgba(255, 255, 255, 0.95);
  border-radius: 16px;
  padding: 20px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  background: white;
  border-radius: 12px;
  padding: 24px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  gap: 16px;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.metric-header {
.metric-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.metric-icon {
  font-size: 32px;
  width: 60px;
  height: 60px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
  justify-content: center;
  border-radius: 12px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.mtbf-card .metric-icon {
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.mttr-card .metric-icon {
  background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
.availability-card .metric-icon {
  background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
}
.metric-content {
  flex: 1;
}
.metric-title {
  font-size: 16px;
  color: #666;
  font-weight: 500;
}
.metric-trend {
  font-size: 14px;
  font-size: 18px;
  font-weight: 600;
  color: #1f2937;
  margin-bottom: 4px;
}
.trend-up {
  color: #52c41a;
}
.trend-down {
  color: #ff4d4f;
.metric-subtitle {
  font-size: 14px;
  color: #6b7280;
  margin-bottom: 12px;
}
.metric-value {
  font-size: 32px;
  font-weight: 700;
  color: #2c3e50;
  color: #1f2937;
  margin-bottom: 8px;
}
.metric-change {
.unit {
  font-size: 16px;
  font-weight: 500;
  color: #6b7280;
  margin-left: 4px;
}
.metric-trend {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 14px;
  color: #666;
  font-weight: 600;
}
.trend-up {
  color: #10b981;
}
.trend-down {
  color: #ef4444;
}
.trend-icon {
  font-size: 16px;
}
/* 图表区域 */
.charts-section {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
  gap: 20px;
  margin-bottom: 20px;
  margin-bottom: 24px;
}
.chart-card {
  background: rgba(255, 255, 255, 0.95);
  border-radius: 16px;
.chart-container {
  background: white;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  overflow: hidden;
}
.chart-header {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 16px 20px;
  font-weight: 500;
  font-size: 16px;
  padding: 20px 24px;
  border-bottom: 1px solid #e5e7eb;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.chart-container {
.chart-header h3 {
  margin: 0;
  color: #1f2937;
  font-size: 18px;
  font-weight: 600;
}
.chart-legend {
  display: flex;
  gap: 20px;
}
.legend-item {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: #6b7280;
}
.legend-color {
  width: 12px;
  height: 12px;
  border-radius: 2px;
}
.mtbf-legend .legend-color {
  background: #1890ff;
}
.mttr-legend .legend-color {
  background: #52c41a;
}
.chart-wrapper {
  padding: 20px;
  height: 300px;
  height: 400px;
}
.chart {
@@ -420,45 +570,107 @@
  height: 100%;
}
.recommendation-card {
  background: rgba(255, 255, 255, 0.95);
  border-radius: 16px;
/* 建议区域 */
.recommendations-section {
  background: white;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  overflow: hidden;
}
.card-header {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 16px 20px;
  font-weight: 500;
  font-size: 16px;
.section-header {
  padding: 24px;
  border-bottom: 1px solid #e5e7eb;
}
.recommendation-content {
  padding: 20px;
.section-header h3 {
  margin: 0 0 8px 0;
  color: #1f2937;
  font-size: 20px;
  font-weight: 600;
}
.recommendation-item {
  display: flex;
  align-items: flex-start;
  margin-bottom: 15px;
  padding: 12px;
  background: #f8f9fa;
.section-header p {
  margin: 0;
  color: #6b7280;
  font-size: 14px;
}
.recommendations-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
  gap: 20px;
  padding: 24px;
}
.recommendation-card {
  background: #f8fafc;
  border-radius: 8px;
  padding: 20px;
  display: flex;
  gap: 16px;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.recommendation-card:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.recommendation-icon {
  font-size: 18px;
  margin-right: 12px;
  margin-top: 2px;
  font-size: 24px;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  background: white;
  flex-shrink: 0;
}
.recommendation-text {
.recommendation-content {
  flex: 1;
  color: #2c3e50;
}
.recommendation-content h4 {
  margin: 0 0 8px 0;
  color: #1f2937;
  font-size: 16px;
  font-weight: 600;
}
.recommendation-content p {
  margin: 0 0 12px 0;
  color: #6b7280;
  font-size: 14px;
  line-height: 1.5;
}
.recommendation-priority {
  display: inline-block;
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 12px;
  font-weight: 500;
}
.priority.high {
  background: #fef2f2;
  color: #dc2626;
}
.priority.medium {
  background: #fffbeb;
  color: #d97706;
}
.priority.low {
  background: #f0fdf4;
  color: #16a34a;
}
/* 响应式设计 */
@media (max-width: 768px) {
  .kpl-monitor-container {
    padding: 16px;
@@ -466,20 +678,37 @@
  
  .page-header {
    flex-direction: column;
    gap: 15px;
    gap: 16px;
    align-items: stretch;
  }
  
  .metrics-cards {
  .metrics-overview {
    grid-template-columns: 1fr;
  }
  
  .charts-section {
  .recommendations-grid {
    grid-template-columns: 1fr;
  }
  
  .chart-container {
    height: 250px;
  .chart-wrapper {
    height: 300px;
  }
  .chart-legend {
    flex-direction: column;
    gap: 8px;
  }
}
@media (max-width: 480px) {
  .metric-card {
    flex-direction: column;
    text-align: center;
  }
  .recommendation-card {
    flex-direction: column;
    text-align: center;
  }
}
</style>