From 1c0863efe062af3ebcdecb8c10568d779f5c8295 Mon Sep 17 00:00:00 2001
From: zouyu <2723363702@qq.com>
Date: 星期一, 26 一月 2026 15:10:55 +0800
Subject: [PATCH] Merge remote-tracking branch 'refs/remotes/origin/dev_New' into dev_tide_mis_xindao
---
src/views/equipmentManagement/kplMonitor/index.vue | 714 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 714 insertions(+), 0 deletions(-)
diff --git a/src/views/equipmentManagement/kplMonitor/index.vue b/src/views/equipmentManagement/kplMonitor/index.vue
new file mode 100644
index 0000000..178b658
--- /dev/null
+++ b/src/views/equipmentManagement/kplMonitor/index.vue
@@ -0,0 +1,714 @@
+<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: '寤鸿寤虹珛璁惧鍋ュ悍妗f锛屽畾鏈熷垎鏋愯澶囪繍琛屾暟鎹紝鎻愬墠璇嗗埆娼滃湪鏁呴殰椋庨櫓',
+ 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>
\ No newline at end of file
--
Gitblit v1.9.3