|
|
@@ -0,0 +1,4852 @@
|
|
|
+<!--
|
|
|
+ 农机监控汇总页面
|
|
|
+ 功能:实时显示所有农机设备状态、位置分布、作业统计等
|
|
|
+-->
|
|
|
+<template>
|
|
|
+ <div class="machines-monitor-container">
|
|
|
+
|
|
|
+
|
|
|
+ <!-- 控制面板 -->
|
|
|
+ <div class="control-panel">
|
|
|
+ <div class="control-left">
|
|
|
+ <div class="form-group">
|
|
|
+ <label>设备类型</label>
|
|
|
+ <select v-model="filters.machineType" @change="applyFilters">
|
|
|
+ <option value="">全部类型</option>
|
|
|
+ <option value="tractor">拖拉机</option>
|
|
|
+ <option value="harvester">收割机</option>
|
|
|
+ <option value="seeder">播种机</option>
|
|
|
+ <option value="sprayer">喷药机</option>
|
|
|
+ <option value="cultivator">耕地机</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="form-group">
|
|
|
+ <label>农场选择</label>
|
|
|
+ <select v-model="filters.farmId" @change="applyFilters">
|
|
|
+ <option value="">全部农场</option>
|
|
|
+ <option v-for="farm in farmList" :key="farm.id" :value="farm.id">{{ farm.name }}</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="form-group">
|
|
|
+ <label>时间范围</label>
|
|
|
+ <select v-model="timeRange" @change="onTimeRangeChange">
|
|
|
+ <option value="realtime">实时数据</option>
|
|
|
+ <option value="1h">近1小时</option>
|
|
|
+ <option value="24h">近24小时</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="control-right">
|
|
|
+ <button class="refresh-btn" @click="refreshData">
|
|
|
+ <i class="el-icon-refresh"></i>
|
|
|
+ 刷新数据
|
|
|
+ </button>
|
|
|
+ <div class="auto-refresh">
|
|
|
+ <span>自动刷新:</span>
|
|
|
+ <select v-model="settings.refreshInterval" class="auto-refresh-select" @change="restartAutoRefresh">
|
|
|
+ <option value="30">30秒</option>
|
|
|
+ <option value="60">1分钟</option>
|
|
|
+ <option value="120">2分钟</option>
|
|
|
+ <option value="300">5分钟</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="last-update">
|
|
|
+ 最后更新:<span>刚刚</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 统计信息卡片 -->
|
|
|
+ <el-row :gutter="20" class="stats-row">
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card total" shadow="hover">
|
|
|
+ <div class="stat-content">
|
|
|
+ <div class="stat-label">农机总数</div>
|
|
|
+ <div class="stat-value">{{ statistics.total }}</div>
|
|
|
+ <div class="stat-trend">
|
|
|
+ <i class="el-icon-info"></i>
|
|
|
+ <span>系统注册设备</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card online" shadow="hover">
|
|
|
+ <div class="stat-content">
|
|
|
+ <div class="stat-label">在线设备</div>
|
|
|
+ <div class="stat-value">{{ statistics.online }}</div>
|
|
|
+ <div class="stat-trend">
|
|
|
+ <i class="el-icon-top" style="color: #67C23A;"></i>
|
|
|
+ <span>在线率 {{ onlineRate }}%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card working" shadow="hover">
|
|
|
+ <div class="stat-content">
|
|
|
+ <div class="stat-label">作业中</div>
|
|
|
+ <div class="stat-value">{{ statistics.working }}</div>
|
|
|
+ <div class="stat-trend">
|
|
|
+ <i class="el-icon-video-play"></i>
|
|
|
+ <span>正在执行任务</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card warning" shadow="hover">
|
|
|
+ <div class="stat-content">
|
|
|
+ <div class="stat-label">告警设备</div>
|
|
|
+ <div class="stat-value">{{ statistics.warning }}</div>
|
|
|
+ <div class="stat-trend">
|
|
|
+ <i class="el-icon-warning" style="color: #F56C6C;"></i>
|
|
|
+ <span>需要关注</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 保养统计信息卡片 -->
|
|
|
+ <el-row :gutter="20" class="stats-row">
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card maintenance-due" shadow="hover">
|
|
|
+ <div class="stat-content">
|
|
|
+ <div class="stat-label">待保养设备</div>
|
|
|
+ <div class="stat-value">{{ maintenanceStats.due }}</div>
|
|
|
+ <div class="stat-trend">
|
|
|
+ <i class="el-icon-time" style="color: #E6A23C;"></i>
|
|
|
+ <span>本周到期</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card maintenance-progress" shadow="hover">
|
|
|
+ <div class="stat-content">
|
|
|
+ <div class="stat-label">保养进行中</div>
|
|
|
+ <div class="stat-value">{{ maintenanceStats.inProgress }}</div>
|
|
|
+ <div class="stat-trend">
|
|
|
+ <i class="el-icon-setting" style="color: #409EFF;"></i>
|
|
|
+ <span>正在维护</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card maintenance-overdue" shadow="hover">
|
|
|
+ <div class="stat-content">
|
|
|
+ <div class="stat-label">保养超期</div>
|
|
|
+ <div class="stat-value">{{ maintenanceStats.overdue }}</div>
|
|
|
+ <div class="stat-trend">
|
|
|
+ <i class="el-icon-warning" style="color: #F56C6C;"></i>
|
|
|
+ <span>需紧急处理</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card maintenance-cost" shadow="hover">
|
|
|
+ <div class="stat-content">
|
|
|
+ <div class="stat-label">本月保养费用</div>
|
|
|
+ <div class="stat-value">¥{{ formatCurrency(maintenanceStats.monthlyCost) }}</div>
|
|
|
+ <div class="stat-trend">
|
|
|
+ <i class="el-icon-bottom" style="color: #67C23A;"></i>
|
|
|
+ <span>较上月 -12%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 地图区域 -->
|
|
|
+ <el-card class="map-card" shadow="hover">
|
|
|
+ <div slot="header" class="map-header">
|
|
|
+ <span class="map-title">农机位置分布图</span>
|
|
|
+ <div class="map-controls">
|
|
|
+ <el-button-group>
|
|
|
+ <el-button size="small" @click="zoomIn" icon="el-icon-zoom-in"></el-button>
|
|
|
+ <el-button size="small" @click="zoomOut" icon="el-icon-zoom-out"></el-button>
|
|
|
+ <el-button size="small" @click="centerMap" icon="el-icon-position"></el-button>
|
|
|
+ <el-button size="small" @click="toggleMapFullscreen" icon="el-icon-full-screen"></el-button>
|
|
|
+ </el-button-group>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="map-container" ref="mapContainer">
|
|
|
+ <div v-if="mapLoading" class="map-loading">
|
|
|
+ <i class="el-icon-loading"></i>
|
|
|
+ <div>地图加载中...</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 高德地图容器 -->
|
|
|
+ <div v-else id="amap-container" ref="amapContainer" class="amap-container"></div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 作业统计分析 -->
|
|
|
+ <el-card class="analysis-card" shadow="hover">
|
|
|
+ <div slot="header" class="analysis-header">
|
|
|
+ <div class="header-left">
|
|
|
+ <h3 class="analysis-title">农机作业统计分析</h3>
|
|
|
+ <p class="analysis-subtitle">数据来源:农机作业实时上报</p>
|
|
|
+ </div>
|
|
|
+ <div class="header-right">
|
|
|
+ <el-select v-model="analysisTimeRange" placeholder="选择时间范围" @change="updateAnalysisData">
|
|
|
+ <el-option label="最近7天" value="7"></el-option>
|
|
|
+ <el-option label="最近30天" value="30"></el-option>
|
|
|
+ <el-option label="最近90天" value="90"></el-option>
|
|
|
+ </el-select>
|
|
|
+ <el-dropdown @command="handleExport" style="margin-left: 12px;">
|
|
|
+ <el-button type="primary">
|
|
|
+ 导出<i class="el-icon-arrow-down el-icon--right"></i>
|
|
|
+ </el-button>
|
|
|
+ <el-dropdown-menu slot="dropdown">
|
|
|
+ <el-dropdown-item command="image">导出图片</el-dropdown-item>
|
|
|
+ <el-dropdown-item command="excel">导出Excel</el-dropdown-item>
|
|
|
+ <el-dropdown-item command="pdf">导出PDF</el-dropdown-item>
|
|
|
+ </el-dropdown-menu>
|
|
|
+ </el-dropdown>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 核心指标卡片 -->
|
|
|
+ <el-row :gutter="24" class="metrics-row">
|
|
|
+ <el-col :span="8">
|
|
|
+ <div class="work-metric-card area-metric">
|
|
|
+ <div class="metric-header">
|
|
|
+ <div class="metric-icon-wrapper">
|
|
|
+ <i class="el-icon-crop"></i>
|
|
|
+ </div>
|
|
|
+ <div class="metric-trend-badge positive">
|
|
|
+ <i class="el-icon-top"></i>
|
|
|
+ <span>+12.5%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="metric-body">
|
|
|
+ <div class="metric-value">{{ workAnalysis.totalArea }} <span class="metric-unit">亩</span></div>
|
|
|
+ <div class="metric-label">总作业面积</div>
|
|
|
+ <div class="metric-desc">累计完成作业面积</div>
|
|
|
+ </div>
|
|
|
+ <div class="metric-footer">
|
|
|
+ <div class="progress-bar">
|
|
|
+ <div class="progress-fill" style="width: 85%"></div>
|
|
|
+ </div>
|
|
|
+ <span class="progress-text">完成率 85%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <div class="work-metric-card time-metric">
|
|
|
+ <div class="metric-header">
|
|
|
+ <div class="metric-icon-wrapper">
|
|
|
+ <i class="el-icon-time"></i>
|
|
|
+ </div>
|
|
|
+ <div class="metric-trend-badge positive">
|
|
|
+ <i class="el-icon-top"></i>
|
|
|
+ <span>+8.3%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="metric-body">
|
|
|
+ <div class="metric-value">{{ workAnalysis.totalHours }} <span class="metric-unit">小时</span></div>
|
|
|
+ <div class="metric-label">总作业时长</div>
|
|
|
+ <div class="metric-desc">设备累计工作时间</div>
|
|
|
+ </div>
|
|
|
+ <div class="metric-footer">
|
|
|
+ <div class="progress-bar">
|
|
|
+ <div class="progress-fill" style="width: 72%"></div>
|
|
|
+ </div>
|
|
|
+ <span class="progress-text">利用率 72%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <div class="work-metric-card efficiency-metric">
|
|
|
+ <div class="metric-header">
|
|
|
+ <div class="metric-icon-wrapper">
|
|
|
+ <i class="el-icon-lightning"></i>
|
|
|
+ </div>
|
|
|
+ <div class="metric-trend-badge positive">
|
|
|
+ <i class="el-icon-top"></i>
|
|
|
+ <span>+5.7%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="metric-body">
|
|
|
+ <div class="metric-value">{{ workAnalysis.avgEfficiency }} <span class="metric-unit">亩/小时</span></div>
|
|
|
+ <div class="metric-label">平均作业效率</div>
|
|
|
+ <div class="metric-desc">单位时间作业面积</div>
|
|
|
+ </div>
|
|
|
+ <div class="metric-footer">
|
|
|
+ <div class="progress-bar">
|
|
|
+ <div class="progress-fill" style="width: 92%"></div>
|
|
|
+ </div>
|
|
|
+ <span class="progress-text">效率等级 A+</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 图表区域 -->
|
|
|
+ <el-row :gutter="24" class="charts-row">
|
|
|
+ <el-col :span="12">
|
|
|
+ <div class="chart-card work-chart-card">
|
|
|
+ <div class="chart-header">
|
|
|
+ <h4 class="chart-title">农机类型作业占比</h4>
|
|
|
+ </div>
|
|
|
+ <div class="chart-content chart-flex-content">
|
|
|
+ <div class="work-donut-chart-horizontal">
|
|
|
+ <!-- 左侧:饼图区域 -->
|
|
|
+ <div class="work-donut-container-side">
|
|
|
+ <svg width="280" height="280" viewBox="0 0 280 280">
|
|
|
+ <!-- 环形图 -->
|
|
|
+ <circle cx="140" cy="140" r="85" fill="none" stroke="#E5F4E8" stroke-width="35"/>
|
|
|
+ <circle cx="140" cy="140" r="85" fill="none" stroke="#3BB44A" stroke-width="35"
|
|
|
+ stroke-dasharray="267 534" stroke-dashoffset="0" transform="rotate(-90 140 140)"
|
|
|
+ class="work-segment" data-type="tractor"
|
|
|
+ />
|
|
|
+ <circle cx="140" cy="140" r="85" fill="none" stroke="#66C574" stroke-width="35"
|
|
|
+ stroke-dasharray="160 534" stroke-dashoffset="-267" transform="rotate(-90 140 140)"
|
|
|
+ class="work-segment" data-type="harvester"
|
|
|
+ />
|
|
|
+ <circle cx="140" cy="140" r="85" fill="none" stroke="#99D6A3" stroke-width="35"
|
|
|
+ stroke-dasharray="107 534" stroke-dashoffset="-427" transform="rotate(-90 140 140)"
|
|
|
+ class="work-segment" data-type="seeder"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 中心文字 -->
|
|
|
+ <text x="140" y="125" text-anchor="middle" class="work-donut-center-label">总台数</text>
|
|
|
+ <text x="140" y="155" text-anchor="middle" class="work-donut-center-value">28</text>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <!-- 右侧:图例区域 -->
|
|
|
+ <div class="work-donut-legend-side">
|
|
|
+ <div class="work-legend-item-enhanced" data-type="tractor" style="--index: 0;">
|
|
|
+ <div class="work-legend-color" style="background-color: #3BB44A;"></div>
|
|
|
+ <div class="work-legend-content-enhanced">
|
|
|
+ <span class="work-legend-label">拖拉机</span>
|
|
|
+ <span class="work-legend-value">50%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="work-legend-item-enhanced" data-type="harvester" style="--index: 1;">
|
|
|
+ <div class="work-legend-color" style="background-color: #66C574;"></div>
|
|
|
+ <div class="work-legend-content-enhanced">
|
|
|
+ <span class="work-legend-label">收割机</span>
|
|
|
+ <span class="work-legend-value">30%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="work-legend-item-enhanced" data-type="seeder" style="--index: 2;">
|
|
|
+ <div class="work-legend-color" style="background-color: #99D6A3;"></div>
|
|
|
+ <div class="work-legend-content-enhanced">
|
|
|
+ <span class="work-legend-label">播种机</span>
|
|
|
+ <span class="work-legend-value">20%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <div class="chart-card work-chart-card">
|
|
|
+ <div class="chart-header">
|
|
|
+ <h4 class="chart-title">作业变化趋势</h4>
|
|
|
+ <div class="chart-controls">
|
|
|
+ <el-radio-group v-model="trendMetric" size="small" @change="updateTrendChart">
|
|
|
+ <el-radio-button label="area">面积</el-radio-button>
|
|
|
+ <el-radio-button label="time">时长</el-radio-button>
|
|
|
+ <el-radio-button label="efficiency">效率</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="chart-content chart-flex-content">
|
|
|
+ <div class="work-trend-chart">
|
|
|
+ <div class="work-chart-area">
|
|
|
+ <div class="work-chart-unit-label">单位:亩</div>
|
|
|
+ <svg width="100%" height="240" viewBox="0 0 600 240">
|
|
|
+ <!-- 网格线 -->
|
|
|
+ <defs>
|
|
|
+ <pattern id="grid" width="60" height="60" patternUnits="userSpaceOnUse">
|
|
|
+ <path d="M 60 0 L 0 0 0 60" fill="none" stroke="#F0F4F1" stroke-width="1"/>
|
|
|
+ </pattern>
|
|
|
+ <linearGradient id="trendGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
|
+ <stop offset="0%" style="stop-color:#3BB44A;stop-opacity:0.3"/>
|
|
|
+ <stop offset="100%" style="stop-color:#3BB44A;stop-opacity:0"/>
|
|
|
+ </linearGradient>
|
|
|
+ </defs>
|
|
|
+ <rect width="100%" height="100%" fill="url(#grid)"/>
|
|
|
+ <!-- 趋势面积 -->
|
|
|
+ <path d="M40,220 L100,170 L160,140 L220,110 L280,130 L340,100 L400,75 L460,60 L520,45 L580,35 L580,240 L40,240 Z"
|
|
|
+ fill="url(#trendGradient)"/>
|
|
|
+ <!-- 趋势线 -->
|
|
|
+ <polyline points="40,220 100,170 160,140 220,110 280,130 340,100 400,75 460,60 520,45 580,35"
|
|
|
+ fill="none" stroke="#3BB44A" stroke-width="4"/>
|
|
|
+ <!-- 数据点 -->
|
|
|
+ <circle cx="40" cy="220" r="6" fill="#3BB44A" class="data-point" style="--index: 0;"
|
|
|
+ @mouseenter="showDataTooltip($event, 0)" @mouseleave="hideDataTooltip"/>
|
|
|
+ <circle cx="100" cy="170" r="6" fill="#3BB44A" class="data-point" style="--index: 1;"
|
|
|
+ @mouseenter="showDataTooltip($event, 1)" @mouseleave="hideDataTooltip"/>
|
|
|
+ <circle cx="160" cy="140" r="6" fill="#3BB44A" class="data-point" style="--index: 2;"
|
|
|
+ @mouseenter="showDataTooltip($event, 2)" @mouseleave="hideDataTooltip"/>
|
|
|
+ <circle cx="220" cy="110" r="6" fill="#3BB44A" class="data-point" style="--index: 3;"
|
|
|
+ @mouseenter="showDataTooltip($event, 3)" @mouseleave="hideDataTooltip"/>
|
|
|
+ <circle cx="280" cy="130" r="6" fill="#3BB44A" class="data-point" style="--index: 4;"
|
|
|
+ @mouseenter="showDataTooltip($event, 4)" @mouseleave="hideDataTooltip"/>
|
|
|
+ <circle cx="340" cy="100" r="6" fill="#3BB44A" class="data-point" style="--index: 5;"
|
|
|
+ @mouseenter="showDataTooltip($event, 5)" @mouseleave="hideDataTooltip"/>
|
|
|
+ <circle cx="400" cy="75" r="6" fill="#3BB44A" class="data-point" style="--index: 6;"
|
|
|
+ @mouseenter="showDataTooltip($event, 6)" @mouseleave="hideDataTooltip"/>
|
|
|
+ <circle cx="460" cy="60" r="6" fill="#3BB44A" class="data-point" style="--index: 7;"
|
|
|
+ @mouseenter="showDataTooltip($event, 7)" @mouseleave="hideDataTooltip"/>
|
|
|
+ <circle cx="520" cy="45" r="6" fill="#3BB44A" class="data-point" style="--index: 8;"
|
|
|
+ @mouseenter="showDataTooltip($event, 8)" @mouseleave="hideDataTooltip"/>
|
|
|
+ <circle cx="580" cy="35" r="6" fill="#3BB44A" class="data-point" style="--index: 9;"
|
|
|
+ @mouseenter="showDataTooltip($event, 9)" @mouseleave="hideDataTooltip"/>
|
|
|
+ <!-- X轴标签 -->
|
|
|
+ <text x="40" y="230" text-anchor="middle" class="axis-label">1/9</text>
|
|
|
+ <text x="100" y="230" text-anchor="middle" class="axis-label">1/10</text>
|
|
|
+ <text x="160" y="230" text-anchor="middle" class="axis-label">1/11</text>
|
|
|
+ <text x="220" y="230" text-anchor="middle" class="axis-label">1/12</text>
|
|
|
+ <text x="280" y="230" text-anchor="middle" class="axis-label">1/13</text>
|
|
|
+ <text x="340" y="230" text-anchor="middle" class="axis-label">1/14</text>
|
|
|
+ <text x="400" y="230" text-anchor="middle" class="axis-label">1/15</text>
|
|
|
+ <text x="460" y="230" text-anchor="middle" class="axis-label">1/16</text>
|
|
|
+ <text x="520" y="230" text-anchor="middle" class="axis-label">1/17</text>
|
|
|
+ <text x="580" y="230" text-anchor="middle" class="axis-label">今日</text>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="work-trend-summary">
|
|
|
+ <div class="work-summary-item">
|
|
|
+ <span class="work-summary-label">今日作业</span>
|
|
|
+ <span class="work-summary-value primary">{{ trendSummary.today }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="work-summary-item">
|
|
|
+ <span class="work-summary-label">昨日作业</span>
|
|
|
+ <span class="work-summary-value">{{ trendSummary.yesterday }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="work-summary-item">
|
|
|
+ <span class="work-summary-label">环比增长</span>
|
|
|
+ <span class="work-summary-value positive">{{ trendSummary.growth }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 保养统计分析 -->
|
|
|
+ <el-card class="analysis-card" shadow="hover">
|
|
|
+ <div slot="header" class="analysis-header">
|
|
|
+ <div class="header-left">
|
|
|
+ <h3 class="analysis-title">保养统计分析</h3>
|
|
|
+ <p class="analysis-subtitle">设备维护状况监控与预测分析</p>
|
|
|
+ </div>
|
|
|
+ <div class="header-right">
|
|
|
+ <el-select v-model="maintenanceTimeRange" placeholder="选择时间范围" @change="updateMaintenanceAnalysisData">
|
|
|
+ <el-option label="近7天" value="7"></el-option>
|
|
|
+ <el-option label="近30天" value="30"></el-option>
|
|
|
+ <el-option label="近90天" value="90"></el-option>
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 保养核心指标 -->
|
|
|
+ <el-row :gutter="24" class="metrics-row">
|
|
|
+ <el-col :span="8">
|
|
|
+ <div class="maintenance-metric-card execution-metric">
|
|
|
+ <div class="maintenance-metric-header">
|
|
|
+ <div class="maintenance-metric-icon-wrapper">
|
|
|
+ <i class="el-icon-check"></i>
|
|
|
+ </div>
|
|
|
+ <div class="maintenance-metric-trend-badge positive">
|
|
|
+ <i class="el-icon-top"></i>
|
|
|
+ <span>+5.2%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="maintenance-metric-body">
|
|
|
+ <div class="maintenance-metric-value">{{ maintenanceAnalysis.executionRate }}<span class="maintenance-metric-unit">%</span></div>
|
|
|
+ <div class="maintenance-metric-label">保养计划执行率</div>
|
|
|
+ <div class="maintenance-metric-desc">按时完成保养的设备比例</div>
|
|
|
+ </div>
|
|
|
+ <div class="maintenance-metric-footer">
|
|
|
+ <div class="maintenance-progress-bar">
|
|
|
+ <div class="maintenance-progress-fill" style="width: 89.6%"></div>
|
|
|
+ </div>
|
|
|
+ <span class="maintenance-progress-text">执行率 89.6%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <div class="maintenance-metric-card cycle-metric">
|
|
|
+ <div class="maintenance-metric-header">
|
|
|
+ <div class="maintenance-metric-icon-wrapper">
|
|
|
+ <i class="el-icon-time"></i>
|
|
|
+ </div>
|
|
|
+ <div class="maintenance-metric-trend-badge neutral">
|
|
|
+ <i class="el-icon-minus"></i>
|
|
|
+ <span>持平</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="maintenance-metric-body">
|
|
|
+ <div class="maintenance-metric-value">{{ maintenanceAnalysis.avgCycle }}<span class="maintenance-metric-unit">天</span></div>
|
|
|
+ <div class="maintenance-metric-label">平均保养周期</div>
|
|
|
+ <div class="maintenance-metric-desc">各类设备保养间隔时间</div>
|
|
|
+ </div>
|
|
|
+ <div class="maintenance-metric-footer">
|
|
|
+ <div class="maintenance-progress-bar">
|
|
|
+ <div class="maintenance-progress-fill cycle-fill" style="width: 78.3%"></div>
|
|
|
+ </div>
|
|
|
+ <span class="maintenance-progress-text">周期优化 78.3%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <div class="maintenance-metric-card cost-metric">
|
|
|
+ <div class="maintenance-metric-header">
|
|
|
+ <div class="maintenance-metric-icon-wrapper">
|
|
|
+ <i class="el-icon-money"></i>
|
|
|
+ </div>
|
|
|
+ <div class="maintenance-metric-trend-badge positive">
|
|
|
+ <i class="el-icon-bottom"></i>
|
|
|
+ <span>-8.1%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="maintenance-metric-body">
|
|
|
+ <div class="maintenance-metric-value">¥{{ maintenanceAnalysis.avgCost }}<span class="maintenance-metric-unit">/台</span></div>
|
|
|
+ <div class="maintenance-metric-label">保养成本控制</div>
|
|
|
+ <div class="maintenance-metric-desc">单台设备月均保养费用</div>
|
|
|
+ </div>
|
|
|
+ <div class="maintenance-metric-footer">
|
|
|
+ <div class="maintenance-progress-bar">
|
|
|
+ <div class="maintenance-progress-fill cost-fill" style="width: 91.9%"></div>
|
|
|
+ </div>
|
|
|
+ <span class="maintenance-progress-text">成本控制 91.9%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 保养图表区域 -->
|
|
|
+ <el-row :gutter="32" class="charts-row">
|
|
|
+ <el-col :span="12">
|
|
|
+ <div class="chart-card maintenance-chart-card">
|
|
|
+ <div class="chart-header">
|
|
|
+ <h4 class="chart-title">设备保养状态分布</h4>
|
|
|
+ </div>
|
|
|
+ <div class="chart-content chart-flex-content">
|
|
|
+ <div class="maintenance-donut-chart-horizontal">
|
|
|
+ <!-- 左侧:饼图区域 -->
|
|
|
+ <div class="maintenance-donut-container-side">
|
|
|
+ <svg width="280" height="280" viewBox="0 0 280 280">
|
|
|
+ <!-- 环形图 -->
|
|
|
+ <circle cx="140" cy="140" r="85" fill="none" stroke="#E5F4E8" stroke-width="35"/>
|
|
|
+ <circle cx="140" cy="140" r="85" fill="none" stroke="#4CAF50" stroke-width="35"
|
|
|
+ stroke-dasharray="267 534" stroke-dashoffset="0" transform="rotate(-90 140 140)"
|
|
|
+ class="maintenance-segment" data-status="normal"
|
|
|
+ />
|
|
|
+ <circle cx="140" cy="140" r="85" fill="none" stroke="#FFC107" stroke-width="35"
|
|
|
+ stroke-dasharray="107 534" stroke-dashoffset="-267" transform="rotate(-90 140 140)"
|
|
|
+ class="maintenance-segment" data-status="due"
|
|
|
+ />
|
|
|
+ <circle cx="140" cy="140" r="85" fill="none" stroke="#FF5722" stroke-width="35"
|
|
|
+ stroke-dasharray="53 534" stroke-dashoffset="-374" transform="rotate(-90 140 140)"
|
|
|
+ class="maintenance-segment" data-status="overdue"
|
|
|
+ />
|
|
|
+ <circle cx="140" cy="140" r="85" fill="none" stroke="#2196F3" stroke-width="35"
|
|
|
+ stroke-dasharray="107 534" stroke-dashoffset="-427" transform="rotate(-90 140 140)"
|
|
|
+ class="maintenance-segment" data-status="progress"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 中心文字 -->
|
|
|
+ <text x="140" y="125" text-anchor="middle" class="maintenance-donut-center-label">总设备</text>
|
|
|
+ <text x="140" y="155" text-anchor="middle" class="maintenance-donut-center-value">24</text>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <!-- 右侧:图例区域 -->
|
|
|
+ <div class="maintenance-donut-legend-side">
|
|
|
+ <div class="maintenance-legend-item-enhanced" data-status="normal" style="--index: 0;">
|
|
|
+ <div class="maintenance-legend-color" style="background-color: #4CAF50;"></div>
|
|
|
+ <div class="maintenance-legend-content-enhanced">
|
|
|
+ <span class="maintenance-legend-label">保养正常</span>
|
|
|
+ <span class="maintenance-legend-value">50%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="maintenance-legend-item-enhanced" data-status="due" style="--index: 1;">
|
|
|
+ <div class="maintenance-legend-color" style="background-color: #FFC107;"></div>
|
|
|
+ <div class="maintenance-legend-content-enhanced">
|
|
|
+ <span class="maintenance-legend-label">即将到期</span>
|
|
|
+ <span class="maintenance-legend-value">20%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="maintenance-legend-item-enhanced" data-status="overdue" style="--index: 2;">
|
|
|
+ <div class="maintenance-legend-color" style="background-color: #FF5722;"></div>
|
|
|
+ <div class="maintenance-legend-content-enhanced">
|
|
|
+ <span class="maintenance-legend-label">保养超期</span>
|
|
|
+ <span class="maintenance-legend-value">10%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="maintenance-legend-item-enhanced" data-status="progress" style="--index: 3;">
|
|
|
+ <div class="maintenance-legend-color" style="background-color: #2196F3;"></div>
|
|
|
+ <div class="maintenance-legend-content-enhanced">
|
|
|
+ <span class="maintenance-legend-label">保养中</span>
|
|
|
+ <span class="maintenance-legend-value">20%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <div class="chart-card maintenance-chart-card">
|
|
|
+ <div class="chart-header">
|
|
|
+ <h4 class="chart-title">保养成本变化趋势</h4>
|
|
|
+ <div class="chart-controls">
|
|
|
+ <el-radio-group v-model="maintenanceTrendMetric" size="small" @change="updateMaintenanceTrendChart">
|
|
|
+ <el-radio-button label="cost">成本</el-radio-button>
|
|
|
+ <el-radio-button label="frequency">频次</el-radio-button>
|
|
|
+ <el-radio-button label="efficiency">效率</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="chart-content chart-flex-content">
|
|
|
+ <div class="maintenance-trend-chart">
|
|
|
+ <div class="maintenance-chart-area">
|
|
|
+ <div class="maintenance-chart-unit-label">单位:元</div>
|
|
|
+ <svg width="100%" height="260" viewBox="0 0 500 260">
|
|
|
+ <!-- 网格线 -->
|
|
|
+ <defs>
|
|
|
+ <pattern id="maintenanceGrid" width="50" height="50" patternUnits="userSpaceOnUse">
|
|
|
+ <path d="M 50 0 L 0 0 0 50" fill="none" stroke="#F0F4F1" stroke-width="1"/>
|
|
|
+ </pattern>
|
|
|
+ <linearGradient id="maintenanceTrendGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
|
+ <stop offset="0%" style="stop-color:#FF9800;stop-opacity:0.3"/>
|
|
|
+ <stop offset="100%" style="stop-color:#FF9800;stop-opacity:0"/>
|
|
|
+ </linearGradient>
|
|
|
+ </defs>
|
|
|
+ <rect width="100%" height="100%" fill="url(#maintenanceGrid)"/>
|
|
|
+
|
|
|
+ <!-- 趋势面积 -->
|
|
|
+ <path d="M30,180 L90,160 L150,140 L210,120 L270,130 L330,110 L390,95 L450,80 L450,260 L30,260 Z"
|
|
|
+ fill="url(#maintenanceTrendGradient)"/>
|
|
|
+
|
|
|
+ <!-- 趋势线 -->
|
|
|
+ <polyline points="30,180 90,160 150,140 210,120 270,130 330,110 390,95 450,80"
|
|
|
+ fill="none" stroke="#FF9800" stroke-width="4"/>
|
|
|
+
|
|
|
+ <!-- 数据点 -->
|
|
|
+ <circle cx="30" cy="180" r="6" fill="#FF9800" class="maintenance-data-point" style="--index: 0;"
|
|
|
+ @mouseenter="showMaintenanceDataTooltip($event, 0)" @mouseleave="hideMaintenanceDataTooltip"/>
|
|
|
+ <circle cx="90" cy="160" r="6" fill="#FF9800" class="maintenance-data-point" style="--index: 1;"
|
|
|
+ @mouseenter="showMaintenanceDataTooltip($event, 1)" @mouseleave="hideMaintenanceDataTooltip"/>
|
|
|
+ <circle cx="150" cy="140" r="6" fill="#FF9800" class="maintenance-data-point" style="--index: 2;"
|
|
|
+ @mouseenter="showMaintenanceDataTooltip($event, 2)" @mouseleave="hideMaintenanceDataTooltip"/>
|
|
|
+ <circle cx="210" cy="120" r="6" fill="#FF9800" class="maintenance-data-point" style="--index: 3;"
|
|
|
+ @mouseenter="showMaintenanceDataTooltip($event, 3)" @mouseleave="hideMaintenanceDataTooltip"/>
|
|
|
+ <circle cx="270" cy="130" r="6" fill="#FF9800" class="maintenance-data-point" style="--index: 4;"
|
|
|
+ @mouseenter="showMaintenanceDataTooltip($event, 4)" @mouseleave="hideMaintenanceDataTooltip"/>
|
|
|
+ <circle cx="330" cy="110" r="6" fill="#FF9800" class="maintenance-data-point" style="--index: 5;"
|
|
|
+ @mouseenter="showMaintenanceDataTooltip($event, 5)" @mouseleave="hideMaintenanceDataTooltip"/>
|
|
|
+ <circle cx="390" cy="95" r="6" fill="#FF9800" class="maintenance-data-point" style="--index: 6;"
|
|
|
+ @mouseenter="showMaintenanceDataTooltip($event, 6)" @mouseleave="hideMaintenanceDataTooltip"/>
|
|
|
+ <circle cx="450" cy="80" r="6" fill="#FF9800" class="maintenance-data-point" style="--index: 7;"
|
|
|
+ @mouseenter="showMaintenanceDataTooltip($event, 7)" @mouseleave="hideMaintenanceDataTooltip"/>
|
|
|
+
|
|
|
+ <!-- X轴标签 -->
|
|
|
+ <text x="30" y="240" text-anchor="middle" class="maintenance-axis-label">1/9</text>
|
|
|
+ <text x="90" y="240" text-anchor="middle" class="maintenance-axis-label">1/10</text>
|
|
|
+ <text x="150" y="240" text-anchor="middle" class="maintenance-axis-label">1/11</text>
|
|
|
+ <text x="210" y="240" text-anchor="middle" class="maintenance-axis-label">1/12</text>
|
|
|
+ <text x="270" y="240" text-anchor="middle" class="maintenance-axis-label">1/13</text>
|
|
|
+ <text x="330" y="240" text-anchor="middle" class="maintenance-axis-label">1/14</text>
|
|
|
+ <text x="390" y="240" text-anchor="middle" class="maintenance-axis-label">1/15</text>
|
|
|
+ <text x="450" y="240" text-anchor="middle" class="maintenance-axis-label">今日</text>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="maintenance-trend-summary">
|
|
|
+ <div class="maintenance-summary-item">
|
|
|
+ <span class="maintenance-summary-label">今日保养{{ getMaintenanceMetricLabel() }}</span>
|
|
|
+ <span class="maintenance-summary-value primary">{{ maintenanceSummary.today }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="maintenance-summary-item">
|
|
|
+ <span class="maintenance-summary-label">昨日保养{{ getMaintenanceMetricLabel() }}</span>
|
|
|
+ <span class="maintenance-summary-value">{{ maintenanceSummary.yesterday }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="maintenance-summary-item">
|
|
|
+ <span class="maintenance-summary-label">环比变化</span>
|
|
|
+ <span class="maintenance-summary-value positive">{{ maintenanceSummary.growth }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ <!-- 监控设置对话框 -->
|
|
|
+ <el-dialog title="监控设置" :visible.sync="settingsVisible" width="600px">
|
|
|
+ <el-form :model="settings" label-width="120px">
|
|
|
+ <el-form-item label="刷新间隔">
|
|
|
+ <el-select v-model="settings.refreshInterval">
|
|
|
+ <el-option label="30秒" value="30"></el-option>
|
|
|
+ <el-option label="1分钟" value="60"></el-option>
|
|
|
+ <el-option label="2分钟" value="120"></el-option>
|
|
|
+ <el-option label="5分钟" value="300"></el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="告警通知">
|
|
|
+ <el-switch v-model="settings.alertNotification"></el-switch>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="声音提醒">
|
|
|
+ <el-switch v-model="settings.soundAlert"></el-switch>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="地图类型">
|
|
|
+ <el-radio-group v-model="settings.mapType">
|
|
|
+ <el-radio label="satellite">卫星图</el-radio>
|
|
|
+ <el-radio label="standard">标准地图</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <div slot="footer">
|
|
|
+ <el-button @click="settingsVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="saveSettings">保存</el-button>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { getMachines } from "@/api/base/machines"
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: "MachinesMonitor",
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ // 页面状态
|
|
|
+ refreshing: false,
|
|
|
+ mapLoading: false,
|
|
|
+ updateTime: '',
|
|
|
+ timeRange: 'realtime',
|
|
|
+
|
|
|
+ // 筛选条件
|
|
|
+ filters: {
|
|
|
+ machineType: '',
|
|
|
+ farmId: ''
|
|
|
+ },
|
|
|
+
|
|
|
+ // 农场列表
|
|
|
+ farmList: [
|
|
|
+ { id: 'farm1', name: '东湖智慧农场' },
|
|
|
+ { id: 'farm2', name: '西湖有机农场' },
|
|
|
+ { id: 'farm3', name: '南山生态农场' }
|
|
|
+ ],
|
|
|
+
|
|
|
+ // 统计数据
|
|
|
+ statistics: {
|
|
|
+ total: 24,
|
|
|
+ online: 18,
|
|
|
+ working: 12,
|
|
|
+ warning: 3
|
|
|
+ },
|
|
|
+
|
|
|
+ // 保养统计
|
|
|
+ maintenanceStats: {
|
|
|
+ due: 5,
|
|
|
+ inProgress: 2,
|
|
|
+ overdue: 1,
|
|
|
+ monthlyCost: 28500
|
|
|
+ },
|
|
|
+
|
|
|
+ // 农机列表
|
|
|
+ machinesList: [],
|
|
|
+ originalMachines: [], // 原始农机数据,用于筛选重置
|
|
|
+ visibleMachines: [],
|
|
|
+
|
|
|
+ // 地图相关
|
|
|
+ selectedMachine: null,
|
|
|
+ popupPosition: {
|
|
|
+ left: '0px',
|
|
|
+ top: '0px'
|
|
|
+ },
|
|
|
+ amap: null, // 高德地图实例
|
|
|
+ machineMarkers: [], // 农机标记数组
|
|
|
+ infoWindow: null, // 信息窗口
|
|
|
+ mapCenter: [117.065029, 34.227274], // 地图中心点坐标
|
|
|
+
|
|
|
+ // 分析数据
|
|
|
+ analysisTimeRange: '7',
|
|
|
+ maintenanceTimeRange: '30',
|
|
|
+ trendMetric: 'area',
|
|
|
+ maintenanceTrendMetric: 'cost',
|
|
|
+
|
|
|
+ // 作业分析数据
|
|
|
+ workAnalysis: {
|
|
|
+ totalArea: '1,248.5',
|
|
|
+ totalHours: '89.2',
|
|
|
+ avgEfficiency: '14.2'
|
|
|
+ },
|
|
|
+
|
|
|
+ // 保养分析数据
|
|
|
+ maintenanceAnalysis: {
|
|
|
+ executionRate: '89.6',
|
|
|
+ avgCycle: '23.5',
|
|
|
+ avgCost: '1,245'
|
|
|
+ },
|
|
|
+
|
|
|
+ // 趋势汇总
|
|
|
+ trendSummary: {
|
|
|
+ today: '156.8 亩',
|
|
|
+ yesterday: '142.3 亩',
|
|
|
+ growth: '+10.2%'
|
|
|
+ },
|
|
|
+
|
|
|
+ // 保养趋势汇总
|
|
|
+ maintenanceSummary: {
|
|
|
+ today: '¥2,500',
|
|
|
+ yesterday: '¥2,800',
|
|
|
+ growth: '-10.7%'
|
|
|
+ },
|
|
|
+
|
|
|
+ // 设置
|
|
|
+ settingsVisible: false,
|
|
|
+ settings: {
|
|
|
+ refreshInterval: '120',
|
|
|
+ alertNotification: true,
|
|
|
+ soundAlert: false,
|
|
|
+ mapType: 'standard'
|
|
|
+ },
|
|
|
+
|
|
|
+ // 定时器
|
|
|
+ timers: [],
|
|
|
+
|
|
|
+ // 筛选防抖定时器
|
|
|
+ filterTimeout: null
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ onlineRate() {
|
|
|
+ if (this.statistics.total === 0) return 0
|
|
|
+ return Math.round((this.statistics.online / this.statistics.total) * 100)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ this.loadData()
|
|
|
+ this.startAutoRefresh()
|
|
|
+ this.updateCurrentTime()
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.initCharts()
|
|
|
+ this.initAmap()
|
|
|
+ this.updateTrendChartData() // 初始化趋势图表数据
|
|
|
+
|
|
|
+ // 添加全局方法供信息窗口调用
|
|
|
+ window.viewMachineDetail = (machineId) => {
|
|
|
+ this.$router.push(`/base/machines/monitor/${machineId}`)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ this.timers.forEach(timer => clearInterval(timer))
|
|
|
+ if (this.filterTimeout) {
|
|
|
+ clearTimeout(this.filterTimeout)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清理地图资源
|
|
|
+ if (this.amap) {
|
|
|
+ this.clearMachineMarkers()
|
|
|
+ this.amap.destroy()
|
|
|
+ this.amap = null
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清理全局方法
|
|
|
+ if (window.viewMachineDetail) {
|
|
|
+ delete window.viewMachineDetail
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ // 加载数据
|
|
|
+ async loadData() {
|
|
|
+ try {
|
|
|
+ await this.loadMachinesList()
|
|
|
+ this.loadStatistics()
|
|
|
+ this.loadMaintenanceStats()
|
|
|
+ this.generateMockMachines()
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error('数据加载失败')
|
|
|
+ console.error(error)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载农机列表
|
|
|
+ async loadMachinesList() {
|
|
|
+ try {
|
|
|
+ const response = await getMachines()
|
|
|
+ this.machinesList = response.rows || []
|
|
|
+ this.applyFilters()
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载农机列表失败:', error)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载统计数据
|
|
|
+ loadStatistics() {
|
|
|
+ // 实际项目中应该调用API获取统计数据
|
|
|
+ // 这里使用模拟数据
|
|
|
+ this.statistics = {
|
|
|
+ total: this.machinesList.length || 24,
|
|
|
+ online: Math.floor(Math.random() * 6) + 16,
|
|
|
+ working: Math.floor(Math.random() * 5) + 10,
|
|
|
+ warning: Math.floor(Math.random() * 3) + 1
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载保养统计
|
|
|
+ loadMaintenanceStats() {
|
|
|
+ this.maintenanceStats = {
|
|
|
+ due: Math.floor(Math.random() * 3) + 4,
|
|
|
+ inProgress: Math.floor(Math.random() * 2) + 1,
|
|
|
+ overdue: Math.floor(Math.random() * 2),
|
|
|
+ monthlyCost: Math.floor(Math.random() * 5000) + 25000
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 生成模拟农机数据
|
|
|
+ generateMockMachines() {
|
|
|
+ const mockMachines = [
|
|
|
+ {
|
|
|
+ id: 1,
|
|
|
+ machineName: '东方红拖拉机',
|
|
|
+ machineCode: 'TL001',
|
|
|
+ machineType: 'tractor',
|
|
|
+ onlineStatus: 2,
|
|
|
+ maintenanceStatus: 0,
|
|
|
+ operator: '张师傅',
|
|
|
+ currentTask: '深松作业',
|
|
|
+ fieldArea: '东区1号地块',
|
|
|
+ workProgress: 65,
|
|
|
+ lastMaintenance: '2024-01-02',
|
|
|
+ nextMaintenance: '2024-01-25',
|
|
|
+ maintenanceMileage: '850/1000',
|
|
|
+ position: { x: 25, y: 30 }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 2,
|
|
|
+ machineName: '久保田收割机',
|
|
|
+ machineCode: 'SJ002',
|
|
|
+ machineType: 'harvester',
|
|
|
+ onlineStatus: 1,
|
|
|
+ maintenanceStatus: 1,
|
|
|
+ operator: '李师傅',
|
|
|
+ currentTask: '待分配',
|
|
|
+ fieldArea: '西区2号地块',
|
|
|
+ workProgress: 0,
|
|
|
+ lastMaintenance: '2023-12-15',
|
|
|
+ nextMaintenance: '2024-01-16',
|
|
|
+ maintenanceMileage: '980/1000',
|
|
|
+ position: { x: 60, y: 45 }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 3,
|
|
|
+ machineName: '东风喷药机',
|
|
|
+ machineCode: 'PY003',
|
|
|
+ machineType: 'sprayer',
|
|
|
+ onlineStatus: 3,
|
|
|
+ maintenanceStatus: 2,
|
|
|
+ operator: '王师傅',
|
|
|
+ currentTask: '告警检修',
|
|
|
+ fieldArea: '南区3号地块',
|
|
|
+ workProgress: 0,
|
|
|
+ lastMaintenance: '2023-11-20',
|
|
|
+ nextMaintenance: '2024-01-05',
|
|
|
+ maintenanceMileage: '1200/1000',
|
|
|
+ position: { x: 40, y: 65 }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 4,
|
|
|
+ machineName: '雷沃播种机',
|
|
|
+ machineCode: 'BZ004',
|
|
|
+ machineType: 'seeder',
|
|
|
+ onlineStatus: 2,
|
|
|
+ maintenanceStatus: 0,
|
|
|
+ operator: '赵师傅',
|
|
|
+ currentTask: '播种作业',
|
|
|
+ fieldArea: '北区4号地块',
|
|
|
+ workProgress: 80,
|
|
|
+ lastMaintenance: '2024-01-08',
|
|
|
+ nextMaintenance: '2024-02-10',
|
|
|
+ maintenanceMileage: '320/1000',
|
|
|
+ position: { x: 75, y: 20 }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 5,
|
|
|
+ machineName: '东方红拖拉机',
|
|
|
+ machineCode: 'TL005',
|
|
|
+ machineType: 'tractor',
|
|
|
+ onlineStatus: 1,
|
|
|
+ maintenanceStatus: 3,
|
|
|
+ operator: '孙师傅',
|
|
|
+ currentTask: '保养维护',
|
|
|
+ fieldArea: '维修车间',
|
|
|
+ workProgress: 60,
|
|
|
+ lastMaintenance: '2024-01-15',
|
|
|
+ nextMaintenance: '2024-02-20',
|
|
|
+ maintenanceMileage: '0/1000',
|
|
|
+ position: { x: 20, y: 80 }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 6,
|
|
|
+ machineName: '东方红耕地机',
|
|
|
+ machineCode: 'GD006',
|
|
|
+ machineType: 'cultivator',
|
|
|
+ onlineStatus: 1,
|
|
|
+ maintenanceStatus: 1,
|
|
|
+ operator: '钱师傅',
|
|
|
+ currentTask: '待分配',
|
|
|
+ fieldArea: '东区5号地块',
|
|
|
+ workProgress: 0,
|
|
|
+ lastMaintenance: '2023-12-20',
|
|
|
+ nextMaintenance: '2024-01-18',
|
|
|
+ maintenanceMileage: '950/1000',
|
|
|
+ position: { x: 85, y: 55 }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 7,
|
|
|
+ machineName: '约翰迪尔播种机',
|
|
|
+ machineCode: 'BZ007',
|
|
|
+ machineType: 'seeder',
|
|
|
+ onlineStatus: 2,
|
|
|
+ maintenanceStatus: 0,
|
|
|
+ operator: '刘师傅',
|
|
|
+ currentTask: '精量播种',
|
|
|
+ fieldArea: '东区6号地块',
|
|
|
+ workProgress: 45,
|
|
|
+ lastMaintenance: '2024-01-10',
|
|
|
+ nextMaintenance: '2024-02-15',
|
|
|
+ maintenanceMileage: '420/1000',
|
|
|
+ position: { x: 15, y: 25 }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 8,
|
|
|
+ machineName: '凯斯收割机',
|
|
|
+ machineCode: 'SJ008',
|
|
|
+ machineType: 'harvester',
|
|
|
+ onlineStatus: 0,
|
|
|
+ maintenanceStatus: 1,
|
|
|
+ operator: '周师傅',
|
|
|
+ currentTask: '离线维修',
|
|
|
+ fieldArea: '维修车间',
|
|
|
+ workProgress: 0,
|
|
|
+ lastMaintenance: '2023-12-25',
|
|
|
+ nextMaintenance: '2024-01-20',
|
|
|
+ maintenanceMileage: '870/1000',
|
|
|
+ position: { x: 30, y: 75 }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+
|
|
|
+ // 保存原始数据,用于筛选重置
|
|
|
+ this.originalMachines = [...mockMachines]
|
|
|
+ this.visibleMachines = [...mockMachines]
|
|
|
+ },
|
|
|
+
|
|
|
+ // 应用筛选
|
|
|
+ applyFilters() {
|
|
|
+ let filtered = [...this.originalMachines]
|
|
|
+
|
|
|
+ // 设备类型筛选
|
|
|
+ if (this.filters.machineType) {
|
|
|
+ filtered = filtered.filter(m => m.machineType === this.filters.machineType)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 农场筛选(这里使用模拟逻辑,实际项目中会根据农机的农场ID筛选)
|
|
|
+ if (this.filters.farmId) {
|
|
|
+ // 模拟根据农场筛选
|
|
|
+ filtered = filtered.filter(m => {
|
|
|
+ // 实际项目中这里会有farmId字段
|
|
|
+ return true // 暂时返回所有数据
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新可见的农机列表
|
|
|
+ this.visibleMachines = filtered
|
|
|
+
|
|
|
+ // 更新统计数据
|
|
|
+ this.updateFilteredStatistics(filtered)
|
|
|
+
|
|
|
+ // 更新地图标记
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.addMachineMarkers()
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 更新筛选后的统计数据
|
|
|
+ updateFilteredStatistics(filtered) {
|
|
|
+ this.statistics.total = filtered.length
|
|
|
+ this.statistics.online = filtered.filter(m => m.onlineStatus > 0).length
|
|
|
+ this.statistics.working = filtered.filter(m => m.onlineStatus === 2).length
|
|
|
+ this.statistics.warning = filtered.filter(m => m.onlineStatus === 3).length
|
|
|
+ },
|
|
|
+
|
|
|
+ // 重置筛选
|
|
|
+ resetFilters() {
|
|
|
+ this.filters = {
|
|
|
+ machineType: '',
|
|
|
+ farmId: ''
|
|
|
+ }
|
|
|
+ this.visibleMachines = [...this.originalMachines]
|
|
|
+ this.loadStatistics()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 刷新数据
|
|
|
+ async refreshData() {
|
|
|
+ this.refreshing = true
|
|
|
+ try {
|
|
|
+ await this.loadData()
|
|
|
+ this.updateCurrentTime()
|
|
|
+ this.$message.success('数据已刷新')
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error('刷新失败')
|
|
|
+ } finally {
|
|
|
+ setTimeout(() => {
|
|
|
+ this.refreshing = false
|
|
|
+ }, 1000)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 开始自动刷新
|
|
|
+ startAutoRefresh() {
|
|
|
+ // 时间更新定时器
|
|
|
+ const timeTimer = setInterval(() => {
|
|
|
+ this.updateCurrentTime()
|
|
|
+ }, 1000)
|
|
|
+ this.timers.push(timeTimer)
|
|
|
+
|
|
|
+ // 数据刷新定时器
|
|
|
+ const dataTimer = setInterval(() => {
|
|
|
+ this.loadStatistics()
|
|
|
+ this.loadMaintenanceStats()
|
|
|
+ }, parseInt(this.settings.refreshInterval) * 1000)
|
|
|
+ this.timers.push(dataTimer)
|
|
|
+ },
|
|
|
+
|
|
|
+ // 更新当前时间
|
|
|
+ updateCurrentTime() {
|
|
|
+ const now = new Date()
|
|
|
+ const year = now.getFullYear()
|
|
|
+ const month = String(now.getMonth() + 1).padStart(2, '0')
|
|
|
+ const day = String(now.getDate()).padStart(2, '0')
|
|
|
+ const hours = String(now.getHours()).padStart(2, '0')
|
|
|
+ const minutes = String(now.getMinutes()).padStart(2, '0')
|
|
|
+ const seconds = String(now.getSeconds()).padStart(2, '0')
|
|
|
+
|
|
|
+ this.updateTime = `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`
|
|
|
+ },
|
|
|
+
|
|
|
+ // 地图控制方法
|
|
|
+ zoomIn() {
|
|
|
+ this.$message.info('地图放大功能')
|
|
|
+ },
|
|
|
+
|
|
|
+ zoomOut() {
|
|
|
+ this.$message.info('地图缩小功能')
|
|
|
+ },
|
|
|
+
|
|
|
+ centerMap() {
|
|
|
+ this.$message.info('地图居中功能')
|
|
|
+ },
|
|
|
+
|
|
|
+ toggleMapFullscreen() {
|
|
|
+ const mapContainer = this.$refs.mapContainer
|
|
|
+ if (!document.fullscreenElement) {
|
|
|
+ mapContainer.requestFullscreen().then(() => {
|
|
|
+ this.$message.success('已进入全屏模式')
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ document.exitFullscreen().then(() => {
|
|
|
+ this.$message.success('已退出全屏')
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 显示农机弹层
|
|
|
+ showMachinePopup(machine, event) {
|
|
|
+ this.selectedMachine = machine
|
|
|
+
|
|
|
+ const rect = event.target.getBoundingClientRect()
|
|
|
+ const containerRect = this.$refs.mapContainer.getBoundingClientRect()
|
|
|
+
|
|
|
+ this.popupPosition = {
|
|
|
+ left: Math.max(10, Math.min(rect.left - containerRect.left - 140, containerRect.width - 290)) + 'px',
|
|
|
+ top: Math.max(10, rect.top - containerRect.top - 200) + 'px'
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 关闭农机弹层
|
|
|
+ closeMachinePopup() {
|
|
|
+ this.selectedMachine = null
|
|
|
+ },
|
|
|
+
|
|
|
+ // 查看农机详情
|
|
|
+ viewMachineDetail() {
|
|
|
+ if (this.selectedMachine) {
|
|
|
+ this.$router.push(`/base/machines/monitor/${this.selectedMachine.id}`)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取农机标记样式类
|
|
|
+ getMachineMarkerClass(machine) {
|
|
|
+ const statusClass = {
|
|
|
+ 0: 'offline',
|
|
|
+ 1: 'online',
|
|
|
+ 2: 'working',
|
|
|
+ 3: 'warning'
|
|
|
+ }[machine.onlineStatus] || 'offline'
|
|
|
+
|
|
|
+ const maintenanceClass = {
|
|
|
+ 0: 'maintenance-normal',
|
|
|
+ 1: 'maintenance-due',
|
|
|
+ 2: 'maintenance-overdue',
|
|
|
+ 3: 'maintenance-progress'
|
|
|
+ }[machine.maintenanceStatus] || 'maintenance-normal'
|
|
|
+
|
|
|
+ return `${statusClass} ${maintenanceClass}`
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取农机位置样式
|
|
|
+ getMachinePosition(machine) {
|
|
|
+ return {
|
|
|
+ left: machine.position.x + '%',
|
|
|
+ top: machine.position.y + '%'
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取农机图标
|
|
|
+ getMachineIcon(machineType) {
|
|
|
+ const iconMap = {
|
|
|
+ 'tractor': 'el-icon-truck',
|
|
|
+ 'harvester': 'el-icon-goods',
|
|
|
+ 'seeder': 'el-icon-set-up',
|
|
|
+ 'sprayer': 'el-icon-grape',
|
|
|
+ 'cultivator': 'el-icon-menu'
|
|
|
+ }
|
|
|
+ return iconMap[machineType] || 'el-icon-truck'
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取农机提示信息
|
|
|
+ getMachineTooltip(machine) {
|
|
|
+ return `${machine.machineName} #${machine.machineCode} - ${this.getStatusText(machine.onlineStatus)} | ${this.getMaintenanceStatusText(machine.maintenanceStatus)}`
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取状态文本
|
|
|
+ getStatusText(status) {
|
|
|
+ const statusMap = {
|
|
|
+ 0: '离线',
|
|
|
+ 1: '在线',
|
|
|
+ 2: '作业中',
|
|
|
+ 3: '告警'
|
|
|
+ }
|
|
|
+ return statusMap[status] || '未知'
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取状态标签类型
|
|
|
+ getStatusTagType(status) {
|
|
|
+ const typeMap = {
|
|
|
+ 0: 'info',
|
|
|
+ 1: 'success',
|
|
|
+ 2: 'primary',
|
|
|
+ 3: 'warning'
|
|
|
+ }
|
|
|
+ return typeMap[status] || 'info'
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取保养状态文本
|
|
|
+ getMaintenanceStatusText(status) {
|
|
|
+ const statusMap = {
|
|
|
+ 0: '正常',
|
|
|
+ 1: '即将到期',
|
|
|
+ 2: '已超期',
|
|
|
+ 3: '保养中'
|
|
|
+ }
|
|
|
+ return statusMap[status] || '未知'
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取保养状态标签类型
|
|
|
+ getMaintenanceTagType(status) {
|
|
|
+ const typeMap = {
|
|
|
+ 0: 'success',
|
|
|
+ 1: 'warning',
|
|
|
+ 2: 'danger',
|
|
|
+ 3: 'primary'
|
|
|
+ }
|
|
|
+ return typeMap[status] || 'info'
|
|
|
+ },
|
|
|
+
|
|
|
+ // 格式化货币
|
|
|
+ formatCurrency(amount) {
|
|
|
+ return amount.toLocaleString()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 更新分析数据
|
|
|
+ updateAnalysisData() {
|
|
|
+ // 根据时间范围更新作业分析数据
|
|
|
+ const mockData = {
|
|
|
+ '7': {
|
|
|
+ totalArea: '1,248.5',
|
|
|
+ totalHours: '89.2',
|
|
|
+ avgEfficiency: '14.2'
|
|
|
+ },
|
|
|
+ '30': {
|
|
|
+ totalArea: '5,432.1',
|
|
|
+ totalHours: '392.7',
|
|
|
+ avgEfficiency: '13.8'
|
|
|
+ },
|
|
|
+ '90': {
|
|
|
+ totalArea: '16,847.3',
|
|
|
+ totalHours: '1,203.5',
|
|
|
+ avgEfficiency: '14.0'
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.workAnalysis = mockData[this.analysisTimeRange]
|
|
|
+ this.updateCharts()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 更新保养分析数据
|
|
|
+ updateMaintenanceAnalysisData() {
|
|
|
+ const mockData = {
|
|
|
+ '7': {
|
|
|
+ executionRate: '91.2',
|
|
|
+ avgCycle: '21.8',
|
|
|
+ avgCost: '1,180'
|
|
|
+ },
|
|
|
+ '30': {
|
|
|
+ executionRate: '89.6',
|
|
|
+ avgCycle: '23.5',
|
|
|
+ avgCost: '1,245'
|
|
|
+ },
|
|
|
+ '90': {
|
|
|
+ executionRate: '87.3',
|
|
|
+ avgCycle: '25.2',
|
|
|
+ avgCost: '1,320'
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.maintenanceAnalysis = mockData[this.maintenanceTimeRange]
|
|
|
+ this.updateMaintenanceCharts()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 更新趋势图
|
|
|
+ updateTrendChart() {
|
|
|
+ const summaryData = {
|
|
|
+ area: {
|
|
|
+ today: '156.8 亩',
|
|
|
+ yesterday: '142.3 亩',
|
|
|
+ growth: '+10.2%'
|
|
|
+ },
|
|
|
+ time: {
|
|
|
+ today: '11.2 小时',
|
|
|
+ yesterday: '10.1 小时',
|
|
|
+ growth: '+10.9%'
|
|
|
+ },
|
|
|
+ efficiency: {
|
|
|
+ today: '14.5 亩/小时',
|
|
|
+ yesterday: '13.8 亩/小时',
|
|
|
+ growth: '+5.1%'
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新汇总数据
|
|
|
+ this.trendSummary = summaryData[this.trendMetric]
|
|
|
+
|
|
|
+ // 更新图表数据和单位
|
|
|
+ this.updateTrendChartData()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 更新趋势图表数据
|
|
|
+ updateTrendChartData() {
|
|
|
+ // 根据不同的指标设置不同的数据点和单位
|
|
|
+ const chartData = {
|
|
|
+ area: {
|
|
|
+ unit: '亩',
|
|
|
+ points: [120, 135, 145, 160, 155, 170, 185, 200, 215, 230],
|
|
|
+ yCoords: [200, 175, 165, 150, 155, 140, 125, 110, 95, 80],
|
|
|
+ path: 'M40,200 L100,175 L160,165 L220,150 L280,155 L340,140 L400,125 L460,110 L520,95 L580,80',
|
|
|
+ areaPath: 'M40,200 L100,175 L160,165 L220,150 L280,155 L340,140 L400,125 L460,110 L520,95 L580,80 L580,240 L40,240 Z'
|
|
|
+ },
|
|
|
+ time: {
|
|
|
+ unit: '小时',
|
|
|
+ points: [8, 9, 9.5, 10, 9.8, 10.5, 11, 11.5, 12, 12.5],
|
|
|
+ yCoords: [220, 200, 190, 180, 185, 170, 160, 150, 140, 130],
|
|
|
+ path: 'M40,220 L100,200 L160,190 L220,180 L280,185 L340,170 L400,160 L460,150 L520,140 L580,130',
|
|
|
+ areaPath: 'M40,220 L100,200 L160,190 L220,180 L280,185 L340,170 L400,160 L460,150 L520,140 L580,130 L580,240 L40,240 Z'
|
|
|
+ },
|
|
|
+ efficiency: {
|
|
|
+ unit: '亩/小时',
|
|
|
+ points: [12, 13, 13.5, 14, 13.8, 14.2, 14.8, 15.2, 15.8, 16.2],
|
|
|
+ yCoords: [200, 185, 175, 165, 170, 155, 145, 135, 125, 115],
|
|
|
+ path: 'M40,200 L100,185 L160,175 L220,165 L280,170 L340,155 L400,145 L460,135 L520,125 L580,115',
|
|
|
+ areaPath: 'M40,200 L100,185 L160,175 L220,165 L280,170 L340,155 L400,145 L460,135 L520,125 L580,115 L580,240 L40,240 Z'
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const currentData = chartData[this.trendMetric]
|
|
|
+
|
|
|
+ // 更新单位标签
|
|
|
+ this.$nextTick(() => {
|
|
|
+ const unitLabel = document.querySelector('.work-chart-unit-label')
|
|
|
+ if (unitLabel) {
|
|
|
+ unitLabel.textContent = `单位:${currentData.unit}`
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新SVG路径
|
|
|
+ const trendLine = document.querySelector('.work-trend-chart polyline')
|
|
|
+ const trendArea = document.querySelector('.work-trend-chart path[fill="url(#trendGradient)"]')
|
|
|
+
|
|
|
+ if (trendLine) {
|
|
|
+ // 将路径字符串转换为points格式
|
|
|
+ const points = currentData.path.replace(/[ML]/g, ' ').trim().split(' ').filter(p => p !== '')
|
|
|
+ const pointsString = points.join(' ')
|
|
|
+ trendLine.setAttribute('points', pointsString)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (trendArea) {
|
|
|
+ trendArea.setAttribute('d', currentData.areaPath)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新数据点位置
|
|
|
+ const dataPoints = document.querySelectorAll('.work-trend-chart .data-point')
|
|
|
+ dataPoints.forEach((point, index) => {
|
|
|
+ if (index < currentData.yCoords.length) {
|
|
|
+ // 使用预定义的Y坐标,确保与线条路径完全匹配
|
|
|
+ const y = currentData.yCoords[index]
|
|
|
+ point.setAttribute('cy', y)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 初始化图表
|
|
|
+ initCharts() {
|
|
|
+ // 在实际项目中,这里会初始化ECharts或其他图表库
|
|
|
+ console.log('初始化图表')
|
|
|
+
|
|
|
+ // 确保图表数据正确初始化
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.updateTrendChartData()
|
|
|
+ this.updateMaintenanceTrendChartData()
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 更新图表
|
|
|
+ updateCharts() {
|
|
|
+ // 更新作业统计图表
|
|
|
+ console.log('更新作业统计图表')
|
|
|
+ },
|
|
|
+
|
|
|
+ // 更新保养图表
|
|
|
+ updateMaintenanceCharts() {
|
|
|
+ // 更新保养统计图表
|
|
|
+ console.log('更新保养统计图表')
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理导出
|
|
|
+ handleExport(command) {
|
|
|
+ this.$message.success(`正在导出${command}...`)
|
|
|
+ // 实际项目中实现导出功能
|
|
|
+ },
|
|
|
+
|
|
|
+ // 打开设置
|
|
|
+ openSettings() {
|
|
|
+ this.settingsVisible = true
|
|
|
+ },
|
|
|
+
|
|
|
+ // 保存设置
|
|
|
+ saveSettings() {
|
|
|
+ this.settingsVisible = false
|
|
|
+ this.$message.success('设置已保存')
|
|
|
+
|
|
|
+ // 重新设置定时器
|
|
|
+ this.timers.forEach(timer => clearInterval(timer))
|
|
|
+ this.timers = []
|
|
|
+ this.startAutoRefresh()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 防抖应用筛选
|
|
|
+ debouncedApplyFilters() {
|
|
|
+ if (this.filterTimeout) {
|
|
|
+ clearTimeout(this.filterTimeout)
|
|
|
+ }
|
|
|
+ this.filterTimeout = setTimeout(() => {
|
|
|
+ this.applyFilters()
|
|
|
+ }, 500)
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ // 重启自动刷新
|
|
|
+ restartAutoRefresh() {
|
|
|
+ this.timers.forEach(timer => clearInterval(timer))
|
|
|
+ this.timers = []
|
|
|
+ this.startAutoRefresh()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 时间范围变化处理
|
|
|
+ onTimeRangeChange() {
|
|
|
+ // 可以在这里添加时间范围变化的处理逻辑
|
|
|
+ // 例如:重新加载数据、更新图表等
|
|
|
+ console.log('时间范围已更改为:', this.timeRange)
|
|
|
+ this.loadData()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 初始化高德地图
|
|
|
+ initAmap() {
|
|
|
+ if (typeof AMap === 'undefined') {
|
|
|
+ console.error('高德地图API未加载,请检查API引用')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ this.mapLoading = true
|
|
|
+
|
|
|
+ // 创建地图实例
|
|
|
+ this.amap = new AMap.Map('amap-container', {
|
|
|
+ center: this.mapCenter, // 设置地图中心点坐标
|
|
|
+ zoom: 12, // 设置地图缩放级别
|
|
|
+ mapStyle: 'amap://styles/light', // 设置地图样式为浅色
|
|
|
+ viewMode: '2D', // 设置地图模式为2D
|
|
|
+ features: ['bg', 'road', 'building'], // 设置地图显示要素
|
|
|
+ showLabel: true // 显示地图标注
|
|
|
+ })
|
|
|
+
|
|
|
+ // 地图加载完成后的回调
|
|
|
+ this.amap.on('complete', () => {
|
|
|
+ this.mapLoading = false
|
|
|
+ this.addMachineMarkers()
|
|
|
+ console.log('高德地图初始化完成')
|
|
|
+ })
|
|
|
+
|
|
|
+ // 创建信息窗口
|
|
|
+ this.infoWindow = new AMap.InfoWindow({
|
|
|
+ anchor: 'bottom-center',
|
|
|
+ offset: new AMap.Pixel(0, -30)
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 添加农机标记到地图
|
|
|
+ addMachineMarkers() {
|
|
|
+ if (!this.amap) return
|
|
|
+
|
|
|
+ // 清除现有标记
|
|
|
+ this.clearMachineMarkers()
|
|
|
+
|
|
|
+ this.visibleMachines.forEach(machine => {
|
|
|
+ // 将百分比位置转换为真实坐标(这里需要根据实际情况调整)
|
|
|
+ const lng = this.mapCenter[0] + (machine.position.x - 50) * 0.01
|
|
|
+ const lat = this.mapCenter[1] + (50 - machine.position.y) * 0.01
|
|
|
+
|
|
|
+ // 创建自定义标记DOM元素
|
|
|
+ const markerDiv = document.createElement('div')
|
|
|
+ markerDiv.className = 'custom-marker'
|
|
|
+ markerDiv.innerHTML = this.createMarkerContent(machine)
|
|
|
+
|
|
|
+ // 创建标记
|
|
|
+ const marker = new AMap.Marker({
|
|
|
+ position: [lng, lat],
|
|
|
+ title: machine.machineName,
|
|
|
+ content: markerDiv,
|
|
|
+ anchor: 'center',
|
|
|
+ offset: new AMap.Pixel(0, 0)
|
|
|
+ })
|
|
|
+
|
|
|
+ // 添加点击事件
|
|
|
+ marker.on('click', () => {
|
|
|
+ this.showMachineInfoWindow(machine, marker)
|
|
|
+ })
|
|
|
+
|
|
|
+ // 添加标记到地图
|
|
|
+ this.amap.add(marker)
|
|
|
+ this.machineMarkers.push(marker)
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 创建农机标记内容
|
|
|
+ createMarkerContent(machine) {
|
|
|
+ const statusColor = this.getMarkerColor(machine.onlineStatus)
|
|
|
+ const icon = this.getMachineIcon(machine.machineType)
|
|
|
+
|
|
|
+ return `
|
|
|
+ <div class="amap-marker" style="background-color: ${statusColor}; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0,0,0,0.15); border: 2px solid white; cursor: pointer; transition: all 0.3s ease;">
|
|
|
+ <i class="${icon}" style="color: white; font-size: 16px;"></i>
|
|
|
+ </div>
|
|
|
+ `
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取标记颜色
|
|
|
+ getMarkerColor(status) {
|
|
|
+ const colorMap = {
|
|
|
+ 0: '#9ca3af', // 离线
|
|
|
+ 1: '#10b981', // 在线
|
|
|
+ 2: '#3b82f6', // 作业中
|
|
|
+ 3: '#f59e0b' // 告警
|
|
|
+ }
|
|
|
+ return colorMap[status] || '#9ca3af'
|
|
|
+ },
|
|
|
+
|
|
|
+ // 显示农机信息窗口
|
|
|
+ showMachineInfoWindow(machine, marker) {
|
|
|
+ const content = `
|
|
|
+ <div class="machine-info-window">
|
|
|
+ <div class="info-header">
|
|
|
+ <h4>${machine.machineName}</h4>
|
|
|
+ <span class="status-tag status-${machine.onlineStatus}">${this.getStatusText(machine.onlineStatus)}</span>
|
|
|
+ </div>
|
|
|
+ <div class="info-content">
|
|
|
+ <p><strong>设备编号:</strong>${machine.machineCode}</p>
|
|
|
+ <p><strong>负责人:</strong>${machine.operator}</p>
|
|
|
+ <p><strong>当前任务:</strong>${machine.currentTask}</p>
|
|
|
+ <p><strong>所在地块:</strong>${machine.fieldArea}</p>
|
|
|
+ <p><strong>作业进度:</strong>${machine.workProgress}%</p>
|
|
|
+ </div>
|
|
|
+ <div class="info-actions">
|
|
|
+ <button onclick="window.viewMachineDetail('${machine.id}')" class="detail-btn">查看详情</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `
|
|
|
+
|
|
|
+ this.infoWindow.setContent(content)
|
|
|
+ this.infoWindow.open(this.amap, marker.getPosition())
|
|
|
+ },
|
|
|
+
|
|
|
+ // 清除农机标记
|
|
|
+ clearMachineMarkers() {
|
|
|
+ if (this.machineMarkers.length > 0) {
|
|
|
+ this.amap.remove(this.machineMarkers)
|
|
|
+ this.machineMarkers = []
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 地图控制方法更新
|
|
|
+ zoomIn() {
|
|
|
+ if (this.amap) {
|
|
|
+ this.amap.zoomIn()
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ zoomOut() {
|
|
|
+ if (this.amap) {
|
|
|
+ this.amap.zoomOut()
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ centerMap() {
|
|
|
+ if (this.amap) {
|
|
|
+ this.amap.setCenter(this.mapCenter)
|
|
|
+ this.amap.setZoom(12)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ toggleMapFullscreen() {
|
|
|
+ const mapContainer = this.$refs.mapContainer
|
|
|
+ if (!document.fullscreenElement) {
|
|
|
+ mapContainer.requestFullscreen().then(() => {
|
|
|
+ this.$message.success('已进入全屏模式')
|
|
|
+ // 全屏后重新调整地图大小
|
|
|
+ setTimeout(() => {
|
|
|
+ if (this.amap) {
|
|
|
+ this.amap.getSize()
|
|
|
+ }
|
|
|
+ }, 100)
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ document.exitFullscreen().then(() => {
|
|
|
+ this.$message.success('已退出全屏')
|
|
|
+ // 退出全屏后重新调整地图大小
|
|
|
+ setTimeout(() => {
|
|
|
+ if (this.amap) {
|
|
|
+ this.amap.getSize()
|
|
|
+ }
|
|
|
+ }, 100)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 显示数据提示
|
|
|
+ showDataTooltip(event, index) {
|
|
|
+ const tooltip = this.createTooltip();
|
|
|
+
|
|
|
+ // 根据当前选择的指标获取对应的数据
|
|
|
+ const chartData = {
|
|
|
+ area: {
|
|
|
+ unit: '亩',
|
|
|
+ points: [120, 135, 145, 160, 155, 170, 185, 200, 215, 230]
|
|
|
+ },
|
|
|
+ time: {
|
|
|
+ unit: '小时',
|
|
|
+ points: [8, 9, 9.5, 10, 9.8, 10.5, 11, 11.5, 12, 12.5]
|
|
|
+ },
|
|
|
+ efficiency: {
|
|
|
+ unit: '亩/小时',
|
|
|
+ points: [12, 13, 13.5, 14, 13.8, 14.2, 14.8, 15.2, 15.8, 16.2]
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const dates = ['1/9', '1/10', '1/11', '1/12', '1/13', '1/14', '1/15', '1/16', '1/17', '今日'];
|
|
|
+ const currentData = chartData[this.trendMetric];
|
|
|
+
|
|
|
+ if (currentData && index < currentData.points.length) {
|
|
|
+ const value = currentData.points[index];
|
|
|
+ const unit = currentData.unit;
|
|
|
+ const date = dates[index];
|
|
|
+
|
|
|
+ tooltip.innerHTML = `
|
|
|
+ <div style="font-weight: 600; margin-bottom: 4px;">${date}</div>
|
|
|
+ <div style="color: #3BB44A;">${value} ${unit}</div>
|
|
|
+ `;
|
|
|
+
|
|
|
+ const rect = event.target.getBoundingClientRect();
|
|
|
+ tooltip.style.left = rect.left + 'px';
|
|
|
+ tooltip.style.top = (rect.top - 70) + 'px';
|
|
|
+ tooltip.style.opacity = '1';
|
|
|
+ tooltip.style.display = 'block';
|
|
|
+ tooltip.style.visibility = 'visible';
|
|
|
+ tooltip.style.position = 'fixed';
|
|
|
+ tooltip.style.zIndex = '99999';
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 隐藏数据提示
|
|
|
+ hideDataTooltip() {
|
|
|
+ const tooltip = document.getElementById('chart-tooltip');
|
|
|
+ if (tooltip) {
|
|
|
+ tooltip.style.opacity = '0';
|
|
|
+ setTimeout(() => {
|
|
|
+ tooltip.style.display = 'none';
|
|
|
+ }, 200);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 显示保养数据提示
|
|
|
+ showMaintenanceDataTooltip(event, index) {
|
|
|
+ const tooltip = this.createMaintenanceTooltip();
|
|
|
+
|
|
|
+ // 根据当前选择的指标获取对应的数据
|
|
|
+ const chartData = {
|
|
|
+ cost: {
|
|
|
+ unit: '元',
|
|
|
+ points: [3200, 3450, 3180, 2950, 3100, 2850, 2700, 2500]
|
|
|
+ },
|
|
|
+ frequency: {
|
|
|
+ unit: '次/天',
|
|
|
+ points: [2.1, 2.2, 2.0, 1.9, 2.1, 2.0, 1.8, 1.7]
|
|
|
+ },
|
|
|
+ efficiency: {
|
|
|
+ unit: '%',
|
|
|
+ points: [82, 84, 81, 79, 83, 80, 78, 76]
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const dates = ['1/9', '1/10', '1/11', '1/12', '1/13', '1/14', '1/15', '今日'];
|
|
|
+ const currentData = chartData[this.maintenanceTrendMetric];
|
|
|
+
|
|
|
+ console.log('保养tooltip调试:', {
|
|
|
+ metric: this.maintenanceTrendMetric,
|
|
|
+ index: index,
|
|
|
+ currentData: currentData,
|
|
|
+ value: currentData ? currentData.points[index] : null
|
|
|
+ });
|
|
|
+
|
|
|
+ if (currentData && index < currentData.points.length) {
|
|
|
+ const value = currentData.points[index];
|
|
|
+ const unit = currentData.unit;
|
|
|
+ const date = dates[index];
|
|
|
+
|
|
|
+ let displayValue;
|
|
|
+ if (this.maintenanceTrendMetric === 'cost') {
|
|
|
+ displayValue = `¥${value.toLocaleString()}`;
|
|
|
+ } else if (this.maintenanceTrendMetric === 'frequency') {
|
|
|
+ displayValue = `${value} ${unit}`;
|
|
|
+ } else {
|
|
|
+ displayValue = `${value}${unit}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('保养tooltip显示值:', displayValue);
|
|
|
+
|
|
|
+ tooltip.innerHTML = `
|
|
|
+ <div style="font-weight: 600; margin-bottom: 4px;">${date}</div>
|
|
|
+ <div style="color: #FF9800;">${displayValue}</div>
|
|
|
+ `;
|
|
|
+
|
|
|
+ const rect = event.target.getBoundingClientRect();
|
|
|
+ tooltip.style.left = rect.left + 'px';
|
|
|
+ tooltip.style.top = (rect.top - 70) + 'px';
|
|
|
+ tooltip.style.opacity = '1';
|
|
|
+ tooltip.style.display = 'block';
|
|
|
+ tooltip.style.visibility = 'visible';
|
|
|
+ tooltip.style.position = 'fixed';
|
|
|
+ tooltip.style.zIndex = '99999';
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 隐藏保养数据提示
|
|
|
+ hideMaintenanceDataTooltip() {
|
|
|
+ const tooltip = document.getElementById('maintenance-chart-tooltip');
|
|
|
+ if (tooltip) {
|
|
|
+ tooltip.style.opacity = '0';
|
|
|
+ setTimeout(() => {
|
|
|
+ tooltip.style.display = 'none';
|
|
|
+ }, 200);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 创建保养提示框
|
|
|
+ createMaintenanceTooltip() {
|
|
|
+ let tooltip = document.getElementById('maintenance-chart-tooltip');
|
|
|
+ if (!tooltip) {
|
|
|
+ tooltip = document.createElement('div');
|
|
|
+ tooltip.id = 'maintenance-chart-tooltip';
|
|
|
+ tooltip.className = 'maintenance-chart-tooltip';
|
|
|
+ tooltip.style.display = 'none';
|
|
|
+ tooltip.style.opacity = '0';
|
|
|
+ document.body.appendChild(tooltip);
|
|
|
+ }
|
|
|
+ return tooltip;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 更新保养趋势图
|
|
|
+ updateMaintenanceTrendChart() {
|
|
|
+ const summaryData = {
|
|
|
+ cost: {
|
|
|
+ today: '¥2,500',
|
|
|
+ yesterday: '¥2,800',
|
|
|
+ growth: '-10.7%'
|
|
|
+ },
|
|
|
+ frequency: {
|
|
|
+ today: '2.3 次/天',
|
|
|
+ yesterday: '2.1 次/天',
|
|
|
+ growth: '+9.5%'
|
|
|
+ },
|
|
|
+ efficiency: {
|
|
|
+ today: '85.2%',
|
|
|
+ yesterday: '82.1%',
|
|
|
+ growth: '+3.8%'
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 更新底部统计数据
|
|
|
+ this.maintenanceSummary = summaryData[this.maintenanceTrendMetric];
|
|
|
+
|
|
|
+ // 更新图表数据
|
|
|
+ this.updateMaintenanceTrendChartData();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 更新保养趋势图表数据
|
|
|
+ updateMaintenanceTrendChartData() {
|
|
|
+ // 根据不同的指标设置不同的数据点和单位
|
|
|
+ const chartData = {
|
|
|
+ cost: {
|
|
|
+ unit: '元',
|
|
|
+ points: [3200, 3450, 3180, 2950, 3100, 2850, 2700, 2500],
|
|
|
+ yCoords: [180, 160, 140, 120, 130, 110, 95, 80],
|
|
|
+ path: 'M30,180 L90,160 L150,140 L210,120 L270,130 L330,110 L390,95 L450,80',
|
|
|
+ areaPath: 'M30,180 L90,160 L150,140 L210,120 L270,130 L330,110 L390,95 L450,80 L450,260 L30,260 Z'
|
|
|
+ },
|
|
|
+ frequency: {
|
|
|
+ unit: '次/天',
|
|
|
+ points: [2.1, 2.2, 2.0, 1.9, 2.1, 2.0, 1.8, 1.7],
|
|
|
+ yCoords: [170, 160, 180, 190, 170, 180, 200, 210],
|
|
|
+ path: 'M30,170 L90,160 L150,180 L210,190 L270,170 L330,180 L390,200 L450,210',
|
|
|
+ areaPath: 'M30,170 L90,160 L150,180 L210,190 L270,170 L330,180 L390,200 L450,210 L450,260 L30,260 Z'
|
|
|
+ },
|
|
|
+ efficiency: {
|
|
|
+ unit: '%',
|
|
|
+ points: [82, 84, 81, 79, 83, 80, 78, 76],
|
|
|
+ yCoords: [160, 140, 170, 190, 150, 180, 200, 220],
|
|
|
+ path: 'M30,160 L90,140 L150,170 L210,190 L270,150 L330,180 L390,200 L450,220',
|
|
|
+ areaPath: 'M30,160 L90,140 L150,170 L210,190 L270,150 L330,180 L390,200 L450,220 L450,260 L30,260 Z'
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const currentData = chartData[this.maintenanceTrendMetric];
|
|
|
+
|
|
|
+ console.log('保养图表更新调试:', {
|
|
|
+ metric: this.maintenanceTrendMetric,
|
|
|
+ currentData: currentData
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新单位标签
|
|
|
+ this.$nextTick(() => {
|
|
|
+ const unitLabel = document.querySelector('.maintenance-chart-unit-label');
|
|
|
+ if (unitLabel) {
|
|
|
+ unitLabel.textContent = `单位:${currentData.unit}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新SVG路径
|
|
|
+ const trendLine = document.querySelector('.maintenance-trend-chart polyline');
|
|
|
+ const trendArea = document.querySelector('.maintenance-trend-chart path[fill="url(#maintenanceTrendGradient)"]');
|
|
|
+
|
|
|
+ if (trendLine) {
|
|
|
+ // 将路径字符串转换为points格式
|
|
|
+ const points = currentData.path.replace(/[ML]/g, ' ').trim().split(' ').filter(p => p !== '');
|
|
|
+ const pointsString = points.join(' ');
|
|
|
+ trendLine.setAttribute('points', pointsString);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (trendArea) {
|
|
|
+ trendArea.setAttribute('d', currentData.areaPath);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新数据点位置
|
|
|
+ const dataPoints = document.querySelectorAll('.maintenance-trend-chart .maintenance-data-point');
|
|
|
+ dataPoints.forEach((point, index) => {
|
|
|
+ if (index < currentData.yCoords.length) {
|
|
|
+ // 使用预定义的Y坐标,确保与线条路径完全匹配
|
|
|
+ const y = currentData.yCoords[index];
|
|
|
+ point.setAttribute('cy', y);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取保养指标标签
|
|
|
+ getMaintenanceMetricLabel() {
|
|
|
+ const labels = {
|
|
|
+ cost: '费用',
|
|
|
+ frequency: '频次',
|
|
|
+ efficiency: '效率'
|
|
|
+ };
|
|
|
+ return labels[this.maintenanceTrendMetric] || '费用';
|
|
|
+ },
|
|
|
+
|
|
|
+ // 创建提示框
|
|
|
+ createTooltip() {
|
|
|
+ let tooltip = document.getElementById('chart-tooltip');
|
|
|
+ if (!tooltip) {
|
|
|
+ tooltip = document.createElement('div');
|
|
|
+ tooltip.id = 'chart-tooltip';
|
|
|
+ tooltip.className = 'chart-tooltip';
|
|
|
+ tooltip.style.display = 'none';
|
|
|
+ tooltip.style.opacity = '0';
|
|
|
+ document.body.appendChild(tooltip);
|
|
|
+ }
|
|
|
+ return tooltip;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 保养图表交互方法
|
|
|
+ handleMaintenanceLegendHover(status, isEnter) {
|
|
|
+ const segments = document.querySelectorAll('.maintenance-segment');
|
|
|
+ const legendItems = document.querySelectorAll('.maintenance-legend-item-enhanced');
|
|
|
+
|
|
|
+ if (isEnter) {
|
|
|
+ // 高亮对应的饼图段
|
|
|
+ segments.forEach(segment => {
|
|
|
+ if (segment.dataset.status === status) {
|
|
|
+ segment.classList.add('highlighted');
|
|
|
+ } else {
|
|
|
+ segment.classList.add('dimmed');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 高亮对应的图例项
|
|
|
+ legendItems.forEach(item => {
|
|
|
+ if (item.dataset.status === status) {
|
|
|
+ item.classList.add('active');
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 移除所有高亮和暗化效果
|
|
|
+ segments.forEach(segment => {
|
|
|
+ segment.classList.remove('highlighted', 'dimmed');
|
|
|
+ });
|
|
|
+
|
|
|
+ legendItems.forEach(item => {
|
|
|
+ item.classList.remove('active');
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.machines-monitor-container {
|
|
|
+ padding: 20px;
|
|
|
+ background: #f5f7f9;
|
|
|
+ min-height: 100vh;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/* 控制面板 */
|
|
|
+.machines-monitor-container > .control-panel {
|
|
|
+ background: white;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 16px;
|
|
|
+ padding: 1.5rem;
|
|
|
+ margin-bottom: 2rem;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ gap: 2rem;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.machines-monitor-container > .control-panel:hover {
|
|
|
+ transform: translateY(-4px);
|
|
|
+ box-shadow: 0 10px 20px -5px rgba(0, 0, 0, 0.15);
|
|
|
+ border-color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+.control-left {
|
|
|
+ display: flex;
|
|
|
+ gap: 1.5rem;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.form-group {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 0.5rem;
|
|
|
+}
|
|
|
+
|
|
|
+.form-group label {
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: #6b7280;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.form-group select {
|
|
|
+ background-color: white;
|
|
|
+ border: 1px solid #d1d5db;
|
|
|
+ color: #1f2937;
|
|
|
+ padding: 8px 12px;
|
|
|
+ border-radius: 8px;
|
|
|
+ min-width: 150px;
|
|
|
+}
|
|
|
+
|
|
|
+.form-group select:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: #10b981;
|
|
|
+ box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.control-right {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 1rem;
|
|
|
+}
|
|
|
+
|
|
|
+.refresh-btn {
|
|
|
+ background-color: #10b981;
|
|
|
+ color: white;
|
|
|
+ border: none;
|
|
|
+ padding: 10px 20px;
|
|
|
+ border-radius: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ font-weight: 500;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.refresh-btn:hover {
|
|
|
+ background-color: #059669;
|
|
|
+ transform: translateY(-1px);
|
|
|
+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
|
|
|
+}
|
|
|
+
|
|
|
+.auto-refresh {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 0.5rem;
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+
|
|
|
+.auto-refresh-select {
|
|
|
+ background-color: white;
|
|
|
+ border: 1px solid #d1d5db;
|
|
|
+ color: #1f2937;
|
|
|
+ padding: 4px 8px;
|
|
|
+ border-radius: 6px;
|
|
|
+ font-size: 0.875rem;
|
|
|
+}
|
|
|
+
|
|
|
+.auto-refresh-select:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: #10b981;
|
|
|
+ box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.last-update {
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+
|
|
|
+.last-update span {
|
|
|
+ color: #1f2937;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+/* 统计卡片 */
|
|
|
+.stats-row {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card {
|
|
|
+ background: white;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 12px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card:hover {
|
|
|
+ transform: translateY(-4px);
|
|
|
+ box-shadow: 0 8px 25px rgba(16, 185, 129, 0.25);
|
|
|
+ border-color: #10b981;
|
|
|
+ border-width: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-content {
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-label {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value {
|
|
|
+ font-size: 32px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f2937;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 不同类型卡片的数值颜色 */
|
|
|
+.stat-card.total .stat-value {
|
|
|
+ color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card.online .stat-value {
|
|
|
+ color: #3b82f6;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card.working .stat-value {
|
|
|
+ color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card.warning .stat-value {
|
|
|
+ color: #f59e0b;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card.maintenance-due .stat-value {
|
|
|
+ color: #f59e0b;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card.maintenance-progress .stat-value {
|
|
|
+ color: #3b82f6;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card.maintenance-overdue .stat-value {
|
|
|
+ color: #ef4444;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card.maintenance-cost .stat-value {
|
|
|
+ color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-trend {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+
|
|
|
+/* 地图卡片 */
|
|
|
+.map-card {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.map-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.map-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+}
|
|
|
+
|
|
|
+.map-container {
|
|
|
+ position: relative;
|
|
|
+ height: 400px;
|
|
|
+ background: #f8fafc;
|
|
|
+ border-radius: 8px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.amap-container {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ border-radius: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.map-loading {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ height: 100%;
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+
|
|
|
+.map-area {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(45deg, #f3f4f6 25%, transparent 25%),
|
|
|
+ linear-gradient(-45deg, #f3f4f6 25%, transparent 25%),
|
|
|
+ linear-gradient(45deg, transparent 75%, #f3f4f6 75%),
|
|
|
+ linear-gradient(-45deg, transparent 75%, #f3f4f6 75%);
|
|
|
+ background-size: 20px 20px;
|
|
|
+ background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
|
|
|
+ background-color: #e5e7eb;
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.map-placeholder {
|
|
|
+ text-align: center;
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+
|
|
|
+.map-placeholder i {
|
|
|
+ font-size: 48px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ color: #9ca3af;
|
|
|
+}
|
|
|
+
|
|
|
+.placeholder-title {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ color: #374151;
|
|
|
+}
|
|
|
+
|
|
|
+.placeholder-desc {
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 高德地图农机标记样式 */
|
|
|
+.custom-marker {
|
|
|
+ position: relative;
|
|
|
+ z-index: 10;
|
|
|
+}
|
|
|
+
|
|
|
+.amap-marker {
|
|
|
+ width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
|
+ border: 2px solid white;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.amap-marker:hover {
|
|
|
+ transform: scale(1.2);
|
|
|
+ box-shadow: 0 4px 12px rgba(0,0,0,0.25);
|
|
|
+}
|
|
|
+
|
|
|
+.amap-marker i {
|
|
|
+ font-size: 16px !important;
|
|
|
+ color: white !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 高德地图信息窗口样式 */
|
|
|
+.machine-info-window {
|
|
|
+ width: 280px;
|
|
|
+ font-family: 'Helvetica Neue', Arial, sans-serif;
|
|
|
+}
|
|
|
+
|
|
|
+.machine-info-window .info-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ padding-bottom: 8px;
|
|
|
+ border-bottom: 1px solid #e5e7eb;
|
|
|
+}
|
|
|
+
|
|
|
+.machine-info-window .info-header h4 {
|
|
|
+ margin: 0;
|
|
|
+ color: #1f2937;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.machine-info-window .status-tag {
|
|
|
+ padding: 2px 8px;
|
|
|
+ border-radius: 12px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.machine-info-window .status-tag.status-0 {
|
|
|
+ background-color: rgba(156, 163, 175, 0.1);
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+
|
|
|
+.machine-info-window .status-tag.status-1 {
|
|
|
+ background-color: rgba(16, 185, 129, 0.1);
|
|
|
+ color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+.machine-info-window .status-tag.status-2 {
|
|
|
+ background-color: rgba(59, 130, 246, 0.1);
|
|
|
+ color: #3b82f6;
|
|
|
+}
|
|
|
+
|
|
|
+.machine-info-window .status-tag.status-3 {
|
|
|
+ background-color: rgba(245, 158, 11, 0.1);
|
|
|
+ color: #f59e0b;
|
|
|
+}
|
|
|
+
|
|
|
+.machine-info-window .info-content p {
|
|
|
+ margin: 8px 0;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #374151;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.machine-info-window .info-actions {
|
|
|
+ margin-top: 12px;
|
|
|
+ padding-top: 12px;
|
|
|
+ border-top: 1px solid #e5e7eb;
|
|
|
+}
|
|
|
+
|
|
|
+.machine-info-window .detail-btn {
|
|
|
+ background-color: #10b981;
|
|
|
+ color: white;
|
|
|
+ border: none;
|
|
|
+ padding: 6px 12px;
|
|
|
+ border-radius: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: background-color 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.machine-info-window .detail-btn:hover {
|
|
|
+ background-color: #059669;
|
|
|
+}
|
|
|
+
|
|
|
+/* 农机弹层 */
|
|
|
+.machine-popup {
|
|
|
+ position: absolute;
|
|
|
+ background: white;
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 20px;
|
|
|
+ min-width: 280px;
|
|
|
+ box-shadow: 0 8px 24px rgba(0,0,0,0.15);
|
|
|
+ z-index: 30;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+}
|
|
|
+
|
|
|
+.machine-popup::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ bottom: -8px;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border: 8px solid transparent;
|
|
|
+ border-top-color: white;
|
|
|
+}
|
|
|
+
|
|
|
+.popup-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: flex-start;
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.machine-name {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1f2937;
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.machine-id {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+
|
|
|
+.popup-content {
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.info-item {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.info-item .label {
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+
|
|
|
+.info-item .value {
|
|
|
+ font-weight: 500;
|
|
|
+ color: #374151;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-section {
|
|
|
+ border-top: 1px solid #e5e7eb;
|
|
|
+ padding-top: 12px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.section-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.section-title {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-details .info-item {
|
|
|
+ margin-bottom: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.popup-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 分析卡片 */
|
|
|
+.analysis-card {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.analysis-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: flex-start;
|
|
|
+}
|
|
|
+
|
|
|
+.header-left {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.analysis-title {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1f2937;
|
|
|
+ margin: 0 0 4px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.analysis-subtitle {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #6b7280;
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.header-right {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 作业统计指标卡片 */
|
|
|
+.metrics-row {
|
|
|
+ margin-bottom: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.work-metric-card {
|
|
|
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 16px;
|
|
|
+ padding: 24px;
|
|
|
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
|
+ animation: fadeInUp 0.6s ease-out;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes fadeInUp {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(20px);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 为不同卡片添加延迟动画 */
|
|
|
+.work-metric-card:nth-child(1) {
|
|
|
+ animation-delay: 0.1s;
|
|
|
+}
|
|
|
+
|
|
|
+.work-metric-card:nth-child(2) {
|
|
|
+ animation-delay: 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.work-metric-card:nth-child(3) {
|
|
|
+ animation-delay: 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+.work-metric-card:hover {
|
|
|
+ transform: translateY(-6px);
|
|
|
+ box-shadow: 0 12px 32px rgba(16, 185, 129, 0.15);
|
|
|
+ border-color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: flex-start;
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-icon-wrapper {
|
|
|
+ width: 56px;
|
|
|
+ height: 56px;
|
|
|
+ border-radius: 16px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
|
|
|
+ border: 2px solid #bbf7d0;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.work-metric-card:hover .metric-icon-wrapper {
|
|
|
+ transform: scale(1.1);
|
|
|
+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.metric-icon-wrapper i {
|
|
|
+ font-size: 24px;
|
|
|
+ color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+/* 不同类型卡片的图标背景色 */
|
|
|
+.area-metric .metric-icon-wrapper {
|
|
|
+ background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
|
|
|
+ border-color: #bbf7d0;
|
|
|
+}
|
|
|
+
|
|
|
+.area-metric .metric-icon-wrapper i {
|
|
|
+ color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+.time-metric .metric-icon-wrapper {
|
|
|
+ background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
|
|
|
+ border-color: #bfdbfe;
|
|
|
+}
|
|
|
+
|
|
|
+.time-metric .metric-icon-wrapper i {
|
|
|
+ color: #3b82f6;
|
|
|
+}
|
|
|
+
|
|
|
+.efficiency-metric .metric-icon-wrapper {
|
|
|
+ background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);
|
|
|
+ border-color: #fde68a;
|
|
|
+}
|
|
|
+
|
|
|
+.efficiency-metric .metric-icon-wrapper i {
|
|
|
+ color: #f59e0b;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-trend-badge {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+ padding: 6px 12px;
|
|
|
+ border-radius: 20px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-trend-badge.positive {
|
|
|
+ background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%);
|
|
|
+ color: #10b981;
|
|
|
+ border: 1px solid #86efac;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-trend-badge.negative {
|
|
|
+ background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
|
|
|
+ color: #ef4444;
|
|
|
+ border: 1px solid #fca5a5;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-trend-badge.neutral {
|
|
|
+ background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
|
|
|
+ color: #6b7280;
|
|
|
+ border: 1px solid #d1d5db;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-body {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-value {
|
|
|
+ font-size: 32px;
|
|
|
+ font-weight: 800;
|
|
|
+ color: #1f2937;
|
|
|
+ line-height: 1.1;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ letter-spacing: -0.5px;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-unit {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-left: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-label {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+ margin-bottom: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-desc {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #9ca3af;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-footer {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.progress-bar {
|
|
|
+ flex: 1;
|
|
|
+ height: 6px;
|
|
|
+ background: #f3f4f6;
|
|
|
+ border-radius: 3px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.progress-fill {
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(90deg, #10b981, #34d399);
|
|
|
+ border-radius: 3px;
|
|
|
+ transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.progress-fill::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: -100%;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
|
|
+ animation: shimmer 2s infinite;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes shimmer {
|
|
|
+ 0% {
|
|
|
+ left: -100%;
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ left: 100%;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 不同类型卡片的进度条颜色 */
|
|
|
+.time-metric .progress-fill {
|
|
|
+ background: linear-gradient(90deg, #3b82f6, #60a5fa);
|
|
|
+}
|
|
|
+
|
|
|
+.efficiency-metric .progress-fill {
|
|
|
+ background: linear-gradient(90deg, #f59e0b, #fbbf24);
|
|
|
+}
|
|
|
+
|
|
|
+.progress-text {
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #6b7280;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养统计指标卡片样式 */
|
|
|
+.maintenance-metric-card {
|
|
|
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 16px;
|
|
|
+ padding: 24px;
|
|
|
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
|
+ animation: fadeInUp 0.6s ease-out;
|
|
|
+}
|
|
|
+
|
|
|
+/* 为不同卡片添加延迟动画 */
|
|
|
+.maintenance-metric-card:nth-child(1) {
|
|
|
+ animation-delay: 0.1s;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-card:nth-child(2) {
|
|
|
+ animation-delay: 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-card:nth-child(3) {
|
|
|
+ animation-delay: 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-card:hover {
|
|
|
+ transform: translateY(-6px);
|
|
|
+ box-shadow: 0 12px 32px rgba(16, 185, 129, 0.15);
|
|
|
+ border-color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: flex-start;
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-icon-wrapper {
|
|
|
+ width: 56px;
|
|
|
+ height: 56px;
|
|
|
+ border-radius: 16px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
|
|
|
+ border: 2px solid #bbf7d0;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-card:hover .maintenance-metric-icon-wrapper {
|
|
|
+ transform: scale(1.1);
|
|
|
+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-icon-wrapper i {
|
|
|
+ font-size: 24px;
|
|
|
+ color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+/* 不同类型保养卡片的图标背景色 */
|
|
|
+.execution-metric .maintenance-metric-icon-wrapper {
|
|
|
+ background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
|
|
|
+ border-color: #bbf7d0;
|
|
|
+}
|
|
|
+
|
|
|
+.execution-metric .maintenance-metric-icon-wrapper i {
|
|
|
+ color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+.cycle-metric .maintenance-metric-icon-wrapper {
|
|
|
+ background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
|
|
|
+ border-color: #bfdbfe;
|
|
|
+}
|
|
|
+
|
|
|
+.cycle-metric .maintenance-metric-icon-wrapper i {
|
|
|
+ color: #3b82f6;
|
|
|
+}
|
|
|
+
|
|
|
+.cost-metric .maintenance-metric-icon-wrapper {
|
|
|
+ background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);
|
|
|
+ border-color: #fde68a;
|
|
|
+}
|
|
|
+
|
|
|
+.cost-metric .maintenance-metric-icon-wrapper i {
|
|
|
+ color: #f59e0b;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-trend-badge {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+ padding: 6px 12px;
|
|
|
+ border-radius: 20px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-trend-badge.positive {
|
|
|
+ background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%);
|
|
|
+ color: #10b981;
|
|
|
+ border: 1px solid #86efac;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-trend-badge.negative {
|
|
|
+ background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
|
|
|
+ color: #ef4444;
|
|
|
+ border: 1px solid #fca5a5;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-trend-badge.neutral {
|
|
|
+ background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
|
|
|
+ color: #6b7280;
|
|
|
+ border: 1px solid #d1d5db;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-body {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-value {
|
|
|
+ font-size: 32px;
|
|
|
+ font-weight: 800;
|
|
|
+ color: #1f2937;
|
|
|
+ line-height: 1.1;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ letter-spacing: -0.5px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-unit {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-left: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-label {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+ margin-bottom: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-desc {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #9ca3af;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-footer {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-progress-bar {
|
|
|
+ flex: 1;
|
|
|
+ height: 6px;
|
|
|
+ background: #f3f4f6;
|
|
|
+ border-radius: 3px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-progress-fill {
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(90deg, #10b981, #34d399);
|
|
|
+ border-radius: 3px;
|
|
|
+ transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-progress-fill::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: -100%;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
|
|
+ animation: shimmer 2s infinite;
|
|
|
+}
|
|
|
+
|
|
|
+/* 不同类型保养卡片的进度条颜色 */
|
|
|
+.cycle-metric .maintenance-progress-fill {
|
|
|
+ background: linear-gradient(90deg, #3b82f6, #60a5fa);
|
|
|
+}
|
|
|
+
|
|
|
+.cost-metric .maintenance-progress-fill {
|
|
|
+ background: linear-gradient(90deg, #f59e0b, #fbbf24);
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-progress-text {
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #6b7280;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+/* 保留原有的保养指标卡片样式(兼容性) */
|
|
|
+.metric-card {
|
|
|
+ background: linear-gradient(135deg, #ffffff 0%, #f9fafb 100%);
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 20px;
|
|
|
+ transition: all 0.3s;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-card::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ height: 4px;
|
|
|
+ background: linear-gradient(90deg, #10b981, #34d399);
|
|
|
+}
|
|
|
+
|
|
|
+.metric-card:hover {
|
|
|
+ transform: translateY(-4px);
|
|
|
+ box-shadow: 0 8px 24px rgba(16, 185, 129, 0.12);
|
|
|
+}
|
|
|
+
|
|
|
+.metric-icon {
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-icon i {
|
|
|
+ font-size: 32px;
|
|
|
+ color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-content {
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-label {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-value {
|
|
|
+ font-size: 28px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f2937;
|
|
|
+ line-height: 1.1;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-unit {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-left: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-trend {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-trend.positive .trend-text {
|
|
|
+ color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+.trend-text {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.trend-desc {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #9ca3af;
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养指标特殊样式 */
|
|
|
+.metric-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-title {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-trend.neutral {
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-body {
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.metric-subtitle {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #9ca3af;
|
|
|
+ margin-top: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 图表区域 */
|
|
|
+.charts-row {
|
|
|
+ margin-bottom: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-card {
|
|
|
+ background: white;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 20px;
|
|
|
+ transition: all 0.3s;
|
|
|
+ min-height: 400px;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-card:hover {
|
|
|
+ box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
|
|
+}
|
|
|
+
|
|
|
+.chart-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ padding-bottom: 12px;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1f2937;
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-content {
|
|
|
+ padding: 16px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-container {
|
|
|
+ height: 260px;
|
|
|
+ background: #f8fafc;
|
|
|
+ border-radius: 8px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #6b7280;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 趋势汇总 */
|
|
|
+.trend-summary {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-around;
|
|
|
+ padding: 16px 0;
|
|
|
+ border-top: 1px solid #f0f0f0;
|
|
|
+ margin-top: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-item {
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-label {
|
|
|
+ display: block;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-bottom: 6px;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-value {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-value.primary {
|
|
|
+ color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-value.positive {
|
|
|
+ color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式设计 */
|
|
|
+@media (max-width: 1200px) {
|
|
|
+
|
|
|
+ .control-panel {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 1rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .control-left, .control-right {
|
|
|
+ justify-content: space-between;
|
|
|
+ }
|
|
|
+
|
|
|
+ .charts-row {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .charts-row .el-col {
|
|
|
+ width: 100%;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 作业统计卡片响应式 */
|
|
|
+ .metrics-row .el-col {
|
|
|
+ margin-bottom: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .work-metric-card {
|
|
|
+ padding: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .metric-value {
|
|
|
+ font-size: 28px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .metric-icon-wrapper {
|
|
|
+ width: 48px;
|
|
|
+ height: 48px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .metric-icon-wrapper i {
|
|
|
+ font-size: 20px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .machines-monitor-container {
|
|
|
+ padding: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .control-panel {
|
|
|
+ padding: 1rem;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 1rem;
|
|
|
+ align-items: stretch;
|
|
|
+ }
|
|
|
+
|
|
|
+ .control-left {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 1rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .control-right {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: stretch;
|
|
|
+ gap: 1rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-group {
|
|
|
+ justify-content: space-between;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stats-row .el-col {
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .metrics-row .el-col {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 作业统计卡片移动端优化 */
|
|
|
+ .work-metric-card {
|
|
|
+ padding: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .metric-header {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .metric-icon-wrapper {
|
|
|
+ width: 44px;
|
|
|
+ height: 44px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .metric-icon-wrapper i {
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .metric-value {
|
|
|
+ font-size: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .metric-label {
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .metric-desc {
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .metric-footer {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .progress-bar {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 保养统计卡片移动端优化 */
|
|
|
+ .maintenance-metric-card {
|
|
|
+ padding: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-metric-header {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-metric-icon-wrapper {
|
|
|
+ width: 44px;
|
|
|
+ height: 44px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-metric-icon-wrapper i {
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-metric-value {
|
|
|
+ font-size: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-metric-label {
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-metric-desc {
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-metric-footer {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-progress-bar {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .map-container {
|
|
|
+ height: 300px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .machine-popup {
|
|
|
+ min-width: 240px;
|
|
|
+ padding: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .popup-actions {
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 480px) {
|
|
|
+ .work-metric-card {
|
|
|
+ padding: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .metric-header {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .metric-trend-badge {
|
|
|
+ align-self: flex-end;
|
|
|
+ }
|
|
|
+
|
|
|
+ .metric-value {
|
|
|
+ font-size: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .metric-unit {
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .metric-label {
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 保养统计卡片小屏幕优化 */
|
|
|
+ .maintenance-metric-card {
|
|
|
+ padding: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-metric-header {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-metric-trend-badge {
|
|
|
+ align-self: flex-end;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-metric-value {
|
|
|
+ font-size: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-metric-unit {
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-metric-label {
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 图表卡片内容横向布局和高度统一 */
|
|
|
+.chart-flex-content {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex: 1;
|
|
|
+ padding: 0;
|
|
|
+ position: relative;
|
|
|
+ box-sizing: border-box;
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.donut-chart-flex {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ gap: 40px;
|
|
|
+}
|
|
|
+
|
|
|
+.donut-container {
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 240px;
|
|
|
+ height: 240px;
|
|
|
+}
|
|
|
+
|
|
|
+.donut-legend-vertical {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 24px;
|
|
|
+ min-width: 140px;
|
|
|
+}
|
|
|
+
|
|
|
+.donut-legend-vertical .legend-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ font-size: 16px;
|
|
|
+ color: #374151;
|
|
|
+ padding: 8px 12px;
|
|
|
+ border-radius: 8px;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.donut-legend-vertical .legend-item:hover {
|
|
|
+ background-color: #f9fafb;
|
|
|
+ transform: translateX(4px);
|
|
|
+}
|
|
|
+
|
|
|
+.donut-legend-vertical .legend-color {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ border-radius: 6px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.donut-legend-vertical .legend-label {
|
|
|
+ font-weight: 600;
|
|
|
+ min-width: 64px;
|
|
|
+ color: #1f2937;
|
|
|
+}
|
|
|
+
|
|
|
+.donut-legend-vertical .legend-value {
|
|
|
+ font-weight: 700;
|
|
|
+ color: #10b981;
|
|
|
+ margin-left: auto;
|
|
|
+ font-size: 18px;
|
|
|
+}
|
|
|
+
|
|
|
+.trend-chart {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-area {
|
|
|
+ width: 100%;
|
|
|
+ height: 240px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.trend-summary-align {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-around;
|
|
|
+ align-items: center;
|
|
|
+ padding: 20px 0 0 0;
|
|
|
+ border-top: 1px solid #e5e7eb;
|
|
|
+ margin-top: 0;
|
|
|
+ background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
|
|
|
+ border-radius: 0 0 12px 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-item {
|
|
|
+ text-align: center;
|
|
|
+ padding: 16px 20px;
|
|
|
+ border-radius: 8px;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-item:hover {
|
|
|
+ background-color: white;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
+ transform: translateY(-2px);
|
|
|
+}
|
|
|
+
|
|
|
+.summary-label {
|
|
|
+ display: block;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ font-weight: 500;
|
|
|
+ text-transform: uppercase;
|
|
|
+ letter-spacing: 0.5px;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-value {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #374151;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-value.primary {
|
|
|
+ color: #10b981;
|
|
|
+ font-size: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-value.positive {
|
|
|
+ color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+/* 图表卡片布局优化 */
|
|
|
+.chart-card-donut {
|
|
|
+ min-height: 420px;
|
|
|
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 16px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-card-donut:hover {
|
|
|
+ transform: translateY(-4px);
|
|
|
+ box-shadow: 0 12px 32px rgba(16, 185, 129, 0.15);
|
|
|
+ border-color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-card-trend {
|
|
|
+ min-height: 420px;
|
|
|
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 16px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-card-trend:hover {
|
|
|
+ transform: translateY(-4px);
|
|
|
+ box-shadow: 0 12px 32px rgba(16, 185, 129, 0.15);
|
|
|
+ border-color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-unit-label {
|
|
|
+ position: absolute;
|
|
|
+ top: 12px;
|
|
|
+ right: 20px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6B7280;
|
|
|
+ font-weight: 600;
|
|
|
+ z-index: 10;
|
|
|
+ background: rgba(255, 255, 255, 0.9);
|
|
|
+ padding: 4px 8px;
|
|
|
+ border-radius: 6px;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+}
|
|
|
+
|
|
|
+/* 数据点交互样式 */
|
|
|
+.data-point {
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.data-point:hover {
|
|
|
+ r: 8;
|
|
|
+ fill: #2A8F38;
|
|
|
+ stroke: white;
|
|
|
+ stroke-width: 3;
|
|
|
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
|
|
|
+}
|
|
|
+
|
|
|
+/* Tooltip样式 */
|
|
|
+.chart-tooltip {
|
|
|
+ position: fixed !important;
|
|
|
+ background: rgba(0, 0, 0, 0.9) !important;
|
|
|
+ color: white !important;
|
|
|
+ padding: 10px 14px !important;
|
|
|
+ border-radius: 8px !important;
|
|
|
+ font-size: 13px !important;
|
|
|
+ pointer-events: none !important;
|
|
|
+ z-index: 99999 !important;
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.3s ease !important;
|
|
|
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2) !important;
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
|
|
+ display: none;
|
|
|
+ min-width: 80px;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-tooltip.show {
|
|
|
+ opacity: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-tooltip::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 100%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border: 5px solid transparent;
|
|
|
+ border-top-color: rgba(0, 0, 0, 0.9);
|
|
|
+}
|
|
|
+
|
|
|
+/* 图表标题样式优化 */
|
|
|
+.chart-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ padding-bottom: 16px;
|
|
|
+ border-bottom: 2px solid #f0f4f1;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-title {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f2937;
|
|
|
+ margin: 0;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+.chart-controls {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式布局优化 */
|
|
|
+@media (max-width: 1200px) {
|
|
|
+ .chart-flex-content {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ .donut-chart-flex {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .donut-legend-vertical {
|
|
|
+ flex-direction: row;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 20px;
|
|
|
+ margin-top: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-area {
|
|
|
+ width: 100%;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-card-donut,
|
|
|
+ .chart-card-trend {
|
|
|
+ min-height: 360px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .chart-flex-content {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ .donut-container {
|
|
|
+ width: 200px;
|
|
|
+ height: 200px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .donut-legend-vertical {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16px;
|
|
|
+ align-items: flex-start;
|
|
|
+ }
|
|
|
+
|
|
|
+ .donut-legend-vertical .legend-item {
|
|
|
+ font-size: 14px;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .donut-legend-vertical .legend-color {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .donut-legend-vertical .legend-label {
|
|
|
+ min-width: 56px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .donut-legend-vertical .legend-value {
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-unit-label {
|
|
|
+ top: 8px;
|
|
|
+ right: 16px;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .trend-summary-align {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 16px 0 0 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .summary-item {
|
|
|
+ padding: 12px 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .summary-value {
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .summary-value.primary {
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 作业图表卡片样式 */
|
|
|
+.work-chart-card {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ height: 540px;
|
|
|
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 16px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.work-chart-card:hover {
|
|
|
+ transform: translateY(-4px);
|
|
|
+ box-shadow: 0 12px 32px rgba(59, 180, 74, 0.15);
|
|
|
+ border-color: #3BB44A;
|
|
|
+}
|
|
|
+
|
|
|
+/* 作业环形图样式 - 水平布局 */
|
|
|
+.work-donut-chart-horizontal {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 24px;
|
|
|
+ flex: 1;
|
|
|
+ position: relative;
|
|
|
+ box-sizing: border-box;
|
|
|
+ overflow: hidden;
|
|
|
+ min-height: 0;
|
|
|
+ gap: 32px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 左侧饼图容器 */
|
|
|
+.work-donut-container-side {
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 280px;
|
|
|
+ height: 280px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 右侧图例区域 */
|
|
|
+.work-donut-legend-side {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 20px;
|
|
|
+ padding: 20px 0;
|
|
|
+ flex: 1;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: flex-start;
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+}
|
|
|
+
|
|
|
+/* 作业图表图例项 */
|
|
|
+.work-legend-item-enhanced {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 12px 16px;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: rgba(255, 255, 255, 0.8);
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ cursor: pointer;
|
|
|
+ width: 100%;
|
|
|
+ box-sizing: border-box;
|
|
|
+ animation: fadeInSlide 0.6s ease-out;
|
|
|
+ animation-delay: calc(var(--index) * 0.1s);
|
|
|
+ animation-fill-mode: both;
|
|
|
+}
|
|
|
+
|
|
|
+.work-legend-item-enhanced:hover {
|
|
|
+ background: white;
|
|
|
+ border-color: #3BB44A;
|
|
|
+ transform: translateX(4px);
|
|
|
+ box-shadow: 0 4px 12px rgba(59, 180, 74, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.work-legend-color {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ border-radius: 4px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.work-legend-content-enhanced {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.work-legend-label {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+}
|
|
|
+
|
|
|
+.work-legend-value {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #3BB44A;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/* 作业环形图中心样式 */
|
|
|
+.work-donut-center-label {
|
|
|
+ font-size: 16px;
|
|
|
+ fill: #6B7280;
|
|
|
+ font-weight: 500;
|
|
|
+ font-family: 'Helvetica Neue', Arial, sans-serif;
|
|
|
+}
|
|
|
+
|
|
|
+.work-donut-center-value {
|
|
|
+ font-size: 32px;
|
|
|
+ fill: #3BB44A;
|
|
|
+ font-weight: 700;
|
|
|
+ font-family: 'Helvetica Neue', Arial, sans-serif;
|
|
|
+}
|
|
|
+
|
|
|
+/* 作业趋势图样式 */
|
|
|
+.work-trend-chart {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ height: 100%;
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.work-chart-area {
|
|
|
+ width: 100%;
|
|
|
+ height: 260px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ position: relative;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.work-chart-unit-label {
|
|
|
+ position: absolute;
|
|
|
+ top: 16px;
|
|
|
+ right: 24px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6b7280;
|
|
|
+ font-weight: 500;
|
|
|
+ background: rgba(255, 255, 255, 0.9);
|
|
|
+ padding: 4px 8px;
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+}
|
|
|
+
|
|
|
+/* 作业趋势汇总 */
|
|
|
+.work-trend-summary {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-around;
|
|
|
+ align-items: center;
|
|
|
+ padding: 20px 0;
|
|
|
+ border-top: 1px solid #e5e7eb;
|
|
|
+ margin-top: 0;
|
|
|
+ background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
|
|
|
+ border-radius: 0 0 12px 12px;
|
|
|
+ min-height: 80px;
|
|
|
+}
|
|
|
+
|
|
|
+.work-summary-item {
|
|
|
+ text-align: center;
|
|
|
+ padding: 16px 20px;
|
|
|
+ border-radius: 8px;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.work-summary-item:hover {
|
|
|
+ background-color: white;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
+ transform: translateY(-2px);
|
|
|
+}
|
|
|
+
|
|
|
+.work-summary-label {
|
|
|
+ display: block;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ font-weight: 500;
|
|
|
+ text-transform: uppercase;
|
|
|
+ letter-spacing: 0.5px;
|
|
|
+}
|
|
|
+
|
|
|
+.work-summary-value {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #374151;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.work-summary-value.primary {
|
|
|
+ color: #3BB44A;
|
|
|
+ font-size: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.work-summary-value.positive {
|
|
|
+ color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/* 图表动画效果 */
|
|
|
+.chart-card-donut,
|
|
|
+.chart-card-trend,
|
|
|
+.work-chart-card {
|
|
|
+ animation: fadeInUp 0.6s ease-out;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes fadeInUp {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(20px);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 环形图动画 */
|
|
|
+.donut-container svg circle {
|
|
|
+ animation: drawCircle 1.5s ease-out;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes drawCircle {
|
|
|
+ from {
|
|
|
+ stroke-dasharray: 0 263;
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ stroke-dasharray: var(--dash-array) 263;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 趋势线动画 */
|
|
|
+.chart-area svg polyline {
|
|
|
+ animation: drawLine 2s ease-out;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes drawLine {
|
|
|
+ from {
|
|
|
+ stroke-dasharray: 0 1000;
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ stroke-dasharray: 1000 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 数据点动画 */
|
|
|
+.data-point {
|
|
|
+ animation: fadeInScale 0.8s ease-out;
|
|
|
+ animation-delay: calc(var(--index) * 0.1s);
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes fadeInScale {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: scale(0);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 图例项动画 */
|
|
|
+.donut-legend-vertical .legend-item {
|
|
|
+ animation: slideInRight 0.6s ease-out;
|
|
|
+ animation-delay: calc(var(--index) * 0.2s);
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes slideInRight {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateX(20px);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateX(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes slideInUp {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(15px);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 图表卡片阴影效果 */
|
|
|
+.chart-card-donut::before,
|
|
|
+.chart-card-trend::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: linear-gradient(135deg, rgba(16, 185, 129, 0.05) 0%, transparent 100%);
|
|
|
+ border-radius: 16px;
|
|
|
+ pointer-events: none;
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-card-donut:hover::before,
|
|
|
+.chart-card-trend:hover::before {
|
|
|
+ opacity: 1;
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养统计卡片增强效果 */
|
|
|
+.maintenance-metric-card::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: linear-gradient(135deg, rgba(16, 185, 129, 0.03) 0%, transparent 100%);
|
|
|
+ border-radius: 16px;
|
|
|
+ pointer-events: none;
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-card:hover::before {
|
|
|
+ opacity: 1;
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养统计卡片装饰边框 - 已移除 */
|
|
|
+
|
|
|
+/* 保养统计卡片图标动画 */
|
|
|
+.maintenance-metric-icon-wrapper {
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-icon-wrapper::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: -2px;
|
|
|
+ left: -2px;
|
|
|
+ right: -2px;
|
|
|
+ bottom: -2px;
|
|
|
+ background: linear-gradient(45deg, transparent, rgba(16, 185, 129, 0.1), transparent);
|
|
|
+ border-radius: 18px;
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-card:hover .maintenance-metric-icon-wrapper::before {
|
|
|
+ opacity: 1;
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养统计卡片数值动画 */
|
|
|
+.maintenance-metric-value {
|
|
|
+ position: relative;
|
|
|
+ display: inline-block;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-metric-card:hover .maintenance-metric-value {
|
|
|
+ animation: pulse 0.6s ease-in-out;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes pulse {
|
|
|
+ 0%, 100% {
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ transform: scale(1.05);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养统计卡片进度条动画增强 */
|
|
|
+.maintenance-progress-fill {
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-progress-fill::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
|
|
|
+ animation: progressShine 2s infinite;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes progressShine {
|
|
|
+ 0% {
|
|
|
+ transform: translateX(-100%);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ transform: translateX(100%);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 图表标题装饰 */
|
|
|
+.chart-title::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ left: -8px;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ width: 4px;
|
|
|
+ height: 20px;
|
|
|
+ background: linear-gradient(180deg, #10b981, #34d399);
|
|
|
+ border-radius: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 控制按钮样式优化 */
|
|
|
+.chart-controls .el-radio-group .el-radio-button__inner {
|
|
|
+ border-color: #e5e7eb;
|
|
|
+ color: #6b7280;
|
|
|
+ background: white;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-controls .el-radio-group .el-radio-button__inner:hover {
|
|
|
+ border-color: #10b981;
|
|
|
+ color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-controls .el-radio-group .el-radio-button__orig-radio:checked + .el-radio-button__inner {
|
|
|
+ background: #10b981;
|
|
|
+ border-color: #10b981;
|
|
|
+ color: white;
|
|
|
+ box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
|
|
|
+}
|
|
|
+
|
|
|
+/* 轴标签样式 */
|
|
|
+.axis-label {
|
|
|
+ font-size: 12px;
|
|
|
+ fill: #6b7280;
|
|
|
+ font-weight: 500;
|
|
|
+ font-family: 'Helvetica Neue', Arial, sans-serif;
|
|
|
+}
|
|
|
+
|
|
|
+/* 图表网格线优化 */
|
|
|
+.chart-area svg pattern path {
|
|
|
+ stroke: #f0f4f1;
|
|
|
+ stroke-width: 1;
|
|
|
+ opacity: 0.8;
|
|
|
+}
|
|
|
+
|
|
|
+/* 趋势线渐变优化 */
|
|
|
+.chart-area svg polyline {
|
|
|
+ stroke-linecap: round;
|
|
|
+ stroke-linejoin: round;
|
|
|
+ filter: drop-shadow(0 2px 4px rgba(59, 180, 74, 0.2));
|
|
|
+}
|
|
|
+
|
|
|
+/* 趋势面积渐变优化 */
|
|
|
+.chart-area svg path[fill="url(#trendGradient)"] {
|
|
|
+ opacity: 0.8;
|
|
|
+ filter: drop-shadow(0 1px 2px rgba(59, 180, 74, 0.1));
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养图表样式 */
|
|
|
+.maintenance-chart-card {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ height: 540px;
|
|
|
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 16px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-chart-card:hover {
|
|
|
+ transform: translateY(-4px);
|
|
|
+ box-shadow: 0 12px 32px rgba(16, 185, 129, 0.15);
|
|
|
+ border-color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养环形图样式 - 水平布局 */
|
|
|
+.maintenance-donut-chart-horizontal {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 24px;
|
|
|
+ flex: 1;
|
|
|
+ position: relative;
|
|
|
+ box-sizing: border-box;
|
|
|
+ overflow: hidden;
|
|
|
+ min-height: 0;
|
|
|
+ gap: 32px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养环形图样式 - 垂直布局(保持兼容性) */
|
|
|
+.maintenance-donut-chart-vertical {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 20px 0;
|
|
|
+ min-height: 280px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养环形图样式 - 原水平布局(保持兼容性) */
|
|
|
+.maintenance-donut-chart {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 40px;
|
|
|
+ padding: 10px 0;
|
|
|
+ min-height: 320px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-donut-container {
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-donut-container-centered {
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-bottom: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 左侧饼图容器 */
|
|
|
+.maintenance-donut-container-side {
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 280px;
|
|
|
+ height: 280px;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+.maintenance-donut-center-label {
|
|
|
+ font-size: 16px;
|
|
|
+ fill: #6B7280;
|
|
|
+ font-weight: 500;
|
|
|
+ font-family: 'Helvetica Neue', Arial, sans-serif;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-donut-center-value {
|
|
|
+ font-size: 32px;
|
|
|
+ fill: #1a1a1a;
|
|
|
+ font-weight: 700;
|
|
|
+ font-family: 'Helvetica Neue', Arial, sans-serif;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/* 横向图例样式 */
|
|
|
+.maintenance-donut-legend-horizontal {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16px;
|
|
|
+ padding: 0 20px;
|
|
|
+ width: 100%;
|
|
|
+ max-width: 400px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-legend-row {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-legend-item-horizontal {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ flex: 1;
|
|
|
+ padding: 12px 16px;
|
|
|
+ background: rgba(248, 250, 252, 0.8);
|
|
|
+ border-radius: 8px;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ animation: slideInUp 0.6s ease-out;
|
|
|
+ animation-delay: calc(var(--index) * 0.15s);
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-legend-item-horizontal:hover {
|
|
|
+ background-color: #f0f9ff;
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.15);
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-legend-content-horizontal {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-legend-content-horizontal .maintenance-legend-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6b7280;
|
|
|
+ font-weight: 500;
|
|
|
+ margin-bottom: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-legend-content-horizontal .maintenance-legend-value {
|
|
|
+ font-size: 16px;
|
|
|
+ color: #1f2937;
|
|
|
+ font-weight: 700;
|
|
|
+}
|
|
|
+
|
|
|
+/* 右侧图例区域 */
|
|
|
+.maintenance-donut-legend-side {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 20px;
|
|
|
+ padding: 20px 0;
|
|
|
+ flex: 1;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: flex-start;
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-legend-row-spaced {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ gap: 24px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-legend-item-enhanced {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ flex: 0 1 auto;
|
|
|
+ min-width: 180px;
|
|
|
+ max-width: 220px;
|
|
|
+ padding: 16px 20px;
|
|
|
+ background: linear-gradient(135deg, rgba(248, 250, 252, 0.9) 0%, rgba(241, 245, 249, 0.8) 100%);
|
|
|
+ border-radius: 12px;
|
|
|
+ border: 1px solid rgba(226, 232, 240, 0.8);
|
|
|
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+ cursor: pointer;
|
|
|
+ animation: slideInUp 0.6s ease-out;
|
|
|
+ animation-delay: calc(var(--index) * 0.15s);
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-legend-item-enhanced::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.5) 0%, transparent 100%);
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-legend-item-enhanced:hover {
|
|
|
+ background: linear-gradient(135deg, rgba(240, 253, 244, 0.9) 0%, rgba(220, 252, 231, 0.8) 100%);
|
|
|
+ border-color: #10b981;
|
|
|
+ transform: translateY(-3px);
|
|
|
+ box-shadow: 0 8px 25px rgba(16, 185, 129, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-legend-item-enhanced:hover::before {
|
|
|
+ opacity: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-legend-item-enhanced.active {
|
|
|
+ background: linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, rgba(52, 211, 153, 0.05) 100%);
|
|
|
+ border-color: #10b981;
|
|
|
+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-legend-content-enhanced {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-legend-content-enhanced .maintenance-legend-label {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #6b7280;
|
|
|
+ font-weight: 500;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ transition: color 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-legend-content-enhanced .maintenance-legend-value {
|
|
|
+ font-size: 18px;
|
|
|
+ color: #1f2937;
|
|
|
+ font-weight: 700;
|
|
|
+ transition: color 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-legend-item-enhanced:hover .maintenance-legend-content-enhanced .maintenance-legend-label {
|
|
|
+ color: #059669;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-legend-item-enhanced:hover .maintenance-legend-content-enhanced .maintenance-legend-value {
|
|
|
+ color: #047857;
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养趋势图样式 */
|
|
|
+.maintenance-trend-chart {
|
|
|
+ padding: 10px 0;
|
|
|
+ min-height: 320px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-chart-area {
|
|
|
+ margin-bottom: 20px;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-chart-area svg {
|
|
|
+ width: 100%;
|
|
|
+ height: 260px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-chart-unit-label {
|
|
|
+ position: absolute;
|
|
|
+ top: 12px;
|
|
|
+ right: 20px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6B7280;
|
|
|
+ font-weight: 600;
|
|
|
+ z-index: 10;
|
|
|
+ background: rgba(255, 255, 255, 0.9);
|
|
|
+ padding: 4px 8px;
|
|
|
+ border-radius: 6px;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养数据点交互样式 */
|
|
|
+.maintenance-data-point {
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ animation: fadeInScale 0.8s ease-out;
|
|
|
+ animation-delay: calc(var(--index) * 0.1s);
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-data-point:hover {
|
|
|
+ r: 8;
|
|
|
+ fill: #E65100;
|
|
|
+ stroke: white;
|
|
|
+ stroke-width: 3;
|
|
|
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养轴标签样式 */
|
|
|
+.maintenance-axis-label {
|
|
|
+ font-size: 12px;
|
|
|
+ fill: #6b7280;
|
|
|
+ font-weight: 500;
|
|
|
+ font-family: 'Helvetica Neue', Arial, sans-serif;
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养趋势汇总样式 */
|
|
|
+.maintenance-trend-summary {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-around;
|
|
|
+ align-items: center;
|
|
|
+ padding: 20px 0;
|
|
|
+ border-top: 1px solid #e5e7eb;
|
|
|
+ margin-top: 0;
|
|
|
+ background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
|
|
|
+ border-radius: 0 0 12px 12px;
|
|
|
+ min-height: 80px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-summary-item {
|
|
|
+ text-align: center;
|
|
|
+ padding: 16px 20px;
|
|
|
+ border-radius: 8px;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-summary-item:hover {
|
|
|
+ background-color: white;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
+ transform: translateY(-2px);
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-summary-label {
|
|
|
+ display: block;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ font-weight: 500;
|
|
|
+ text-transform: uppercase;
|
|
|
+ letter-spacing: 0.5px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-summary-value {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #374151;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-summary-value.primary {
|
|
|
+ color: #FF9800;
|
|
|
+ font-size: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-summary-value.positive {
|
|
|
+ color: #10b981;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-summary-value.warning {
|
|
|
+ color: #f59e0b;
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养图表汇总信息样式 */
|
|
|
+.maintenance-chart-summary {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-around;
|
|
|
+ align-items: center;
|
|
|
+ padding: 20px 0;
|
|
|
+ border-top: 1px solid #e5e7eb;
|
|
|
+ margin-top: 0;
|
|
|
+ background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
|
|
|
+ border-radius: 0 0 12px 12px;
|
|
|
+ min-height: 80px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-chart-summary-item {
|
|
|
+ text-align: center;
|
|
|
+ padding: 16px 20px;
|
|
|
+ border-radius: 8px;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-chart-summary-item:hover {
|
|
|
+ background-color: white;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
+ transform: translateY(-2px);
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-chart-summary-label {
|
|
|
+ display: block;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ font-weight: 500;
|
|
|
+ text-transform: uppercase;
|
|
|
+ letter-spacing: 0.5px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-chart-summary-value {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #374151;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-chart-summary-value.primary {
|
|
|
+ color: #4CAF50;
|
|
|
+ font-size: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-chart-summary-value.warning {
|
|
|
+ color: #FF5722;
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养图表提示框样式 */
|
|
|
+.maintenance-chart-tooltip {
|
|
|
+ position: fixed !important;
|
|
|
+ background: rgba(0, 0, 0, 0.9) !important;
|
|
|
+ color: white !important;
|
|
|
+ padding: 10px 14px !important;
|
|
|
+ border-radius: 8px !important;
|
|
|
+ font-size: 13px !important;
|
|
|
+ pointer-events: none !important;
|
|
|
+ z-index: 99999 !important;
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.3s ease !important;
|
|
|
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2) !important;
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
|
|
+ display: none;
|
|
|
+ min-width: 80px;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-chart-tooltip.show {
|
|
|
+ opacity: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.maintenance-chart-tooltip::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 100%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border: 5px solid transparent;
|
|
|
+ border-top-color: rgba(0, 0, 0, 0.9);
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养图表动画效果 */
|
|
|
+.maintenance-chart-card {
|
|
|
+ animation: fadeInUp 0.6s ease-out;
|
|
|
+}
|
|
|
+
|
|
|
+/* 保养环形图动画 */
|
|
|
+.maintenance-donut-container svg circle {
|
|
|
+ animation: drawCircle 1.5s ease-out;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/* 保养趋势线动画 */
|
|
|
+.maintenance-chart-area svg polyline {
|
|
|
+ animation: drawLine 2s ease-out;
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式布局优化 */
|
|
|
+@media (max-width: 1200px) {
|
|
|
+ .maintenance-donut-chart {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 24px;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-donut-chart-vertical {
|
|
|
+ padding: 16px 0;
|
|
|
+ min-height: 260px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-donut-chart-horizontal {
|
|
|
+ flex-direction: column;
|
|
|
+ padding: 16px;
|
|
|
+ gap: 16px;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ .work-donut-chart-horizontal {
|
|
|
+ flex-direction: column;
|
|
|
+ padding: 16px;
|
|
|
+ gap: 16px;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ .maintenance-chart-area {
|
|
|
+ width: 100%;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-chart-card {
|
|
|
+ height: 500px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ .work-chart-card {
|
|
|
+ height: 500px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ .work-donut-container-side {
|
|
|
+ width: 240px;
|
|
|
+ height: 240px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .work-donut-container-side svg {
|
|
|
+ width: 240px;
|
|
|
+ height: 240px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .work-donut-legend-side {
|
|
|
+ padding: 16px 0;
|
|
|
+ gap: 16px;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .maintenance-chart-card {
|
|
|
+ height: 420px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-donut-chart {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-donut-chart-vertical {
|
|
|
+ padding: 12px 0;
|
|
|
+ min-height: 240px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-donut-chart-horizontal {
|
|
|
+ flex-direction: column;
|
|
|
+ padding: 12px;
|
|
|
+ gap: 12px;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ .work-donut-chart-horizontal {
|
|
|
+ flex-direction: column;
|
|
|
+ padding: 12px;
|
|
|
+ gap: 12px;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ .maintenance-donut-container-side {
|
|
|
+ width: 200px;
|
|
|
+ height: 200px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-donut-container-side svg {
|
|
|
+ width: 200px;
|
|
|
+ height: 200px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-donut-legend-side {
|
|
|
+ padding: 12px 0;
|
|
|
+ gap: 12px;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .work-chart-card {
|
|
|
+ height: 420px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ .work-donut-container-side {
|
|
|
+ width: 200px;
|
|
|
+ height: 200px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .work-donut-container-side svg {
|
|
|
+ width: 200px;
|
|
|
+ height: 200px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .work-donut-legend-side {
|
|
|
+ padding: 12px 0;
|
|
|
+ gap: 12px;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-legend-row-spaced {
|
|
|
+ gap: 16px;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-legend-item-enhanced {
|
|
|
+ padding: 12px 16px;
|
|
|
+ gap: 10px;
|
|
|
+ min-width: 140px;
|
|
|
+ max-width: 180px;
|
|
|
+ flex: 0 1 auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-legend-content-enhanced .maintenance-legend-label {
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-legend-content-enhanced .maintenance-legend-value {
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-donut-legend-horizontal {
|
|
|
+ padding: 0 16px;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-legend-row {
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-legend-item-horizontal {
|
|
|
+ padding: 8px 12px;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-legend-content-horizontal .maintenance-legend-label {
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-legend-content-horizontal .maintenance-legend-value {
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ .maintenance-donut-container {
|
|
|
+ width: 200px;
|
|
|
+ height: 200px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-donut-container svg {
|
|
|
+ width: 200px;
|
|
|
+ height: 200px;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ .maintenance-chart-unit-label {
|
|
|
+ top: 8px;
|
|
|
+ right: 16px;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-trend-summary {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 16px 0 0 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-summary-item {
|
|
|
+ padding: 12px 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-summary-value {
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .maintenance-summary-value.primary {
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|