<template>
|
<div class="kpl-monitor-container">
|
<!-- 页面头部 -->
|
<div class="page-header">
|
<div class="header-content">
|
<h1>KPL监控分析</h1>
|
<p>设备关键性能指标监控与维保策略优化</p>
|
</div>
|
<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-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 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 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-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 class="chart-wrapper">
|
<div ref="trendChart" class="chart"></div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 维保策略建议 -->
|
<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-card"
|
>
|
<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>
|
</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 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 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 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({
|
icon: '🔧',
|
title: '提升MTBF',
|
description: '当前MTBF较低,建议加强预防性维护,定期检查关键部件,延长设备无故障运行时间',
|
priority: 'high',
|
priorityText: '高优先级'
|
})
|
}
|
|
// MTTR相关建议
|
if (currentMTTR.value > 8) {
|
suggestions.push({
|
icon: '⚡',
|
title: '优化MTTR',
|
description: '当前MTTR较高,建议优化维修流程,提高维修人员技能,缩短故障修复时间',
|
priority: 'high',
|
priorityText: '高优先级'
|
})
|
}
|
|
// 可用率相关建议
|
if (currentAvailability.value < 95) {
|
suggestions.push({
|
icon: '📈',
|
title: '提升可用率',
|
description: '设备可用率有待提升,建议优化维保计划安排,减少计划外停机时间',
|
priority: 'medium',
|
priorityText: '中优先级'
|
})
|
}
|
|
// 综合建议
|
if (currentMTBF.value >= 400 && currentMTTR.value <= 8 && currentAvailability.value >= 95) {
|
suggestions.push({
|
icon: '✅',
|
title: '运行状况良好',
|
description: '当前设备运行状况良好,各项指标均达到预期,建议继续保持现有维保策略',
|
priority: 'low',
|
priorityText: '低优先级'
|
})
|
}
|
|
// 预防性建议
|
suggestions.push({
|
icon: '📋',
|
title: '预防性维护',
|
description: '建议建立设备健康档案,定期分析设备运行数据,提前识别潜在故障风险',
|
priority: 'medium',
|
priorityText: '中优先级'
|
})
|
|
return suggestions
|
})
|
|
// 图表实例
|
let trendChart = null
|
|
const initChart = () => {
|
nextTick(() => {
|
trendChart = echarts.init(document.querySelector('.chart'))
|
trendChart.setOption({
|
tooltip: {
|
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,
|
axisLine: {
|
lineStyle: {
|
color: '#e0e0e0'
|
}
|
}
|
},
|
yAxis: [
|
{
|
type: 'value',
|
name: 'MTBF (小时)',
|
position: 'left',
|
axisLine: {
|
lineStyle: {
|
color: '#1890ff'
|
}
|
},
|
axisLabel: {
|
formatter: '{value}h'
|
}
|
},
|
{
|
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)' }
|
])
|
}
|
}
|
]
|
})
|
})
|
}
|
|
const fetchKPLData = () => {
|
// 模拟数据刷新
|
Object.assign(mockData, generateMockData())
|
|
// 重新渲染图表
|
if (trendChart) {
|
trendChart.setOption({
|
xAxis: {
|
data: mockData.months
|
},
|
series: [
|
{
|
data: mockData.mtbfData
|
},
|
{
|
data: mockData.mttrData
|
}
|
]
|
})
|
}
|
}
|
|
onMounted(() => {
|
initChart()
|
|
// 监听窗口大小变化,重新调整图表大小
|
window.addEventListener('resize', () => {
|
if (trendChart) trendChart.resize()
|
})
|
})
|
</script>
|
|
<style scoped>
|
.kpl-monitor-container {
|
min-height: 100vh;
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
padding: 24px;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
}
|
|
/* 页面头部 */
|
.page-header {
|
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;
|
}
|
|
.header-content h1 {
|
margin: 0 0 8px 0;
|
color: #1f2937;
|
font-size: 28px;
|
font-weight: 700;
|
}
|
|
.header-content p {
|
margin: 0;
|
color: #6b7280;
|
font-size: 16px;
|
}
|
|
.time-range {
|
display: flex;
|
align-items: center;
|
gap: 12px;
|
}
|
|
/* 指标概览 */
|
.metrics-overview {
|
display: grid;
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
gap: 20px;
|
margin-bottom: 24px;
|
}
|
|
.metric-card {
|
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-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;
|
align-items: center;
|
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: 18px;
|
font-weight: 600;
|
color: #1f2937;
|
margin-bottom: 4px;
|
}
|
|
.metric-subtitle {
|
font-size: 14px;
|
color: #6b7280;
|
margin-bottom: 12px;
|
}
|
|
.metric-value {
|
font-size: 32px;
|
font-weight: 700;
|
color: #1f2937;
|
margin-bottom: 8px;
|
}
|
|
.unit {
|
font-size: 16px;
|
font-weight: 500;
|
color: #6b7280;
|
margin-left: 4px;
|
}
|
|
.metric-trend {
|
display: flex;
|
align-items: center;
|
gap: 4px;
|
font-size: 14px;
|
font-weight: 600;
|
}
|
|
.trend-up {
|
color: #10b981;
|
}
|
|
.trend-down {
|
color: #ef4444;
|
}
|
|
.trend-icon {
|
font-size: 16px;
|
}
|
|
/* 图表区域 */
|
.charts-section {
|
margin-bottom: 24px;
|
}
|
|
.chart-container {
|
background: white;
|
border-radius: 12px;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
overflow: hidden;
|
}
|
|
.chart-header {
|
padding: 20px 24px;
|
border-bottom: 1px solid #e5e7eb;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.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: 400px;
|
}
|
|
.chart {
|
width: 100%;
|
height: 100%;
|
}
|
|
/* 建议区域 */
|
.recommendations-section {
|
background: white;
|
border-radius: 12px;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
overflow: hidden;
|
}
|
|
.section-header {
|
padding: 24px;
|
border-bottom: 1px solid #e5e7eb;
|
}
|
|
.section-header h3 {
|
margin: 0 0 8px 0;
|
color: #1f2937;
|
font-size: 20px;
|
font-weight: 600;
|
}
|
|
.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: 24px;
|
width: 40px;
|
height: 40px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
border-radius: 8px;
|
background: white;
|
flex-shrink: 0;
|
}
|
|
.recommendation-content {
|
flex: 1;
|
}
|
|
.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;
|
}
|
|
.page-header {
|
flex-direction: column;
|
gap: 16px;
|
align-items: stretch;
|
}
|
|
.metrics-overview {
|
grid-template-columns: 1fr;
|
}
|
|
.recommendations-grid {
|
grid-template-columns: 1fr;
|
}
|
|
.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>
|