gaoluyang
2025-09-25 bb175f417a29f1e9b41533df9e8239f25ab071b8
部署修改
已添加6个文件
已修改1个文件
495 ■■■■■ 文件已修改
multiple/assets/favicon/JLMYico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/JLSNico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/JLMYLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/JLSNLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/JLSNView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/config.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/kplMonitor/index.vue 485 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/JLMYico.ico
multiple/assets/favicon/JLSNico.ico
multiple/assets/logo/JLMYLogo.png
multiple/assets/logo/JLSNLogo.png
multiple/assets/screen/JLSNView.png
multiple/config.json
@@ -187,6 +187,16 @@
    "logo": "logo/HCKXLogo.png",
    "favicon": "favicon/HCKXico.ico"
  },
  "JLSN": {
    "env": {
      "VITE_APP_TITLE": "锦龙水泥信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9094",
      "VITE_JAVA_API": "http://114.132.189.42:9093"
    },
    "screen": "screen/JLSNView.png",
    "logo": "logo/JLSNLogo.png",
    "favicon": "favicon/JLSNico.ico"
  },
  "screen": "/src/assets/images/login-background.png",
  "logo": "/src/assets/logo/logo.png",
  "favicon": "/public/favicon.ico"
src/views/equipmentManagement/kplMonitor/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,485 @@
<template>
  <div class="kpl-monitor-container">
    <div class="page-header">
      <h1>KPL监控分析</h1>
      <div class="time-range">
        <el-date-picker
          v-model="timeRange"
          type="daterange"
          range-separator="至"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          value-format="YYYY-MM-DD"
          @change="fetchKPLData"
        />
      </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>
      </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>
      </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>
      </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>
      </div>
      <div class="chart-card">
        <div class="chart-header">MTTR趋势分析</div>
        <div class="chart-container">
          <div ref="mttrChart" class="chart"></div>
        </div>
      </div>
    </div>
    <!-- ä¼˜åŒ–建议 -->
    <div class="recommendation-card">
      <div class="card-header">维保策略优化建议</div>
      <div class="recommendation-content">
        <div
          v-for="(recommendation, index) in recommendations"
          :key="index"
          class="recommendation-item"
        >
          <div class="recommendation-icon">💡</div>
          <div class="recommendation-text">{{ recommendation }}</div>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
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小时
  const mttrData = months.map(() => Math.floor(Math.random() * 8 + 4)) // 4-12小时
  return {
    months,
    mtbfData,
    mttrData
  }
}
const timeRange = ref([
  new Date(new Date().getFullYear(), 0, 1).toISOString().split('T')[0],
  new Date().toISOString().split('T')[0]
])
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(() => {
  const current = currentMTBF.value
  const previous = mockData.mtbfData[mockData.mtbfData.length - 2] || current
  return Math.round(((current - previous) / previous) * 100)
})
const mttrChange = computed(() => {
  const current = currentMTTR.value
  const previous = mockData.mttrData[mockData.mttrData.length - 2] || current
  return Math.round(((current - previous) / previous) * 100)
})
const availabilityChange = computed(() => {
  const current = currentAvailability.value
  const previous = Math.round(
    (mockData.mtbfData[mockData.mtbfData.length - 2] /
    (mockData.mtbfData[mockData.mtbfData.length - 2] + mockData.mttrData[mockData.mttrData.length - 2])) * 100
  ) || current
  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 recommendations = computed(() => {
  const suggestions = []
  if (currentMTBF.value < 400) {
    suggestions.push('MTBF较低,建议加强预防性维护,定期检查关键部件')
  }
  if (currentMTTR.value > 8) {
    suggestions.push('MTTR较高,建议优化维修流程,提高维修效率')
  }
  if (currentAvailability.value < 95) {
    suggestions.push('设备可用率有待提升,建议优化维保计划安排')
  }
  if (currentFailures.value > 8) {
    suggestions.push('故障次数较多,建议加强设备日常巡检')
  }
  if (suggestions.length === 0) {
    suggestions.push('当前设备运行状况良好,继续保持现有维保策略')
  }
  return suggestions
})
// å›¾è¡¨å®žä¾‹
let mtbfChart = null
let mttrChart = null
const initCharts = () => {
  nextTick(() => {
    // MTBF图表
    mtbfChart = echarts.init(document.querySelector('.chart-card:first-child .chart'))
    mtbfChart.setOption({
      tooltip: {
        trigger: 'axis'
      },
      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)' }
          ])
        }
      }]
    })
    // 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'
        },
        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)' }
          ])
        }
      }]
    })
  })
}
const fetchKPLData = () => {
  // æ¨¡æ‹Ÿæ•°æ®åˆ·æ–°
  Object.assign(mockData, generateMockData())
  // é‡æ–°æ¸²æŸ“图表
  if (mtbfChart && mttrChart) {
    mtbfChart.setOption({
      xAxis: {
        data: mockData.months
      },
      series: [{
        data: mockData.mtbfData
      }]
    })
    mttrChart.setOption({
      xAxis: {
        data: mockData.months
      },
      series: [{
        data: mockData.mttrData
      }]
    })
  }
}
onMounted(() => {
  initCharts()
  // ç›‘听窗口大小变化,重新调整图表大小
  window.addEventListener('resize', () => {
    if (mtbfChart) mtbfChart.resize()
    if (mttrChart) mttrChart.resize()
  })
})
</script>
<style scoped>
.kpl-monitor-container {
  min-height: 100vh;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  padding: 20px;
  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;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.page-header h1 {
  margin: 0;
  color: #2c3e50;
  font-size: 24px;
}
.time-range {
  display: flex;
  align-items: center;
  gap: 10px;
}
.metrics-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 20px;
  margin-bottom: 20px;
}
.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);
}
.metric-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
}
.metric-title {
  font-size: 16px;
  color: #666;
  font-weight: 500;
}
.metric-trend {
  font-size: 14px;
  font-weight: 600;
}
.trend-up {
  color: #52c41a;
}
.trend-down {
  color: #ff4d4f;
}
.metric-value {
  font-size: 32px;
  font-weight: 700;
  color: #2c3e50;
  margin-bottom: 8px;
}
.metric-change {
  font-size: 14px;
  color: #666;
}
.charts-section {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
  gap: 20px;
  margin-bottom: 20px;
}
.chart-card {
  background: rgba(255, 255, 255, 0.95);
  border-radius: 16px;
  overflow: hidden;
}
.chart-header {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 16px 20px;
  font-weight: 500;
  font-size: 16px;
}
.chart-container {
  padding: 20px;
  height: 300px;
}
.chart {
  width: 100%;
  height: 100%;
}
.recommendation-card {
  background: rgba(255, 255, 255, 0.95);
  border-radius: 16px;
  overflow: hidden;
}
.card-header {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 16px 20px;
  font-weight: 500;
  font-size: 16px;
}
.recommendation-content {
  padding: 20px;
}
.recommendation-item {
  display: flex;
  align-items: flex-start;
  margin-bottom: 15px;
  padding: 12px;
  background: #f8f9fa;
  border-radius: 8px;
}
.recommendation-icon {
  font-size: 18px;
  margin-right: 12px;
  margin-top: 2px;
}
.recommendation-text {
  flex: 1;
  color: #2c3e50;
  line-height: 1.5;
}
@media (max-width: 768px) {
  .kpl-monitor-container {
    padding: 16px;
  }
  .page-header {
    flex-direction: column;
    gap: 15px;
    align-items: stretch;
  }
  .metrics-cards {
    grid-template-columns: 1fr;
  }
  .charts-section {
    grid-template-columns: 1fr;
  }
  .chart-container {
    height: 250px;
  }
}
</style>