| | |
| | | <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" |
| | |
| | | </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 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-card"> |
| | | <div class="chart-header">MTBF趋势分析</div> |
| | | <div class="chart-container"> |
| | | <div ref="mtbfChart" class="chart"></div> |
| | | <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-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> |
| | |
| | | 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 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(() => { |
| | |
| | | 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 |
| | | data: mockData.months, |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#e0e0e0' |
| | | } |
| | | } |
| | | }, |
| | | yAxis: { |
| | | yAxis: [ |
| | | { |
| | | type: 'value', |
| | | name: '小时' |
| | | name: 'MTBF (小时)', |
| | | position: 'left', |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#1890ff' |
| | | } |
| | | }, |
| | | series: [{ |
| | | data: mockData.mtbfData, |
| | | 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: { |
| | |
| | | { 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, |
| | | { |
| | | name: 'MTTR', |
| | | type: 'line', |
| | | yAxisIndex: 1, |
| | | data: mockData.mttrData, |
| | | smooth: true, |
| | | lineStyle: { |
| | | color: '#52c41a', |
| | | width: 3 |
| | | }, |
| | | itemStyle: { |
| | | color: '#52c41a' |
| | | }, |
| | | areaStyle: { |
| | |
| | | { offset: 1, color: 'rgba(82, 196, 26, 0.1)' } |
| | | ]) |
| | | } |
| | | }] |
| | | } |
| | | ] |
| | | }) |
| | | }) |
| | | } |
| | |
| | | Object.assign(mockData, generateMockData()) |
| | | |
| | | // 重新渲染图表 |
| | | if (mtbfChart && mttrChart) { |
| | | mtbfChart.setOption({ |
| | | if (trendChart) { |
| | | trendChart.setOption({ |
| | | xAxis: { |
| | | data: mockData.months |
| | | }, |
| | | series: [{ |
| | | series: [ |
| | | { |
| | | data: mockData.mtbfData |
| | | }] |
| | | }) |
| | | |
| | | mttrChart.setOption({ |
| | | xAxis: { |
| | | data: mockData.months |
| | | }, |
| | | series: [{ |
| | | { |
| | | data: mockData.mttrData |
| | | }] |
| | | } |
| | | ] |
| | | }) |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | | initCharts() |
| | | initChart() |
| | | |
| | | // 监听窗口大小变化,重新调整图表大小 |
| | | window.addEventListener('resize', () => { |
| | | if (mtbfChart) mtbfChart.resize() |
| | | if (mttrChart) mttrChart.resize() |
| | | if (trendChart) trendChart.resize() |
| | | }) |
| | | }) |
| | | </script> |
| | |
| | | <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 { |
| | |
| | | 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; |
| | |
| | | |
| | | .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> |