|
|
@@ -1,6 +1,211 @@
|
|
|
<template>
|
|
|
-
|
|
|
-
|
|
|
+ <div class="dashboard-container">
|
|
|
+ <!-- 页面头部 -->
|
|
|
+ <header class="dashboard-header">
|
|
|
+ <div class="title-section">
|
|
|
+ <h1 class="dashboard-title">智慧农业数据看板</h1>
|
|
|
+ <p class="dashboard-subtitle">{{ getCurrentFarmDescription() }}</p>
|
|
|
+ </div>
|
|
|
+ <div class="action-buttons">
|
|
|
+ <div class="location-selector-group">
|
|
|
+ <el-cascader
|
|
|
+ v-model="selectedLocation"
|
|
|
+ :options="locationOptions"
|
|
|
+ :props="cascaderProps"
|
|
|
+ size="medium"
|
|
|
+ style="width: 240px"
|
|
|
+ placeholder="请选择农场或地块"
|
|
|
+ clearable>
|
|
|
+ </el-cascader>
|
|
|
+ <el-button
|
|
|
+ icon="el-icon-check"
|
|
|
+ type="primary"
|
|
|
+ size="medium"
|
|
|
+ @click="confirmLocationChange"
|
|
|
+ :disabled="!hasLocationChanged">
|
|
|
+ 确认
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ icon="el-icon-close"
|
|
|
+ size="medium"
|
|
|
+ @click="clearLocation">
|
|
|
+ 清空
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <el-button icon="el-icon-refresh" type="success" size="medium" @click="refreshData">
|
|
|
+ 刷新数据
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </header>
|
|
|
+
|
|
|
+ <!-- 数据概览卡片 -->
|
|
|
+ <div class="overview-section">
|
|
|
+ <div class="overview-grid">
|
|
|
+ <div class="overview-card" v-for="(item, index) in overviewData" :key="index">
|
|
|
+ <div class="card-icon" :style="{ backgroundColor: item.color }">
|
|
|
+ <i :class="item.icon"></i>
|
|
|
+ </div>
|
|
|
+ <div class="card-content">
|
|
|
+ <div class="card-value">{{ item.value }}</div>
|
|
|
+ <div class="card-label">{{ item.label }}</div>
|
|
|
+ <div class="card-change" :class="item.trend">
|
|
|
+ <i :class="item.trend === 'up' ? 'el-icon-top' : 'el-icon-bottom'"></i>
|
|
|
+ {{ item.change }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 主要内容区域 -->
|
|
|
+ <div class="main-content">
|
|
|
+ <!-- 农场分布地图 -->
|
|
|
+ <div class="content-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <h2 class="section-title">农场分布图</h2>
|
|
|
+ </div>
|
|
|
+ <div class="map-container">
|
|
|
+ <div id="farmMap"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 环境监测区域 -->
|
|
|
+ <div class="content-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <h2 class="section-title">环境监测</h2>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="environment-grid">
|
|
|
+ <div class="env-card">
|
|
|
+ <div class="env-header">
|
|
|
+ <span class="env-title">温度监测</span>
|
|
|
+ <span class="env-value">26.5°C</span>
|
|
|
+ </div>
|
|
|
+ <div class="env-chart" id="temperatureChart"></div>
|
|
|
+ <div class="env-status normal">正常</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="env-card">
|
|
|
+ <div class="env-header">
|
|
|
+ <span class="env-title">湿度监测</span>
|
|
|
+ <span class="env-value">68%</span>
|
|
|
+ </div>
|
|
|
+ <div class="env-chart" id="humidityChart"></div>
|
|
|
+ <div class="env-status normal">正常</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="env-card">
|
|
|
+ <div class="env-header">
|
|
|
+ <span class="env-title">土壤监测</span>
|
|
|
+ <span class="env-value">良好</span>
|
|
|
+ </div>
|
|
|
+ <div class="env-chart" id="soilChart"></div>
|
|
|
+ <div class="env-status good">良好</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="env-card">
|
|
|
+ <div class="env-header">
|
|
|
+ <span class="env-title">光照监测</span>
|
|
|
+ <span class="env-value">85%</span>
|
|
|
+ </div>
|
|
|
+ <div class="env-chart" id="lightChart"></div>
|
|
|
+ <div class="env-status normal">正常</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 葡萄与设备状态 -->
|
|
|
+ <div class="content-row">
|
|
|
+ <div class="content-card half-width">
|
|
|
+ <div class="card-header">
|
|
|
+ <h3 class="card-title">葡萄生长状况</h3>
|
|
|
+ <div class="time-range">
|
|
|
+ <el-radio-group v-model="cropTimeRange" size="mini">
|
|
|
+ <el-radio-button label="week">本周</el-radio-button>
|
|
|
+ <el-radio-button label="month">本月</el-radio-button>
|
|
|
+ <el-radio-button label="season">本季</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="chart-container">
|
|
|
+ <div id="cropStatusChart"></div>
|
|
|
+ </div>
|
|
|
+ <div class="chart-legend">
|
|
|
+ <div class="legend-item" v-for="item in getCurrentCropLegend()" :key="item.name">
|
|
|
+ <span class="legend-color" :style="{ background: item.color }"></span>
|
|
|
+ {{ item.name }} ({{ item.value }}亩)
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="content-card half-width">
|
|
|
+ <div class="card-header">
|
|
|
+ <h3 class="card-title">设备运行状态</h3>
|
|
|
+ <div class="device-summary">
|
|
|
+ <span class="device-count">总设备: {{ deviceStats.total }}</span>
|
|
|
+ <span class="device-online">在线: {{ deviceStats.online }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="chart-container">
|
|
|
+ <div id="deviceStatusChart"></div>
|
|
|
+ </div>
|
|
|
+ <div class="device-stats">
|
|
|
+ <div class="stats-item">
|
|
|
+ <span class="stats-label">在线率</span>
|
|
|
+ <span class="stats-value">{{ Math.round(deviceStats.online / deviceStats.total * 100) }}%</span>
|
|
|
+ </div>
|
|
|
+ <div class="stats-item">
|
|
|
+ <span class="stats-label">离线设备</span>
|
|
|
+ <span class="stats-value offline">{{ deviceStats.offline }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 生产数据与告警信息 -->
|
|
|
+ <div class="content-row">
|
|
|
+ <div class="content-card half-width">
|
|
|
+ <div class="card-header">
|
|
|
+ <h3 class="card-title">生产数据统计</h3>
|
|
|
+ <div class="time-range">
|
|
|
+ <el-radio-group v-model="productionTimeRange" size="mini">
|
|
|
+ <el-radio-button label="month">本月</el-radio-button>
|
|
|
+ <el-radio-button label="quarter">本季</el-radio-button>
|
|
|
+ <el-radio-button label="year">本年</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="chart-container">
|
|
|
+ <div id="productionChart"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="content-card half-width">
|
|
|
+ <div class="card-header">
|
|
|
+ <h3 class="card-title">系统告警</h3>
|
|
|
+ <el-badge :value="alertCount" class="alert-badge" type="warning">
|
|
|
+ <span class="alert-text">待处理告警</span>
|
|
|
+ </el-badge>
|
|
|
+ </div>
|
|
|
+ <div class="alert-list">
|
|
|
+ <div class="alert-item" v-for="alert in alertList" :key="alert.id" :class="alert.level">
|
|
|
+ <div class="alert-icon">
|
|
|
+ <i :class="getAlertIcon(alert.level)"></i>
|
|
|
+ </div>
|
|
|
+ <div class="alert-content">
|
|
|
+ <div class="alert-title">{{ alert.title }}</div>
|
|
|
+ <div class="alert-time">{{ alert.time }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="alert-status" :class="alert.status">
|
|
|
+ {{ getAlertStatusText(alert.status) }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
@@ -9,77 +214,2728 @@ export default {
|
|
|
data() {
|
|
|
return {
|
|
|
// 版本号
|
|
|
- version: "3.6.5"
|
|
|
+ version: "3.6.5",
|
|
|
+ selectedLocation: [], // 级联选择器的值
|
|
|
+ confirmedLocation: [], // 已确认的筛选条件
|
|
|
+ cropTimeRange: 'month',
|
|
|
+ productionTimeRange: 'month',
|
|
|
+
|
|
|
+ alertCount: 5,
|
|
|
+
|
|
|
+ // 级联选择器配置
|
|
|
+ cascaderProps: {
|
|
|
+ checkStrictly: true, // 允许选择任意一级
|
|
|
+ label: 'label',
|
|
|
+ value: 'value',
|
|
|
+ children: 'children'
|
|
|
+ },
|
|
|
+
|
|
|
+ // 级联选择器选项
|
|
|
+ locationOptions: [
|
|
|
+ {
|
|
|
+ label: '全部农场',
|
|
|
+ value: 'all',
|
|
|
+ children: []
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '东区智慧农场',
|
|
|
+ value: 'east',
|
|
|
+ children: [
|
|
|
+ { label: 'A1地块', value: 'east_a1' },
|
|
|
+ { label: 'A2地块', value: 'east_a2' },
|
|
|
+ { label: 'A3地块', value: 'east_a3' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '西区智慧农场',
|
|
|
+ value: 'west',
|
|
|
+ children: [
|
|
|
+ { label: 'B1地块', value: 'west_b1' },
|
|
|
+ { label: 'B2地块', value: 'west_b2' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '南区智慧农场',
|
|
|
+ value: 'south',
|
|
|
+ children: [
|
|
|
+ { label: 'C1地块', value: 'south_c1' },
|
|
|
+ { label: 'C2地块', value: 'south_c2' },
|
|
|
+ { label: 'C3地块', value: 'south_c3' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '北区智慧农场',
|
|
|
+ value: 'north',
|
|
|
+ children: [
|
|
|
+ { label: 'D1地块', value: 'north_d1' },
|
|
|
+ { label: 'D2地块', value: 'north_d2' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '中心监测农场',
|
|
|
+ value: 'center',
|
|
|
+ children: [
|
|
|
+ { label: '实验地块1', value: 'center_test1' },
|
|
|
+ { label: '实验地块2', value: 'center_test2' },
|
|
|
+ { label: '示范地块', value: 'center_demo' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ],
|
|
|
+
|
|
|
+ // 概览数据
|
|
|
+ overviewData: [
|
|
|
+ {
|
|
|
+ label: '农场总面积',
|
|
|
+ value: '2,560',
|
|
|
+ unit: '亩',
|
|
|
+ change: '+5.2%',
|
|
|
+ trend: 'up',
|
|
|
+ color: '#10b981',
|
|
|
+ icon: 'el-icon-location'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '设备在线率',
|
|
|
+ value: '95.8%',
|
|
|
+ change: '+2.1%',
|
|
|
+ trend: 'up',
|
|
|
+ color: '#3b82f6',
|
|
|
+ icon: 'el-icon-cpu'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '葡萄健康率',
|
|
|
+ value: '87.3%',
|
|
|
+ change: '+1.8%',
|
|
|
+ trend: 'up',
|
|
|
+ color: '#8b5cf6',
|
|
|
+ icon: 'el-icon-sunny'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '月产量预期',
|
|
|
+ value: '1,850',
|
|
|
+ unit: '吨',
|
|
|
+ change: '+8.5%',
|
|
|
+ trend: 'up',
|
|
|
+ color: '#f59e0b',
|
|
|
+ icon: 'el-icon-box'
|
|
|
+ }
|
|
|
+ ],
|
|
|
+
|
|
|
+ // 设备统计
|
|
|
+ deviceStats: {
|
|
|
+ total: 42,
|
|
|
+ online: 38,
|
|
|
+ offline: 4
|
|
|
+ },
|
|
|
+
|
|
|
+ // 告警列表
|
|
|
+ alertList: [
|
|
|
+ {
|
|
|
+ id: 1,
|
|
|
+ title: '东区土壤湿度过低',
|
|
|
+ time: '2分钟前',
|
|
|
+ level: 'warning',
|
|
|
+ status: 'pending'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 2,
|
|
|
+ title: '西区温度传感器离线',
|
|
|
+ time: '15分钟前',
|
|
|
+ level: 'error',
|
|
|
+ status: 'pending'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 3,
|
|
|
+ title: '南区灌溉系统异常',
|
|
|
+ time: '1小时前',
|
|
|
+ level: 'warning',
|
|
|
+ status: 'processing'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 4,
|
|
|
+ title: '北区葡萄病虫害预警',
|
|
|
+ time: '2小时前',
|
|
|
+ level: 'info',
|
|
|
+ status: 'resolved'
|
|
|
+ }
|
|
|
+ ],
|
|
|
+
|
|
|
+ // 高德地图相关
|
|
|
+ farmMap: null,
|
|
|
+ farmMarkers: [],
|
|
|
+ plotPolygons: []
|
|
|
}
|
|
|
},
|
|
|
+
|
|
|
+ mounted() {
|
|
|
+ // 初始化确认的位置为默认状态(全部农场)
|
|
|
+ this.confirmedLocation = [];
|
|
|
+
|
|
|
+ this.$nextTick(() => {
|
|
|
+ // 等待页面完全渲染后再初始化图表
|
|
|
+ this.waitForElementsAndInit();
|
|
|
+ // 添加窗口调整监听器
|
|
|
+ window.addEventListener('resize', this.handleResize);
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ computed: {
|
|
|
+ hasLocationChanged() {
|
|
|
+ return JSON.stringify(this.selectedLocation) !== JSON.stringify(this.confirmedLocation);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ watch: {
|
|
|
+ confirmedLocation: {
|
|
|
+ handler(newLocation) {
|
|
|
+ this.handleLocationDataUpdate();
|
|
|
+ },
|
|
|
+ deep: true
|
|
|
+ },
|
|
|
+ cropTimeRange: {
|
|
|
+ handler(newRange, oldRange) {
|
|
|
+ this.updateCropChart();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ productionTimeRange: {
|
|
|
+ handler(newRange) {
|
|
|
+ this.updateProductionChart();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ beforeDestroy() {
|
|
|
+ window.removeEventListener('resize', this.handleResize);
|
|
|
+
|
|
|
+ // 清理高德地图资源
|
|
|
+ if (this.farmMap) {
|
|
|
+ if (this.farmMarkers) {
|
|
|
+ this.farmMap.remove(this.farmMarkers);
|
|
|
+ }
|
|
|
+ if (this.plotPolygons) {
|
|
|
+ this.farmMap.remove(this.plotPolygons);
|
|
|
+ }
|
|
|
+ this.farmMap.destroy();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
methods: {
|
|
|
- goTarget(href) {
|
|
|
- window.open(href, "_blank")
|
|
|
+ refreshData() {
|
|
|
+ this.$message.success('数据刷新成功');
|
|
|
+ // 这里可以添加实际的数据刷新逻辑
|
|
|
+ console.log('手动刷新数据和图表...');
|
|
|
+ this.directInitCharts();
|
|
|
+ },
|
|
|
+
|
|
|
+ getCurrentFarmDescription() {
|
|
|
+ const currentSelection = this.getCurrentSelection();
|
|
|
+
|
|
|
+ if (currentSelection.type === 'farm') {
|
|
|
+ const farmDescriptions = {
|
|
|
+ all: '全面展示各农场运营状况,实时监控智慧农业数据',
|
|
|
+ east: '东区智慧农场 - 专注高效种植,数据驱动精准农业',
|
|
|
+ west: '西区智慧农场 - 生态循环种植,科技助力绿色发展',
|
|
|
+ south: '南区智慧农场 - 现代化设施农业,智能管控优质产出',
|
|
|
+ north: '北区智慧农场 - 规模化种植基地,机械化智能化并重',
|
|
|
+ center: '中心监测农场 - 技术研发示范,引领农业创新发展'
|
|
|
+ };
|
|
|
+ return farmDescriptions[currentSelection.id] || farmDescriptions.all;
|
|
|
+ } else if (currentSelection.type === 'plot') {
|
|
|
+ return `${currentSelection.farmName} - ${currentSelection.plotName} 地块详细监测数据`;
|
|
|
+ }
|
|
|
+
|
|
|
+ return '全面展示各农场运营状况,实时监控智慧农业数据';
|
|
|
+ },
|
|
|
+
|
|
|
+ getCurrentSelection() {
|
|
|
+ if (!this.confirmedLocation || this.confirmedLocation.length === 0) {
|
|
|
+ return { type: 'farm', id: 'all' };
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.confirmedLocation.length === 1) {
|
|
|
+ // 选择的是农场
|
|
|
+ return { type: 'farm', id: this.confirmedLocation[0] };
|
|
|
+ } else if (this.confirmedLocation.length === 2) {
|
|
|
+ // 选择的是地块
|
|
|
+ const farmId = this.confirmedLocation[0];
|
|
|
+ const plotId = this.confirmedLocation[1];
|
|
|
+ const farmNames = {
|
|
|
+ east: '东区智慧农场',
|
|
|
+ west: '西区智慧农场',
|
|
|
+ south: '南区智慧农场',
|
|
|
+ north: '北区智慧农场',
|
|
|
+ center: '中心监测农场'
|
|
|
+ };
|
|
|
+
|
|
|
+ // 从级联选择器中找到地块名称
|
|
|
+ const farm = this.locationOptions.find(f => f.value === farmId);
|
|
|
+ const plot = farm ? farm.children.find(p => p.value === plotId) : null;
|
|
|
+
|
|
|
+ return {
|
|
|
+ type: 'plot',
|
|
|
+ farmId: farmId,
|
|
|
+ plotId: plotId,
|
|
|
+ farmName: farmNames[farmId] || '未知农场',
|
|
|
+ plotName: plot ? plot.label : '未知地块'
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ return { type: 'farm', id: 'all' };
|
|
|
+ },
|
|
|
+
|
|
|
+ confirmLocationChange() {
|
|
|
+ // 确认选择的位置
|
|
|
+ if (!this.selectedLocation || this.selectedLocation.length === 0) {
|
|
|
+ this.confirmedLocation = [];
|
|
|
+ this.$message.success('已切换为全部农场视图');
|
|
|
+ } else {
|
|
|
+ // 先设置确认的位置
|
|
|
+ this.confirmedLocation = [...this.selectedLocation];
|
|
|
+
|
|
|
+ // 基于选择的位置获取信息
|
|
|
+ if (this.selectedLocation.length === 1) {
|
|
|
+ const farmNames = {
|
|
|
+ all: '全部农场',
|
|
|
+ east: '东区智慧农场',
|
|
|
+ west: '西区智慧农场',
|
|
|
+ south: '南区智慧农场',
|
|
|
+ north: '北区智慧农场',
|
|
|
+ center: '中心监测农场'
|
|
|
+ };
|
|
|
+ this.$message.success(`已切换为 ${farmNames[this.selectedLocation[0]] || '未知农场'} 视图`);
|
|
|
+ } else if (this.selectedLocation.length === 2) {
|
|
|
+ const farmId = this.selectedLocation[0];
|
|
|
+ const plotId = this.selectedLocation[1];
|
|
|
+ const farmNames = {
|
|
|
+ east: '东区智慧农场',
|
|
|
+ west: '西区智慧农场',
|
|
|
+ south: '南区智慧农场',
|
|
|
+ north: '北区智慧农场',
|
|
|
+ center: '中心监测农场'
|
|
|
+ };
|
|
|
+
|
|
|
+ // 从级联选择器中找到地块名称
|
|
|
+ const farm = this.locationOptions.find(f => f.value === farmId);
|
|
|
+ const plot = farm ? farm.children.find(p => p.value === plotId) : null;
|
|
|
+ const farmName = farmNames[farmId] || '未知农场';
|
|
|
+ const plotName = plot ? plot.label : '未知地块';
|
|
|
+
|
|
|
+ this.$message.success(`已切换为 ${farmName} - ${plotName} 视图`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ clearLocation() {
|
|
|
+ // 清空选择
|
|
|
+ this.selectedLocation = [];
|
|
|
+ this.confirmedLocation = [];
|
|
|
+ this.$message.info('筛选条件已清空');
|
|
|
+ },
|
|
|
+
|
|
|
+ handleLocationDataUpdate() {
|
|
|
+ const currentSelection = this.getCurrentSelection();
|
|
|
+
|
|
|
+ if (currentSelection.type === 'farm') {
|
|
|
+ // 选择农场时更新农场数据
|
|
|
+ this.updateFarmData(currentSelection.id);
|
|
|
+ } else if (currentSelection.type === 'plot') {
|
|
|
+ // 选择地块时更新农场数据和地块特定数据
|
|
|
+ this.updateFarmData(currentSelection.farmId);
|
|
|
+ this.updatePlotData(currentSelection.plotId);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重新初始化图表和地图
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.initCharts();
|
|
|
+ // 只更新地图内容,不重新创建地图
|
|
|
+ this.updateMapContent();
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ updatePlotData(plotId) {
|
|
|
+ // 根据选择的地块更新特定数据
|
|
|
+ console.log('更新地块数据:', plotId);
|
|
|
+ // 这里可以添加地块特定的数据更新逻辑
|
|
|
+ // 比如更新环境监测数据为该地块的具体数据
|
|
|
+ },
|
|
|
+
|
|
|
+ updateFarmData(farmId) {
|
|
|
+ // 根据选择的农场更新数据
|
|
|
+ this.updateOverviewData(farmId);
|
|
|
+ this.updateDeviceStats(farmId);
|
|
|
+ this.updateAlertList(farmId);
|
|
|
+ // 重新初始化图表
|
|
|
+ this.$nextTick(() => {
|
|
|
+ // 延迟重新初始化图表
|
|
|
+ setTimeout(() => {
|
|
|
+ this.initCharts();
|
|
|
+ }, 100);
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ updateOverviewData(farmId) {
|
|
|
+ // 模拟不同农场的数据
|
|
|
+ const farmData = {
|
|
|
+ all: {
|
|
|
+ area: '2,560',
|
|
|
+ deviceRate: '95.8%',
|
|
|
+ healthRate: '87.3%',
|
|
|
+ production: '1,850'
|
|
|
+ },
|
|
|
+ east: {
|
|
|
+ area: '680',
|
|
|
+ deviceRate: '97.2%',
|
|
|
+ healthRate: '89.1%',
|
|
|
+ production: '520'
|
|
|
+ },
|
|
|
+ west: {
|
|
|
+ area: '590',
|
|
|
+ deviceRate: '94.5%',
|
|
|
+ healthRate: '86.8%',
|
|
|
+ production: '450'
|
|
|
+ },
|
|
|
+ south: {
|
|
|
+ area: '720',
|
|
|
+ deviceRate: '96.8%',
|
|
|
+ healthRate: '88.9%',
|
|
|
+ production: '580'
|
|
|
+ },
|
|
|
+ north: {
|
|
|
+ area: '570',
|
|
|
+ deviceRate: '93.2%',
|
|
|
+ healthRate: '85.4%',
|
|
|
+ production: '300'
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const data = farmData[farmId] || farmData.all;
|
|
|
+ this.overviewData[0].value = data.area;
|
|
|
+ this.overviewData[1].value = data.deviceRate;
|
|
|
+ this.overviewData[2].value = data.healthRate;
|
|
|
+ this.overviewData[3].value = data.production;
|
|
|
+ },
|
|
|
+
|
|
|
+ updateDeviceStats(farmId) {
|
|
|
+ // 模拟不同农场的设备统计
|
|
|
+ const deviceData = {
|
|
|
+ all: { total: 42, online: 38, offline: 4 },
|
|
|
+ east: { total: 12, online: 11, offline: 1 },
|
|
|
+ west: { total: 10, online: 9, offline: 1 },
|
|
|
+ south: { total: 14, online: 13, offline: 1 },
|
|
|
+ north: { total: 6, online: 5, offline: 1 }
|
|
|
+ };
|
|
|
+
|
|
|
+ const data = deviceData[farmId] || deviceData.all;
|
|
|
+ this.deviceStats = data;
|
|
|
+ },
|
|
|
+
|
|
|
+ updateAlertList(farmId) {
|
|
|
+ // 模拟不同农场的告警信息
|
|
|
+ const alertData = {
|
|
|
+ all: [
|
|
|
+ { id: 1, title: '多个地块环境异常', time: '2分钟前', level: 'warning', status: 'pending' },
|
|
|
+ { id: 2, title: '设备离线告警汇总', time: '15分钟前', level: 'error', status: 'pending' },
|
|
|
+ { id: 3, title: '生产数据异常统计', time: '1小时前', level: 'warning', status: 'processing' },
|
|
|
+ { id: 4, title: '综合监测预警信息', time: '2小时前', level: 'info', status: 'resolved' }
|
|
|
+ ],
|
|
|
+ east: [
|
|
|
+ { id: 1, title: '东区A地块土壤湿度过低', time: '2分钟前', level: 'warning', status: 'pending' },
|
|
|
+ { id: 2, title: '东区温度传感器离线', time: '15分钟前', level: 'error', status: 'pending' }
|
|
|
+ ],
|
|
|
+ west: [
|
|
|
+ { id: 1, title: '西区B地块光照不足', time: '5分钟前', level: 'warning', status: 'pending' },
|
|
|
+ { id: 2, title: '西区灌溉系统压力异常', time: '30分钟前', level: 'warning', status: 'processing' }
|
|
|
+ ],
|
|
|
+ south: [
|
|
|
+ { id: 1, title: '南区C地块病虫害预警', time: '10分钟前', level: 'info', status: 'pending' },
|
|
|
+ { id: 2, title: '南区设备维护提醒', time: '1小时前', level: 'info', status: 'resolved' }
|
|
|
+ ],
|
|
|
+ north: [
|
|
|
+ { id: 1, title: '北区D地块温度过高', time: '8分钟前', level: 'warning', status: 'pending' }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+
|
|
|
+ const alerts = alertData[farmId] || alertData.all;
|
|
|
+ this.alertList = alerts;
|
|
|
+ this.alertCount = alerts.filter(alert => alert.status === 'pending').length;
|
|
|
+ },
|
|
|
+
|
|
|
+ handleResize() {
|
|
|
+ // 响应式调整图表大小
|
|
|
+ setTimeout(() => {
|
|
|
+ this.resizeAllCharts();
|
|
|
+ }, 100);
|
|
|
+ },
|
|
|
+
|
|
|
+ resizeAllCharts() {
|
|
|
+ const chartIds = ['temperatureChart', 'humidityChart', 'soilChart', 'lightChart',
|
|
|
+ 'cropStatusChart', 'deviceStatusChart', 'productionChart', 'farmMap'];
|
|
|
+ chartIds.forEach(id => {
|
|
|
+ const element = document.getElementById(id);
|
|
|
+ if (element && this.$echarts) {
|
|
|
+ const chart = this.$echarts.getInstanceByDom(element);
|
|
|
+ if (chart) {
|
|
|
+ chart.resize();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 调整高德地图大小
|
|
|
+ if (this.farmMap && typeof this.farmMap.resize === 'function') {
|
|
|
+ try {
|
|
|
+ this.farmMap.resize();
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('地图resize失败:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ getAlertIcon(level) {
|
|
|
+ const icons = {
|
|
|
+ error: 'el-icon-warning',
|
|
|
+ warning: 'el-icon-warning-outline',
|
|
|
+ info: 'el-icon-info'
|
|
|
+ };
|
|
|
+ return icons[level] || 'el-icon-info';
|
|
|
+ },
|
|
|
+
|
|
|
+ getAlertStatusText(status) {
|
|
|
+ const statusMap = {
|
|
|
+ pending: '待处理',
|
|
|
+ processing: '处理中',
|
|
|
+ resolved: '已解决'
|
|
|
+ };
|
|
|
+ return statusMap[status] || '未知';
|
|
|
+ },
|
|
|
+
|
|
|
+ waitForElementsAndInit(attempt = 0) {
|
|
|
+ const maxAttempts = 10;
|
|
|
+ const delay = 200;
|
|
|
+
|
|
|
+ console.log(`等待图表容器准备就绪... (尝试 ${attempt + 1}/${maxAttempts})`);
|
|
|
+
|
|
|
+ // 检查关键元素是否有正确的尺寸
|
|
|
+ const cropElement = document.getElementById('cropStatusChart');
|
|
|
+ const deviceElement = document.getElementById('deviceStatusChart');
|
|
|
+
|
|
|
+ if (cropElement && deviceElement) {
|
|
|
+ const cropRect = cropElement.getBoundingClientRect();
|
|
|
+ const deviceRect = deviceElement.getBoundingClientRect();
|
|
|
+
|
|
|
+ console.log('Crop element size:', cropRect.width, 'x', cropRect.height);
|
|
|
+ console.log('Device element size:', deviceRect.width, 'x', deviceRect.height);
|
|
|
+
|
|
|
+ if (cropRect.width > 0 && cropRect.height > 0 &&
|
|
|
+ deviceRect.width > 0 && deviceRect.height > 0) {
|
|
|
+ // 元素有正确的尺寸,可以初始化图表
|
|
|
+ console.log('图表容器准备就绪,开始初始化图表');
|
|
|
+ this.directInitCharts();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果元素还没准备好,继续等待
|
|
|
+ if (attempt < maxAttempts - 1) {
|
|
|
+ setTimeout(() => {
|
|
|
+ this.waitForElementsAndInit(attempt + 1);
|
|
|
+ }, delay);
|
|
|
+ } else {
|
|
|
+ console.warn('图表容器等待超时,强制初始化');
|
|
|
+ this.directInitCharts();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ directInitCharts() {
|
|
|
+ console.log('Direct chart initialization...');
|
|
|
+ console.log('ECharts available:', !!this.$echarts);
|
|
|
+ console.log('ECharts object:', this.$echarts);
|
|
|
+
|
|
|
+ // 直接初始化作物状况图表
|
|
|
+ try {
|
|
|
+ const cropElement = document.getElementById('cropStatusChart');
|
|
|
+ console.log('cropStatusChart element:', cropElement);
|
|
|
+ if (cropElement) {
|
|
|
+ // 检查元素尺寸
|
|
|
+ const rect = cropElement.getBoundingClientRect();
|
|
|
+ console.log('cropElement dimensions:', rect.width, 'x', rect.height);
|
|
|
+
|
|
|
+ // 如果尺寸为0,强制设置尺寸
|
|
|
+ if (rect.width === 0 || rect.height === 0) {
|
|
|
+ cropElement.style.width = '100%';
|
|
|
+ cropElement.style.height = '300px';
|
|
|
+ cropElement.style.display = 'block';
|
|
|
+ }
|
|
|
+
|
|
|
+ const cropChart = this.$echarts.init(cropElement);
|
|
|
+ console.log('cropChart instance:', cropChart);
|
|
|
+ cropChart.setOption({
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item',
|
|
|
+ formatter: '{b}: {c}亩 ({d}%)'
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ name: '作物分布',
|
|
|
+ type: 'pie',
|
|
|
+ radius: ['45%', '75%'],
|
|
|
+ center: ['50%', '50%'],
|
|
|
+ data: [
|
|
|
+ { value: 1200, name: '水稻', itemStyle: { color: '#10b981' } },
|
|
|
+ { value: 580, name: '小麦', itemStyle: { color: '#3b82f6' } },
|
|
|
+ { value: 520, name: '玉米', itemStyle: { color: '#f59e0b' } },
|
|
|
+ { value: 260, name: '蔬菜', itemStyle: { color: '#8b5cf6' } }
|
|
|
+ ],
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'inside',
|
|
|
+ formatter: '{d}%',
|
|
|
+ fontSize: 12,
|
|
|
+ fontWeight: 'bold',
|
|
|
+ color: 'white'
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ itemStyle: {
|
|
|
+ shadowBlur: 10,
|
|
|
+ shadowOffsetX: 0,
|
|
|
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ });
|
|
|
+ console.log('作物图表初始化成功');
|
|
|
+ } else {
|
|
|
+ console.error('cropStatusChart element not found!');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 直接初始化设备状况图表
|
|
|
+ const deviceElement = document.getElementById('deviceStatusChart');
|
|
|
+ console.log('deviceStatusChart element:', deviceElement);
|
|
|
+ if (deviceElement) {
|
|
|
+ // 检查元素尺寸
|
|
|
+ const rect = deviceElement.getBoundingClientRect();
|
|
|
+ console.log('deviceElement dimensions:', rect.width, 'x', rect.height);
|
|
|
+
|
|
|
+ // 如果尺寸为0,强制设置尺寸
|
|
|
+ if (rect.width === 0 || rect.height === 0) {
|
|
|
+ deviceElement.style.width = '100%';
|
|
|
+ deviceElement.style.height = '300px';
|
|
|
+ deviceElement.style.display = 'block';
|
|
|
+ }
|
|
|
+
|
|
|
+ const deviceChart = this.$echarts.init(deviceElement);
|
|
|
+ console.log('deviceChart instance:', deviceChart);
|
|
|
+ deviceChart.setOption({
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item',
|
|
|
+ formatter: '{b}: {c}台 ({d}%)'
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ name: '设备状态',
|
|
|
+ type: 'pie',
|
|
|
+ radius: '70%',
|
|
|
+ center: ['50%', '50%'],
|
|
|
+ data: [
|
|
|
+ { value: this.deviceStats.online, name: '在线', itemStyle: { color: '#10b981' } },
|
|
|
+ { value: this.deviceStats.offline, name: '离线', itemStyle: { color: '#ef4444' } }
|
|
|
+ ],
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'inside',
|
|
|
+ formatter: '{d}%',
|
|
|
+ fontSize: 12,
|
|
|
+ fontWeight: 'bold',
|
|
|
+ color: 'white'
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ itemStyle: {
|
|
|
+ shadowBlur: 10,
|
|
|
+ shadowOffsetX: 0,
|
|
|
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ });
|
|
|
+ console.log('设备图表初始化成功');
|
|
|
+ } else {
|
|
|
+ console.error('deviceStatusChart element not found!');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('图表初始化错误:', error);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化其他图表
|
|
|
+ this.initEnvironmentCharts();
|
|
|
+ this.initProductionChart();
|
|
|
+ this.initFarmMap();
|
|
|
+ },
|
|
|
+
|
|
|
+ initChartsWithRetry(retryCount = 0) {
|
|
|
+ const maxRetries = 3;
|
|
|
+ const delay = 200 * (retryCount + 1); // 200ms, 400ms, 600ms
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ console.log(`Initializing charts... (attempt ${retryCount + 1})`);
|
|
|
+
|
|
|
+ // 检查关键图表容器是否存在
|
|
|
+ const cropChart = document.getElementById('cropStatusChart');
|
|
|
+ const deviceChart = document.getElementById('deviceStatusChart');
|
|
|
+
|
|
|
+ if (!cropChart || !deviceChart) {
|
|
|
+ console.warn(`Key chart elements not ready, retrying... (${retryCount + 1}/${maxRetries})`);
|
|
|
+ if (retryCount < maxRetries - 1) {
|
|
|
+ this.initChartsWithRetry(retryCount + 1);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.initCharts();
|
|
|
+ }, delay);
|
|
|
+ },
|
|
|
+
|
|
|
+ initCharts() {
|
|
|
+ console.log('Initializing charts...');
|
|
|
+
|
|
|
+ // 检查所有图表容器是否存在
|
|
|
+ const chartElements = [
|
|
|
+ 'temperatureChart', 'humidityChart', 'soilChart', 'lightChart',
|
|
|
+ 'cropStatusChart', 'deviceStatusChart', 'productionChart', 'farmMap'
|
|
|
+ ];
|
|
|
+
|
|
|
+ chartElements.forEach(id => {
|
|
|
+ const element = document.getElementById(id);
|
|
|
+ if (!element) {
|
|
|
+ console.warn(`Chart element ${id} not found`);
|
|
|
+ } else {
|
|
|
+ console.log(`Chart element ${id} found`);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ this.initEnvironmentCharts();
|
|
|
+ this.initCropStatusChart();
|
|
|
+ this.initDeviceStatusChart();
|
|
|
+ this.initProductionChart();
|
|
|
+ this.initFarmMap();
|
|
|
+
|
|
|
+ console.log('Charts initialization completed');
|
|
|
+ },
|
|
|
+
|
|
|
+ initEnvironmentCharts() {
|
|
|
+ // 温度图表
|
|
|
+ const tempElement = document.getElementById('temperatureChart');
|
|
|
+ if (!tempElement) {
|
|
|
+ console.error('temperatureChart element not found');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const tempChart = this.$echarts.init(tempElement);
|
|
|
+ tempChart.setOption({
|
|
|
+ grid: { top: 10, right: 10, bottom: 10, left: 10 },
|
|
|
+ xAxis: { show: false, type: 'category', data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] },
|
|
|
+ yAxis: { show: false, type: 'value' },
|
|
|
+ series: [{
|
|
|
+ type: 'line',
|
|
|
+ data: [22, 24, 26, 25, 27, 26, 25],
|
|
|
+ smooth: true,
|
|
|
+ lineStyle: { color: '#10b981', width: 2 },
|
|
|
+ areaStyle: {
|
|
|
+ color: {
|
|
|
+ type: 'linear',
|
|
|
+ x: 0, y: 0, x2: 0, y2: 1,
|
|
|
+ colorStops: [
|
|
|
+ { offset: 0, color: 'rgba(16, 185, 129, 0.3)' },
|
|
|
+ { offset: 1, color: 'rgba(16, 185, 129, 0.1)' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ },
|
|
|
+ showSymbol: false
|
|
|
+ }]
|
|
|
+ });
|
|
|
+
|
|
|
+ // 湿度图表
|
|
|
+ const humidityElement = document.getElementById('humidityChart');
|
|
|
+ if (!humidityElement) return;
|
|
|
+ const humidityChart = this.$echarts.init(humidityElement);
|
|
|
+ humidityChart.setOption({
|
|
|
+ grid: { top: 10, right: 10, bottom: 10, left: 10 },
|
|
|
+ xAxis: { show: false, type: 'category', data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] },
|
|
|
+ yAxis: { show: false, type: 'value' },
|
|
|
+ series: [{
|
|
|
+ type: 'line',
|
|
|
+ data: [65, 68, 70, 66, 69, 68, 67],
|
|
|
+ smooth: true,
|
|
|
+ lineStyle: { color: '#3b82f6', width: 2 },
|
|
|
+ areaStyle: {
|
|
|
+ color: {
|
|
|
+ type: 'linear',
|
|
|
+ x: 0, y: 0, x2: 0, y2: 1,
|
|
|
+ colorStops: [
|
|
|
+ { offset: 0, color: 'rgba(59, 130, 246, 0.3)' },
|
|
|
+ { offset: 1, color: 'rgba(59, 130, 246, 0.1)' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ },
|
|
|
+ showSymbol: false
|
|
|
+ }]
|
|
|
+ });
|
|
|
+
|
|
|
+ // 土壤图表
|
|
|
+ const soilElement = document.getElementById('soilChart');
|
|
|
+ if (!soilElement) return;
|
|
|
+ const soilChart = this.$echarts.init(soilElement);
|
|
|
+ soilChart.setOption({
|
|
|
+ grid: { top: 10, right: 10, bottom: 10, left: 10 },
|
|
|
+ xAxis: { show: false, type: 'category', data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] },
|
|
|
+ yAxis: { show: false, type: 'value' },
|
|
|
+ series: [{
|
|
|
+ type: 'bar',
|
|
|
+ data: [7.2, 7.1, 7.3, 7.0, 7.2, 7.1, 7.2],
|
|
|
+ itemStyle: {
|
|
|
+ color: {
|
|
|
+ type: 'linear',
|
|
|
+ x: 0, y: 0, x2: 0, y2: 1,
|
|
|
+ colorStops: [
|
|
|
+ { offset: 0, color: '#8b5cf6' },
|
|
|
+ { offset: 1, color: 'rgba(139, 92, 246, 0.6)' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ },
|
|
|
+ barWidth: '60%'
|
|
|
+ }]
|
|
|
+ });
|
|
|
+
|
|
|
+ // 光照图表
|
|
|
+ const lightElement = document.getElementById('lightChart');
|
|
|
+ if (!lightElement) return;
|
|
|
+ const lightChart = this.$echarts.init(lightElement);
|
|
|
+ lightChart.setOption({
|
|
|
+ grid: { top: 10, right: 10, bottom: 10, left: 10 },
|
|
|
+ xAxis: { show: false, type: 'category', data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] },
|
|
|
+ yAxis: { show: false, type: 'value' },
|
|
|
+ series: [{
|
|
|
+ type: 'line',
|
|
|
+ data: [80, 85, 88, 82, 87, 85, 86],
|
|
|
+ smooth: true,
|
|
|
+ lineStyle: { color: '#f59e0b', width: 2 },
|
|
|
+ areaStyle: {
|
|
|
+ color: {
|
|
|
+ type: 'linear',
|
|
|
+ x: 0, y: 0, x2: 0, y2: 1,
|
|
|
+ colorStops: [
|
|
|
+ { offset: 0, color: 'rgba(245, 158, 11, 0.3)' },
|
|
|
+ { offset: 1, color: 'rgba(245, 158, 11, 0.1)' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ },
|
|
|
+ showSymbol: false
|
|
|
+ }]
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ initCropStatusChart() {
|
|
|
+ const chartElement = document.getElementById('cropStatusChart');
|
|
|
+ if (!chartElement) {
|
|
|
+ console.error('cropStatusChart element not found');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const chart = this.$echarts.init(chartElement);
|
|
|
+ const cropData = this.getCropDataByTimeRange(this.cropTimeRange);
|
|
|
+
|
|
|
+ chart.setOption({
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item',
|
|
|
+ formatter: '{b}: {c}亩 ({d}%)'
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ show: false // 不显示图例,因为我们有自定义的图例
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ type: 'pie',
|
|
|
+ radius: ['45%', '75%'],
|
|
|
+ center: ['50%', '50%'],
|
|
|
+ data: cropData,
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'inside',
|
|
|
+ formatter: '{d}%',
|
|
|
+ fontSize: 12,
|
|
|
+ fontWeight: 'bold',
|
|
|
+ color: 'white'
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ itemStyle: {
|
|
|
+ shadowBlur: 10,
|
|
|
+ shadowOffsetX: 0,
|
|
|
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ initDeviceStatusChart() {
|
|
|
+ const chartElement = document.getElementById('deviceStatusChart');
|
|
|
+ if (!chartElement) {
|
|
|
+ console.error('deviceStatusChart element not found');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const chart = this.$echarts.init(chartElement);
|
|
|
+ chart.setOption({
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item',
|
|
|
+ formatter: '{b}: {c}台 ({d}%)'
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ show: false // 不显示图例,因为我们有自定义的统计信息
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ type: 'pie',
|
|
|
+ radius: '70%',
|
|
|
+ center: ['50%', '50%'],
|
|
|
+ data: [
|
|
|
+ { value: this.deviceStats.online, name: '在线', itemStyle: { color: '#10b981' } },
|
|
|
+ { value: this.deviceStats.offline, name: '离线', itemStyle: { color: '#ef4444' } },
|
|
|
+ { value: this.deviceStats.total - this.deviceStats.online - this.deviceStats.offline, name: '故障', itemStyle: { color: '#f59e0b' } }
|
|
|
+ ],
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'inside',
|
|
|
+ formatter: '{d}%',
|
|
|
+ fontSize: 12,
|
|
|
+ fontWeight: 'bold',
|
|
|
+ color: 'white'
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ itemStyle: {
|
|
|
+ shadowBlur: 10,
|
|
|
+ shadowOffsetX: 0,
|
|
|
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ initProductionChart() {
|
|
|
+ const chartElement = document.getElementById('productionChart');
|
|
|
+ if (!chartElement) {
|
|
|
+ console.error('productionChart element not found');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const chart = this.$echarts.init(chartElement);
|
|
|
+ const productionData = this.getProductionDataByTimeRange(this.productionTimeRange);
|
|
|
+
|
|
|
+ chart.setOption({
|
|
|
+ grid: { left: '15%', right: '10%', top: '15%', bottom: '15%' },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: productionData.xAxis,
|
|
|
+ axisLine: { lineStyle: { color: '#e5e7eb' } },
|
|
|
+ axisTick: { show: false },
|
|
|
+ axisLabel: { color: '#6b7280', fontSize: 12 }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ axisLine: { show: false },
|
|
|
+ axisTick: { show: false },
|
|
|
+ axisLabel: { color: '#6b7280', fontSize: 12 },
|
|
|
+ splitLine: { lineStyle: { color: '#f3f4f6' } }
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ formatter: '{b}: {c}吨'
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ type: 'bar',
|
|
|
+ data: productionData.data,
|
|
|
+ itemStyle: {
|
|
|
+ color: {
|
|
|
+ type: 'linear',
|
|
|
+ x: 0, y: 0, x2: 0, y2: 1,
|
|
|
+ colorStops: [
|
|
|
+ { offset: 0, color: '#10b981' },
|
|
|
+ { offset: 1, color: 'rgba(16, 185, 129, 0.6)' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ },
|
|
|
+ barWidth: '50%'
|
|
|
+ }]
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ initFarmMap() {
|
|
|
+ const mapElement = document.getElementById('farmMap');
|
|
|
+ if (!mapElement) {
|
|
|
+ console.error('farmMap element not found');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查高德地图API是否加载
|
|
|
+ if (typeof AMap === 'undefined') {
|
|
|
+ console.error('高德地图API未加载,使用备用方案');
|
|
|
+ this.renderFallbackMap();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 初始化高德地图
|
|
|
+ this.farmMap = new AMap.Map('farmMap', {
|
|
|
+ zoom: 10,
|
|
|
+ center: [117.015029, 34.197274], // 默认中心点
|
|
|
+ mapStyle: 'amap://styles/whitesmoke', // 清新风格
|
|
|
+ viewMode: '2D',
|
|
|
+ resizeEnable: true
|
|
|
+ });
|
|
|
+
|
|
|
+ // 等待地图加载完成
|
|
|
+ this.farmMap.on('complete', () => {
|
|
|
+ console.log('地图加载完成');
|
|
|
+
|
|
|
+ // 添加地图控件
|
|
|
+ this.farmMap.addControl(new AMap.Scale());
|
|
|
+ this.farmMap.addControl(new AMap.ToolBar({
|
|
|
+ position: 'RB'
|
|
|
+ }));
|
|
|
+
|
|
|
+ const currentSelection = this.getCurrentSelection();
|
|
|
+
|
|
|
+ if (currentSelection.type === 'farm' && currentSelection.id === 'all') {
|
|
|
+ // 显示所有农场位置概览
|
|
|
+ this.renderFarmOverviewOnMap();
|
|
|
+ } else if (currentSelection.type === 'farm') {
|
|
|
+ // 显示特定农场的地块详情
|
|
|
+ this.renderFarmDetailsOnMap(currentSelection.id);
|
|
|
+ } else if (currentSelection.type === 'plot') {
|
|
|
+ // 显示特定农场的地块详情,并高亮选中的地块
|
|
|
+ this.renderFarmDetailsOnMap(currentSelection.farmId, currentSelection.plotId);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('地图初始化失败:', error);
|
|
|
+ this.renderFallbackMap();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ renderFallbackMap() {
|
|
|
+ // 备用方案:使用ECharts显示农场分布
|
|
|
+ const mapElement = document.getElementById('farmMap');
|
|
|
+ if (!mapElement) return;
|
|
|
+
|
|
|
+ // 清空容器
|
|
|
+ mapElement.innerHTML = '';
|
|
|
+
|
|
|
+ // 使用ECharts作为备用方案
|
|
|
+ const chart = this.$echarts.init(mapElement);
|
|
|
+
|
|
|
+ const currentSelection = this.getCurrentSelection();
|
|
|
+
|
|
|
+ if (currentSelection.type === 'farm' && currentSelection.id === 'all') {
|
|
|
+ // 显示所有农场位置概览
|
|
|
+ chart.setOption({
|
|
|
+ title: {
|
|
|
+ text: '农场分布图(模拟地图)',
|
|
|
+ subtext: '地图API加载失败,显示备用方案',
|
|
|
+ left: 'center',
|
|
|
+ top: 20,
|
|
|
+ textStyle: {
|
|
|
+ fontSize: 16,
|
|
|
+ color: '#333'
|
|
|
+ },
|
|
|
+ subtextStyle: {
|
|
|
+ fontSize: 12,
|
|
|
+ color: '#666'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ backgroundColor: '#f8fafc',
|
|
|
+ grid: { left: 0, right: 0, top: 60, bottom: 0 },
|
|
|
+ xAxis: {
|
|
|
+ show: false,
|
|
|
+ type: 'value',
|
|
|
+ min: 0,
|
|
|
+ max: 100
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ show: false,
|
|
|
+ type: 'value',
|
|
|
+ min: 0,
|
|
|
+ max: 100
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item',
|
|
|
+ backgroundColor: 'transparent',
|
|
|
+ borderWidth: 0,
|
|
|
+ padding: 0,
|
|
|
+ formatter: function(params) {
|
|
|
+ const data = params.data;
|
|
|
+ return `
|
|
|
+ <div style="
|
|
|
+ padding: 0;
|
|
|
+ margin: 0;
|
|
|
+ min-width: 240px;
|
|
|
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
|
|
+ border: 1px solid #e2e8f0;
|
|
|
+ overflow: hidden;
|
|
|
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
|
+ ">
|
|
|
+ <div style="
|
|
|
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
|
+ padding: 12px 16px;
|
|
|
+ color: white;
|
|
|
+ ">
|
|
|
+ <div style="font-size: 15px; font-weight: 600; margin: 0;">${data[2]}</div>
|
|
|
+ <div style="font-size: 11px; opacity: 0.9; margin-top: 2px;">智慧农业基地</div>
|
|
|
+ </div>
|
|
|
+ <div style="padding: 14px;">
|
|
|
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 12px;">
|
|
|
+ <div style="text-align: center; padding: 8px; background: #f1f5f9; border-radius: 6px;">
|
|
|
+ <div style="font-size: 16px; font-weight: 700; color: #1e293b;">${data[3]}</div>
|
|
|
+ <div style="font-size: 10px; color: #64748b;">面积(亩)</div>
|
|
|
+ </div>
|
|
|
+ <div style="text-align: center; padding: 8px; background: #ecfdf5; border-radius: 6px;">
|
|
|
+ <div style="font-size: 16px; font-weight: 700; color: #059669;">${data[5]}</div>
|
|
|
+ <div style="font-size: 10px; color: #64748b;">在线率</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid #f1f5f9;">
|
|
|
+ <span style="color: #64748b; font-size: 12px;">设备数量</span>
|
|
|
+ <span style="color: #1e293b; font-weight: 600; font-size: 12px;">${data[4]}台</span>
|
|
|
+ </div>
|
|
|
+ <div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 0;">
|
|
|
+ <span style="color: #64748b; font-size: 12px;">主要品种</span>
|
|
|
+ <span style="color: #1e293b; font-weight: 500; font-size: 12px;">${data[6]}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ type: 'scatter',
|
|
|
+ data: [
|
|
|
+ [20, 80, '东区智慧农场', '680', '12', '97.2%', '阳光玫瑰、玫瑰香'],
|
|
|
+ [70, 25, '西区智慧农场', '590', '10', '94.5%', '巨峰、红提'],
|
|
|
+ [80, 75, '南区智慧农场', '720', '14', '96.8%', '阳光玫瑰、夏黑'],
|
|
|
+ [25, 30, '北区智慧农场', '570', '6', '93.2%', '玫瑰香、巨峰'],
|
|
|
+ [50, 50, '中心监测农场', '200', '8', '98.5%', '克瑞森无核']
|
|
|
+ ],
|
|
|
+ symbolSize: function(value) {
|
|
|
+ return Math.max(35, parseInt(value[3]) / 20);
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ color: {
|
|
|
+ type: 'radial',
|
|
|
+ x: 0.5,
|
|
|
+ y: 0.5,
|
|
|
+ r: 0.8,
|
|
|
+ colorStops: [
|
|
|
+ { offset: 0, color: '#34d399' },
|
|
|
+ { offset: 0.7, color: '#10b981' },
|
|
|
+ { offset: 1, color: '#059669' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ borderColor: '#ffffff',
|
|
|
+ borderWidth: 2,
|
|
|
+ shadowBlur: 15,
|
|
|
+ shadowColor: 'rgba(16, 185, 129, 0.4)',
|
|
|
+ shadowOffsetY: 3
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ itemStyle: {
|
|
|
+ color: {
|
|
|
+ type: 'radial',
|
|
|
+ x: 0.5,
|
|
|
+ y: 0.5,
|
|
|
+ r: 0.8,
|
|
|
+ colorStops: [
|
|
|
+ { offset: 0, color: '#6ee7b7' },
|
|
|
+ { offset: 0.7, color: '#059669' },
|
|
|
+ { offset: 1, color: '#047857' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ borderColor: '#ffffff',
|
|
|
+ borderWidth: 3,
|
|
|
+ shadowBlur: 25,
|
|
|
+ shadowColor: 'rgba(16, 185, 129, 0.6)',
|
|
|
+ shadowOffsetY: 5
|
|
|
+ },
|
|
|
+ scale: 1.2
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'bottom',
|
|
|
+ formatter: function(params) {
|
|
|
+ return params.data[2];
|
|
|
+ },
|
|
|
+ color: '#1f2937',
|
|
|
+ fontSize: 13,
|
|
|
+ fontWeight: '600',
|
|
|
+ backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
|
|
+ borderColor: '#e5e7eb',
|
|
|
+ borderWidth: 1,
|
|
|
+ borderRadius: 8,
|
|
|
+ padding: [4, 8],
|
|
|
+ shadowBlur: 3,
|
|
|
+ shadowColor: 'rgba(0, 0, 0, 0.1)',
|
|
|
+ shadowOffsetY: 1
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ label: {
|
|
|
+ backgroundColor: '#ffffff',
|
|
|
+ borderColor: '#10b981',
|
|
|
+ color: '#059669'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 显示地块详情的备用方案
|
|
|
+ this.renderFarmDetailsECharts(chart, currentSelection.farmId || currentSelection.id, currentSelection.plotId);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ renderFarmDetailsECharts(chart, farmId, highlightPlotId = null) {
|
|
|
+ // ECharts版本的农场详情
|
|
|
+ const farmDetailsMap = {
|
|
|
+ east: {
|
|
|
+ boundary: [[10, 70], [30, 90], [40, 85], [35, 65], [10, 70]],
|
|
|
+ plots: [
|
|
|
+ [15, 85, 'A1地块', '220', '4', '健康', '阳光玫瑰'],
|
|
|
+ [25, 80, 'A2地块', '230', '4', '健康', '玫瑰香'],
|
|
|
+ [35, 75, 'A3地块', '230', '4', '良好', '阳光玫瑰']
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ west: {
|
|
|
+ boundary: [[60, 15], [80, 35], [75, 40], [55, 25], [60, 15]],
|
|
|
+ plots: [
|
|
|
+ [65, 25, 'B1地块', '295', '5', '健康', '巨峰'],
|
|
|
+ [75, 30, 'B2地块', '295', '5', '良好', '红提']
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ south: {
|
|
|
+ boundary: [[70, 65], [90, 85], [85, 90], [65, 75], [70, 65]],
|
|
|
+ plots: [
|
|
|
+ [75, 75, 'C1地块', '240', '5', '健康', '阳光玫瑰'],
|
|
|
+ [80, 80, 'C2地块', '240', '5', '健康', '夏黑'],
|
|
|
+ [85, 75, 'C3地块', '240', '4', '良好', '阳光玫瑰']
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ north: {
|
|
|
+ boundary: [[15, 20], [35, 40], [30, 45], [10, 30], [15, 20]],
|
|
|
+ plots: [
|
|
|
+ [20, 30, 'D1地块', '285', '3', '良好', '玫瑰香'],
|
|
|
+ [30, 35, 'D2地块', '285', '3', '健康', '巨峰']
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ center: {
|
|
|
+ boundary: [[40, 40], [60, 60], [55, 65], [35, 50], [40, 40]],
|
|
|
+ plots: [
|
|
|
+ [45, 50, '实验地块1', '67', '3', '优秀', '克瑞森无核'],
|
|
|
+ [50, 55, '实验地块2', '67', '3', '优秀', '阳光玫瑰'],
|
|
|
+ [55, 50, '示范地块', '66', '2', '优秀', '醉金香']
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const farmData = farmDetailsMap[farmId];
|
|
|
+ if (!farmData) return;
|
|
|
+
|
|
|
+ chart.setOption({
|
|
|
+ title: {
|
|
|
+ text: `${farmId === 'east' ? '东区' : farmId === 'west' ? '西区' : farmId === 'south' ? '南区' : farmId === 'north' ? '北区' : '中心'}智慧农场地块分布`,
|
|
|
+ left: 'center',
|
|
|
+ top: 20,
|
|
|
+ textStyle: {
|
|
|
+ fontSize: 16,
|
|
|
+ color: '#333'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ backgroundColor: '#f8fafc',
|
|
|
+ grid: { left: 0, right: 0, top: 60, bottom: 0 },
|
|
|
+ xAxis: {
|
|
|
+ show: false,
|
|
|
+ type: 'value',
|
|
|
+ min: 0,
|
|
|
+ max: 100
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ show: false,
|
|
|
+ type: 'value',
|
|
|
+ min: 0,
|
|
|
+ max: 100
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item',
|
|
|
+ backgroundColor: 'transparent',
|
|
|
+ borderWidth: 0,
|
|
|
+ padding: 0,
|
|
|
+ formatter: function(params) {
|
|
|
+ if (params.seriesIndex === 0) return '';
|
|
|
+ const data = params.data;
|
|
|
+ const isHighlighted = params.color && (params.color.colorStops || params.color).toString().includes('#f59e0b');
|
|
|
+ const healthColor = data[5] === '优秀' ? '#10b981' : data[5] === '健康' ? '#059669' : data[5] === '良好' ? '#f59e0b' : '#ef4444';
|
|
|
+ const healthBgColor = data[5] === '优秀' ? '#ecfdf5' : data[5] === '健康' ? '#f0fdf4' : data[5] === '良好' ? '#fefbf3' : '#fef2f2';
|
|
|
+
|
|
|
+ return `
|
|
|
+ <div style="
|
|
|
+ padding: 0;
|
|
|
+ margin: 0;
|
|
|
+ min-width: 220px;
|
|
|
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
|
|
+ border: 1px solid #e2e8f0;
|
|
|
+ overflow: hidden;
|
|
|
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
|
+ ">
|
|
|
+ <div style="
|
|
|
+ background: linear-gradient(135deg, ${healthColor} 0%, ${healthColor}dd 100%);
|
|
|
+ padding: 12px 14px;
|
|
|
+ color: white;
|
|
|
+ ">
|
|
|
+ <div style="font-size: 14px; font-weight: 600; margin: 0;">${data[2]}</div>
|
|
|
+ <div style="font-size: 10px; opacity: 0.9; margin-top: 2px;">农业种植地块</div>
|
|
|
+ </div>
|
|
|
+ <div style="padding: 12px;">
|
|
|
+ <div style="
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 4px 10px;
|
|
|
+ background: ${healthBgColor};
|
|
|
+ border-radius: 16px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ ">
|
|
|
+ <div style="
|
|
|
+ width: 5px;
|
|
|
+ height: 5px;
|
|
|
+ background: ${healthColor};
|
|
|
+ border-radius: 50%;
|
|
|
+ margin-right: 6px;
|
|
|
+ "></div>
|
|
|
+ <span style="color: ${healthColor}; font-size: 10px; font-weight: 600;">${data[5]}状态</span>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div style="display: flex; justify-content: space-between; align-items: center; padding: 5px 0; border-bottom: 1px solid #f1f5f9;">
|
|
|
+ <span style="color: #64748b; font-size: 11px;">地块面积</span>
|
|
|
+ <span style="color: #1e293b; font-weight: 600; font-size: 12px;">${data[3]} 亩</span>
|
|
|
+ </div>
|
|
|
+ <div style="display: flex; justify-content: space-between; align-items: center; padding: 5px 0; border-bottom: 1px solid #f1f5f9;">
|
|
|
+ <span style="color: #64748b; font-size: 11px;">监测设备</span>
|
|
|
+ <span style="color: #1e293b; font-weight: 600; font-size: 12px;">${data[4]} 台</span>
|
|
|
+ </div>
|
|
|
+ <div style="display: flex; justify-content: space-between; align-items: center; padding: 5px 0;">
|
|
|
+ <span style="color: #64748b; font-size: 11px;">种植品种</span>
|
|
|
+ <span style="color: #1e293b; font-weight: 500; font-size: 12px;">${data[6]}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: 'line',
|
|
|
+ data: farmData.boundary,
|
|
|
+ lineStyle: {
|
|
|
+ color: '#059669',
|
|
|
+ width: 3,
|
|
|
+ type: 'dashed'
|
|
|
+ },
|
|
|
+ symbol: 'none',
|
|
|
+ silent: true
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'scatter',
|
|
|
+ data: farmData.plots.map(plot => {
|
|
|
+ const isHighlighted = highlightPlotId && plot[2].includes(highlightPlotId.split('_')[1]);
|
|
|
+ return {
|
|
|
+ value: plot,
|
|
|
+ itemStyle: isHighlighted ? {
|
|
|
+ color: '#f59e0b',
|
|
|
+ shadowBlur: 20,
|
|
|
+ shadowColor: 'rgba(245, 158, 11, 0.8)',
|
|
|
+ borderColor: '#dc2626',
|
|
|
+ borderWidth: 3
|
|
|
+ } : {
|
|
|
+ color: '#10b981',
|
|
|
+ shadowBlur: 8,
|
|
|
+ shadowColor: 'rgba(16, 185, 129, 0.4)'
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }),
|
|
|
+ symbolSize: function(value) {
|
|
|
+ const isHighlighted = highlightPlotId && value[2].includes(highlightPlotId.split('_')[1]);
|
|
|
+ return isHighlighted ? 35 : 25;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ renderFarmOverviewOnMap() {
|
|
|
+ // 清除之前的标记
|
|
|
+ if (this.farmMarkers) {
|
|
|
+ this.farmMap.remove(this.farmMarkers);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 农场数据(基于新的中心位置分布)
|
|
|
+ const farmData = [
|
|
|
+ {
|
|
|
+ name: '东区智慧农场',
|
|
|
+ position: [117.065029, 34.227274],
|
|
|
+ area: '680',
|
|
|
+ devices: '12',
|
|
|
+ onlineRate: '97.2%',
|
|
|
+ crops: '阳光玫瑰、玫瑰香'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '西区智慧农场',
|
|
|
+ position: [116.965029, 34.167274],
|
|
|
+ area: '590',
|
|
|
+ devices: '10',
|
|
|
+ onlineRate: '94.5%',
|
|
|
+ crops: '巨峰、红提'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '南区智慧农场',
|
|
|
+ position: [117.025029, 34.147274],
|
|
|
+ area: '720',
|
|
|
+ devices: '14',
|
|
|
+ onlineRate: '96.8%',
|
|
|
+ crops: '阳光玫瑰、夏黑'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '北区智慧农场',
|
|
|
+ position: [116.985029, 34.247274],
|
|
|
+ area: '570',
|
|
|
+ devices: '6',
|
|
|
+ onlineRate: '93.2%',
|
|
|
+ crops: '玫瑰香、巨峰'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '中心监测农场',
|
|
|
+ position: [117.015029, 34.197274],
|
|
|
+ area: '200',
|
|
|
+ devices: '8',
|
|
|
+ onlineRate: '98.5%',
|
|
|
+ crops: '克瑞森无核'
|
|
|
+ }
|
|
|
+ ];
|
|
|
+
|
|
|
+ this.farmMarkers = [];
|
|
|
+
|
|
|
+ farmData.forEach((farm, index) => {
|
|
|
+ console.log('创建农场标记:', farm.name, farm.position);
|
|
|
+
|
|
|
+ // 创建简单的标记先测试
|
|
|
+ const marker = new AMap.Marker({
|
|
|
+ position: farm.position,
|
|
|
+ title: farm.name
|
|
|
+ });
|
|
|
+
|
|
|
+ // 完全避免SVG,使用高德地图内置样式
|
|
|
+ // 创建自定义HTML标记元素
|
|
|
+ const markerContent = document.createElement('div');
|
|
|
+ markerContent.style.cssText = `
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ background: #10b981;
|
|
|
+ border: 2px solid white;
|
|
|
+ border-radius: 50%;
|
|
|
+ box-shadow: 0 2px 6px rgba(0,0,0,0.3);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 3px;
|
|
|
+ `;
|
|
|
+
|
|
|
+ // 创建田地的4个小方格
|
|
|
+ const fieldGrid = document.createElement('div');
|
|
|
+ fieldGrid.style.cssText = `
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1fr;
|
|
|
+ grid-template-rows: 1fr 1fr;
|
|
|
+ gap: 1px;
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ `;
|
|
|
+
|
|
|
+ // 创建4个小方格
|
|
|
+ for (let i = 0; i < 4; i++) {
|
|
|
+ const gridItem = document.createElement('div');
|
|
|
+ gridItem.style.cssText = `
|
|
|
+ background: white;
|
|
|
+ border-radius: 1px;
|
|
|
+ opacity: 0.9;
|
|
|
+ `;
|
|
|
+ fieldGrid.appendChild(gridItem);
|
|
|
+ }
|
|
|
+
|
|
|
+ markerContent.appendChild(fieldGrid);
|
|
|
+ marker.setContent(markerContent);
|
|
|
+ marker.setOffset(new AMap.Pixel(-10, -10));
|
|
|
+
|
|
|
+ // 创建信息窗体 - 自定义样式 + 手动添加三角箭头
|
|
|
+ const infoWindow = new AMap.InfoWindow({
|
|
|
+ isCustom: true,
|
|
|
+ content: `
|
|
|
+ <div style="
|
|
|
+ padding: 0;
|
|
|
+ margin: 0;
|
|
|
+ min-width: 280px;
|
|
|
+ position: relative;
|
|
|
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
|
+ ">
|
|
|
+ <!-- 主悬浮框 -->
|
|
|
+ <div style="
|
|
|
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
|
|
+ border: 1px solid #e2e8f0;
|
|
|
+ overflow: hidden;
|
|
|
+ position: relative;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ ">
|
|
|
+ <div style="
|
|
|
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
|
+ padding: 16px 20px;
|
|
|
+ color: white;
|
|
|
+ position: relative;
|
|
|
+ ">
|
|
|
+ <div style="
|
|
|
+ position: absolute;
|
|
|
+ top: -10px;
|
|
|
+ right: -10px;
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ background: rgba(255,255,255,0.1);
|
|
|
+ border-radius: 50%;
|
|
|
+ "></div>
|
|
|
+ <h4 style="
|
|
|
+ margin: 0;
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ text-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
|
|
+ ">${farm.name}</h4>
|
|
|
+ <div style="
|
|
|
+ font-size: 12px;
|
|
|
+ opacity: 0.9;
|
|
|
+ margin-top: 4px;
|
|
|
+ ">智慧农业基地</div>
|
|
|
+ <!-- 关闭按钮 -->
|
|
|
+ <div onclick="this.parentElement.parentElement.parentElement.style.display='none'" style="
|
|
|
+ position: absolute;
|
|
|
+ top: 8px;
|
|
|
+ right: 8px;
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: rgba(255,255,255,0.2);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 12px;
|
|
|
+ color: white;
|
|
|
+ font-weight: bold;
|
|
|
+ ">×</div>
|
|
|
+ </div>
|
|
|
+ <div style="padding: 20px;">
|
|
|
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;">
|
|
|
+ <div style="text-align: center; padding: 12px; background: #f1f5f9; border-radius: 8px;">
|
|
|
+ <div style="font-size: 20px; font-weight: 700; color: #1e293b; margin-bottom: 4px;">${farm.area}</div>
|
|
|
+ <div style="font-size: 12px; color: #64748b;">总面积(亩)</div>
|
|
|
+ </div>
|
|
|
+ <div style="text-align: center; padding: 12px; background: #ecfdf5; border-radius: 8px;">
|
|
|
+ <div style="font-size: 20px; font-weight: 700; color: #059669; margin-bottom: 4px;">${farm.onlineRate}</div>
|
|
|
+ <div style="font-size: 12px; color: #64748b;">设备在线率</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div style="space-y: 12px;">
|
|
|
+ <div style="
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 8px 0;
|
|
|
+ border-bottom: 1px solid #f1f5f9;
|
|
|
+ ">
|
|
|
+ <div style="
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ background: #3b82f6;
|
|
|
+ border-radius: 50%;
|
|
|
+ margin-right: 12px;
|
|
|
+ "></div>
|
|
|
+ <span style="color: #475569; font-size: 14px; flex: 1;">设备数量</span>
|
|
|
+ <span style="color: #1e293b; font-weight: 600; font-size: 14px;">${farm.devices}台</span>
|
|
|
+ </div>
|
|
|
+ <div style="
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 8px 0;
|
|
|
+ ">
|
|
|
+ <div style="
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ background: #f59e0b;
|
|
|
+ border-radius: 50%;
|
|
|
+ margin-right: 12px;
|
|
|
+ "></div>
|
|
|
+ <span style="color: #475569; font-size: 14px; flex: 1;">主要品种</span>
|
|
|
+ <span style="color: #1e293b; font-weight: 500; font-size: 14px;">${farm.crops}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 自定义三角箭头 -->
|
|
|
+ <div style="
|
|
|
+ position: absolute;
|
|
|
+ bottom: -12px;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border-left: 12px solid transparent;
|
|
|
+ border-right: 12px solid transparent;
|
|
|
+ border-top: 12px solid #cbd5e1;
|
|
|
+ z-index: 1;
|
|
|
+ filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
|
|
|
+ "></div>
|
|
|
+ <div style="
|
|
|
+ position: absolute;
|
|
|
+ bottom: -10px;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border-left: 10px solid transparent;
|
|
|
+ border-right: 10px solid transparent;
|
|
|
+ border-top: 10px solid #ffffff;
|
|
|
+ z-index: 2;
|
|
|
+ "></div>
|
|
|
+ </div>
|
|
|
+ `,
|
|
|
+ offset: new AMap.Pixel(0, -5)
|
|
|
+ });
|
|
|
+
|
|
|
+ // 点击标记显示信息窗体
|
|
|
+ marker.on('click', () => {
|
|
|
+ // 关闭其他可能打开的信息窗体
|
|
|
+ this.farmMap.clearInfoWindow();
|
|
|
+ // 在标记位置打开信息窗体
|
|
|
+ infoWindow.open(this.farmMap, marker.getPosition());
|
|
|
+ });
|
|
|
+
|
|
|
+ this.farmMarkers.push(marker);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 添加标记到地图
|
|
|
+ console.log('添加标记到地图,标记数量:', this.farmMarkers.length);
|
|
|
+ this.farmMap.add(this.farmMarkers);
|
|
|
+
|
|
|
+ // 检查标记是否成功添加
|
|
|
+ setTimeout(() => {
|
|
|
+ console.log('地图上的标记数量:', this.farmMap.getAllOverlays('marker').length);
|
|
|
+ }, 500);
|
|
|
+
|
|
|
+ // 自适应显示所有农场
|
|
|
+ const bounds = new AMap.Bounds();
|
|
|
+ farmData.forEach(farm => {
|
|
|
+ bounds.extend(farm.position);
|
|
|
+ });
|
|
|
+ this.farmMap.setBounds(bounds, false, [50, 50, 50, 50]);
|
|
|
+
|
|
|
+ console.log('地图边界设置完成');
|
|
|
+ },
|
|
|
+
|
|
|
+ renderFarmDetailsOnMap(farmId, highlightPlotId = null) {
|
|
|
+ // 清除之前的标记
|
|
|
+ if (this.farmMarkers) {
|
|
|
+ this.farmMap.remove(this.farmMarkers);
|
|
|
+ }
|
|
|
+ if (this.plotPolygons) {
|
|
|
+ this.farmMap.remove(this.plotPolygons);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 农场详情数据
|
|
|
+ const farmDetailsMap = {
|
|
|
+ east: {
|
|
|
+ center: [117.065029, 34.227274],
|
|
|
+ plots: [
|
|
|
+ {
|
|
|
+ name: 'A1地块',
|
|
|
+ area: '220',
|
|
|
+ devices: '4',
|
|
|
+ health: '健康',
|
|
|
+ crop: '阳光玫瑰',
|
|
|
+ polygon: [
|
|
|
+ [117.063029, 34.237274],
|
|
|
+ [117.067029, 34.237274],
|
|
|
+ [117.067029, 34.217274],
|
|
|
+ [117.063029, 34.217274]
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: 'A2地块',
|
|
|
+ area: '230',
|
|
|
+ devices: '4',
|
|
|
+ health: '健康',
|
|
|
+ crop: '玫瑰香',
|
|
|
+ polygon: [
|
|
|
+ [117.067029, 34.237274],
|
|
|
+ [117.071029, 34.237274],
|
|
|
+ [117.071029, 34.217274],
|
|
|
+ [117.067029, 34.217274]
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: 'A3地块',
|
|
|
+ area: '230',
|
|
|
+ devices: '4',
|
|
|
+ health: '良好',
|
|
|
+ crop: '阳光玫瑰',
|
|
|
+ polygon: [
|
|
|
+ [117.063029, 34.217274],
|
|
|
+ [117.071029, 34.217274],
|
|
|
+ [117.071029, 34.197274],
|
|
|
+ [117.063029, 34.197274]
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ west: {
|
|
|
+ center: [116.965029, 34.167274],
|
|
|
+ plots: [
|
|
|
+ {
|
|
|
+ name: 'B1地块',
|
|
|
+ area: '295',
|
|
|
+ devices: '5',
|
|
|
+ health: '健康',
|
|
|
+ crop: '巨峰',
|
|
|
+ polygon: [
|
|
|
+ [116.963029, 34.177274],
|
|
|
+ [116.967029, 34.177274],
|
|
|
+ [116.967029, 34.157274],
|
|
|
+ [116.963029, 34.157274]
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: 'B2地块',
|
|
|
+ area: '295',
|
|
|
+ devices: '5',
|
|
|
+ health: '良好',
|
|
|
+ crop: '红提',
|
|
|
+ polygon: [
|
|
|
+ [116.967029, 34.177274],
|
|
|
+ [116.971029, 34.177274],
|
|
|
+ [116.971029, 34.157274],
|
|
|
+ [116.967029, 34.157274]
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ south: {
|
|
|
+ center: [117.025029, 34.147274],
|
|
|
+ plots: [
|
|
|
+ {
|
|
|
+ name: 'C1地块',
|
|
|
+ area: '240',
|
|
|
+ devices: '5',
|
|
|
+ health: '健康',
|
|
|
+ crop: '阳光玫瑰',
|
|
|
+ polygon: [
|
|
|
+ [117.023029, 34.157274],
|
|
|
+ [117.027029, 34.157274],
|
|
|
+ [117.027029, 34.137274],
|
|
|
+ [117.023029, 34.137274]
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: 'C2地块',
|
|
|
+ area: '240',
|
|
|
+ devices: '5',
|
|
|
+ health: '健康',
|
|
|
+ crop: '夏黑',
|
|
|
+ polygon: [
|
|
|
+ [117.027029, 34.157274],
|
|
|
+ [117.031029, 34.157274],
|
|
|
+ [117.031029, 34.137274],
|
|
|
+ [117.027029, 34.137274]
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: 'C3地块',
|
|
|
+ area: '240',
|
|
|
+ devices: '4',
|
|
|
+ health: '良好',
|
|
|
+ crop: '阳光玫瑰',
|
|
|
+ polygon: [
|
|
|
+ [117.023029, 34.137274],
|
|
|
+ [117.031029, 34.137274],
|
|
|
+ [117.031029, 34.117274],
|
|
|
+ [117.023029, 34.117274]
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ north: {
|
|
|
+ center: [116.985029, 34.247274],
|
|
|
+ plots: [
|
|
|
+ {
|
|
|
+ name: 'D1地块',
|
|
|
+ area: '285',
|
|
|
+ devices: '3',
|
|
|
+ health: '良好',
|
|
|
+ crop: '玫瑰香',
|
|
|
+ polygon: [
|
|
|
+ [116.983029, 34.257274],
|
|
|
+ [116.987029, 34.257274],
|
|
|
+ [116.987029, 34.237274],
|
|
|
+ [116.983029, 34.237274]
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: 'D2地块',
|
|
|
+ area: '285',
|
|
|
+ devices: '3',
|
|
|
+ health: '健康',
|
|
|
+ crop: '巨峰',
|
|
|
+ polygon: [
|
|
|
+ [116.987029, 34.257274],
|
|
|
+ [116.991029, 34.257274],
|
|
|
+ [116.991029, 34.237274],
|
|
|
+ [116.987029, 34.237274]
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ center: {
|
|
|
+ center: [117.015029, 34.197274],
|
|
|
+ plots: [
|
|
|
+ {
|
|
|
+ name: '实验地块1',
|
|
|
+ area: '67',
|
|
|
+ devices: '3',
|
|
|
+ health: '优秀',
|
|
|
+ crop: '克瑞森无核',
|
|
|
+ polygon: [
|
|
|
+ [117.013029, 34.207274],
|
|
|
+ [117.017029, 34.207274],
|
|
|
+ [117.017029, 34.187274],
|
|
|
+ [117.013029, 34.187274]
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '实验地块2',
|
|
|
+ area: '67',
|
|
|
+ devices: '3',
|
|
|
+ health: '优秀',
|
|
|
+ crop: '阳光玫瑰',
|
|
|
+ polygon: [
|
|
|
+ [117.017029, 34.207274],
|
|
|
+ [117.021029, 34.207274],
|
|
|
+ [117.021029, 34.187274],
|
|
|
+ [117.017029, 34.187274]
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '示范地块',
|
|
|
+ area: '66',
|
|
|
+ devices: '2',
|
|
|
+ health: '优秀',
|
|
|
+ crop: '醉金香',
|
|
|
+ polygon: [
|
|
|
+ [117.013029, 34.187274],
|
|
|
+ [117.021029, 34.187274],
|
|
|
+ [117.021029, 34.167274],
|
|
|
+ [117.013029, 34.167274]
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const farmData = farmDetailsMap[farmId];
|
|
|
+ if (!farmData) return;
|
|
|
+
|
|
|
+ // 设置地图中心
|
|
|
+ this.farmMap.setCenter(farmData.center);
|
|
|
+ this.farmMap.setZoom(14);
|
|
|
+
|
|
|
+ this.plotPolygons = [];
|
|
|
+ this.farmMarkers = [];
|
|
|
+
|
|
|
+ farmData.plots.forEach((plot, index) => {
|
|
|
+ const isHighlighted = highlightPlotId && plot.name.includes(highlightPlotId.split('_')[1]);
|
|
|
+
|
|
|
+ // 创建地块多边形
|
|
|
+ const polygon = new AMap.Polygon({
|
|
|
+ path: plot.polygon,
|
|
|
+ strokeColor: isHighlighted ? '#f59e0b' : '#10b981',
|
|
|
+ strokeWeight: isHighlighted ? 3 : 2,
|
|
|
+ strokeOpacity: 0.8,
|
|
|
+ fillColor: isHighlighted ? '#f59e0b' : '#10b981',
|
|
|
+ fillOpacity: isHighlighted ? 0.4 : 0.2,
|
|
|
+ cursor: 'pointer'
|
|
|
+ });
|
|
|
+
|
|
|
+ // 地块中心点坐标
|
|
|
+ const centerLng = plot.polygon.reduce((sum, point) => sum + point[0], 0) / plot.polygon.length;
|
|
|
+ const centerLat = plot.polygon.reduce((sum, point) => sum + point[1], 0) / plot.polygon.length;
|
|
|
+
|
|
|
+ // 创建地块标记
|
|
|
+ const marker = new AMap.Marker({
|
|
|
+ position: [centerLng, centerLat],
|
|
|
+ title: plot.name
|
|
|
+ });
|
|
|
+
|
|
|
+ // 添加自定义图标
|
|
|
+ try {
|
|
|
+ marker.setIcon(new AMap.Icon({
|
|
|
+ size: new AMap.Size(32, 32),
|
|
|
+ image: 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(`
|
|
|
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
|
|
+ <defs>
|
|
|
+ <filter id="plotShadow" x="-50%" y="-50%" width="200%" height="200%">
|
|
|
+ <feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="rgba(0,0,0,0.25)"/>
|
|
|
+ </filter>
|
|
|
+ <linearGradient id="plotGradient${index}" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
|
+ <stop offset="0%" style="stop-color:${isHighlighted ? '#f59e0b' : '#10b981'};stop-opacity:1" />
|
|
|
+ <stop offset="100%" style="stop-color:${isHighlighted ? '#d97706' : '#059669'};stop-opacity:1" />
|
|
|
+ </linearGradient>
|
|
|
+ </defs>
|
|
|
+ <circle cx="16" cy="16" r="13" fill="url(#plotGradient${index})" filter="url(#plotShadow)"/>
|
|
|
+ <circle cx="16" cy="16" r="13" fill="none" stroke="${isHighlighted ? '#ffffff' : '#ffffff'}" stroke-width="2" opacity="0.9"/>
|
|
|
+ ${isHighlighted ?
|
|
|
+ '<circle cx="16" cy="16" r="13" fill="none" stroke="#dc2626" stroke-width="3" opacity="0.6" stroke-dasharray="4,2"/>' :
|
|
|
+ ''
|
|
|
+ }
|
|
|
+ <g transform="translate(16,16)">
|
|
|
+ <rect x="-6" y="-6" width="12" height="12" fill="none" stroke="white" stroke-width="1.5" opacity="0.9" rx="1"/>
|
|
|
+ <path d="M-3,-6 L-3,6 M0,-6 L0,6 M3,-6 L3,6" stroke="white" stroke-width="0.8" opacity="0.7"/>
|
|
|
+ <path d="M-6,-3 L6,-3 M-6,0 L6,0 M-6,3 L6,3" stroke="white" stroke-width="0.8" opacity="0.7"/>
|
|
|
+ <circle cx="0" cy="0" r="1.5" fill="white" opacity="0.95"/>
|
|
|
+ </g>
|
|
|
+ </svg>
|
|
|
+ `),
|
|
|
+ imageOffset: new AMap.Pixel(-16, -16)
|
|
|
+ }));
|
|
|
+ } catch (iconError) {
|
|
|
+ console.warn('设置地块图标失败:', iconError);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建信息窗体 - 自定义样式 + 手动添加三角箭头
|
|
|
+ const healthColor = plot.health === '优秀' ? '#10b981' : plot.health === '健康' ? '#059669' : plot.health === '良好' ? '#f59e0b' : '#ef4444';
|
|
|
+ const healthBgColor = plot.health === '优秀' ? '#ecfdf5' : plot.health === '健康' ? '#f0fdf4' : plot.health === '良好' ? '#fefbf3' : '#fef2f2';
|
|
|
+
|
|
|
+ const infoWindow = new AMap.InfoWindow({
|
|
|
+ isCustom: true,
|
|
|
+ content: `
|
|
|
+ <div style="
|
|
|
+ padding: 0;
|
|
|
+ margin: 0;
|
|
|
+ min-width: 260px;
|
|
|
+ position: relative;
|
|
|
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
|
+ ">
|
|
|
+ <!-- 主悬浮框 -->
|
|
|
+ <div style="
|
|
|
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
|
|
+ border: 1px solid #e2e8f0;
|
|
|
+ overflow: hidden;
|
|
|
+ position: relative;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ ">
|
|
|
+ <div style="
|
|
|
+ background: linear-gradient(135deg, ${healthColor} 0%, ${healthColor}dd 100%);
|
|
|
+ padding: 14px 18px;
|
|
|
+ color: white;
|
|
|
+ position: relative;
|
|
|
+ ">
|
|
|
+ <div style="
|
|
|
+ position: absolute;
|
|
|
+ top: -8px;
|
|
|
+ right: -8px;
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ background: rgba(255,255,255,0.15);
|
|
|
+ border-radius: 50%;
|
|
|
+ "></div>
|
|
|
+ <h4 style="
|
|
|
+ margin: 0;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ text-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
|
|
+ ">${plot.name}</h4>
|
|
|
+ <div style="
|
|
|
+ font-size: 11px;
|
|
|
+ opacity: 0.9;
|
|
|
+ margin-top: 2px;
|
|
|
+ ">农业种植地块</div>
|
|
|
+ <!-- 关闭按钮 -->
|
|
|
+ <div onclick="this.parentElement.parentElement.parentElement.style.display='none'" style="
|
|
|
+ position: absolute;
|
|
|
+ top: 6px;
|
|
|
+ right: 6px;
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: rgba(255,255,255,0.2);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 10px;
|
|
|
+ color: white;
|
|
|
+ font-weight: bold;
|
|
|
+ ">×</div>
|
|
|
+ </div>
|
|
|
+ <div style="padding: 16px;">
|
|
|
+ <div style="
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 6px 12px;
|
|
|
+ background: ${healthBgColor};
|
|
|
+ border-radius: 20px;
|
|
|
+ margin-bottom: 14px;
|
|
|
+ ">
|
|
|
+ <div style="
|
|
|
+ width: 6px;
|
|
|
+ height: 6px;
|
|
|
+ background: ${healthColor};
|
|
|
+ border-radius: 50%;
|
|
|
+ margin-right: 8px;
|
|
|
+ "></div>
|
|
|
+ <span style="color: ${healthColor}; font-size: 12px; font-weight: 600;">${plot.health}状态</span>
|
|
|
+ </div>
|
|
|
+ <div style="space-y: 10px;">
|
|
|
+ <div style="
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 8px 0;
|
|
|
+ border-bottom: 1px solid #f1f5f9;
|
|
|
+ ">
|
|
|
+ <span style="color: #64748b; font-size: 13px;">地块面积</span>
|
|
|
+ <span style="color: #1e293b; font-weight: 600; font-size: 14px;">${plot.area} 亩</span>
|
|
|
+ </div>
|
|
|
+ <div style="
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 8px 0;
|
|
|
+ border-bottom: 1px solid #f1f5f9;
|
|
|
+ ">
|
|
|
+ <span style="color: #64748b; font-size: 13px;">监测设备</span>
|
|
|
+ <span style="color: #1e293b; font-weight: 600; font-size: 14px;">${plot.devices} 台</span>
|
|
|
+ </div>
|
|
|
+ <div style="
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 8px 0;
|
|
|
+ ">
|
|
|
+ <span style="color: #64748b; font-size: 13px;">种植品种</span>
|
|
|
+ <span style="color: #1e293b; font-weight: 500; font-size: 14px;">${plot.crop}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 自定义三角箭头 -->
|
|
|
+ <div style="
|
|
|
+ position: absolute;
|
|
|
+ bottom: -12px;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border-left: 12px solid transparent;
|
|
|
+ border-right: 12px solid transparent;
|
|
|
+ border-top: 12px solid #cbd5e1;
|
|
|
+ z-index: 1;
|
|
|
+ filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
|
|
|
+ "></div>
|
|
|
+ <div style="
|
|
|
+ position: absolute;
|
|
|
+ bottom: -10px;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border-left: 10px solid transparent;
|
|
|
+ border-right: 10px solid transparent;
|
|
|
+ border-top: 10px solid #ffffff;
|
|
|
+ z-index: 2;
|
|
|
+ "></div>
|
|
|
+ </div>
|
|
|
+ `,
|
|
|
+ offset: new AMap.Pixel(-130, -15)
|
|
|
+ });
|
|
|
+
|
|
|
+ // 点击事件
|
|
|
+ const showInfo = () => {
|
|
|
+ // 关闭其他可能打开的信息窗体
|
|
|
+ this.farmMap.clearInfoWindow();
|
|
|
+ // 在地块中心位置打开信息窗体
|
|
|
+ infoWindow.open(this.farmMap, [centerLng, centerLat]);
|
|
|
+ };
|
|
|
+
|
|
|
+ marker.on('click', showInfo);
|
|
|
+ polygon.on('click', showInfo);
|
|
|
+
|
|
|
+ this.plotPolygons.push(polygon);
|
|
|
+ this.farmMarkers.push(marker);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 添加到地图
|
|
|
+ this.farmMap.add(this.plotPolygons);
|
|
|
+ this.farmMap.add(this.farmMarkers);
|
|
|
+ },
|
|
|
+
|
|
|
+ updateMapContent() {
|
|
|
+ // 更新地图内容,不重新创建地图实例
|
|
|
+ if (this.farmMap) {
|
|
|
+ const currentSelection = this.getCurrentSelection();
|
|
|
+
|
|
|
+ if (currentSelection.type === 'farm' && currentSelection.id === 'all') {
|
|
|
+ this.renderFarmOverviewOnMap();
|
|
|
+ } else if (currentSelection.type === 'farm') {
|
|
|
+ this.renderFarmDetailsOnMap(currentSelection.id);
|
|
|
+ } else if (currentSelection.type === 'plot') {
|
|
|
+ this.renderFarmDetailsOnMap(currentSelection.farmId, currentSelection.plotId);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 如果地图实例不存在,使用备用方案更新
|
|
|
+ const mapElement = document.getElementById('farmMap');
|
|
|
+ if (mapElement && this.$echarts) {
|
|
|
+ const chart = this.$echarts.getInstanceByDom(mapElement);
|
|
|
+ if (chart) {
|
|
|
+ const currentSelection = this.getCurrentSelection();
|
|
|
+ if (currentSelection.type === 'farm' && currentSelection.id === 'all') {
|
|
|
+ this.renderFallbackMap();
|
|
|
+ } else {
|
|
|
+ this.renderFarmDetailsECharts(chart, currentSelection.farmId || currentSelection.id, currentSelection.plotId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ getCropDataByTimeRange(timeRange) {
|
|
|
+ // 根据时间范围返回不同的葡萄品种数据
|
|
|
+ const cropDataMap = {
|
|
|
+ week: [
|
|
|
+ { value: 280, name: '阳光玫瑰', itemStyle: { color: '#10b981' } }, // 43.8%
|
|
|
+ { value: 180, name: '玫瑰香', itemStyle: { color: '#3b82f6' } }, // 28.1%
|
|
|
+ { value: 120, name: '巨峰', itemStyle: { color: '#f59e0b' } }, // 18.8%
|
|
|
+ { value: 60, name: '夏黑', itemStyle: { color: '#8b5cf6' } } // 9.4%
|
|
|
+ ],
|
|
|
+ month: [
|
|
|
+ { value: 1200, name: '阳光玫瑰', itemStyle: { color: '#10b981' } }, // 46.9%
|
|
|
+ { value: 580, name: '玫瑰香', itemStyle: { color: '#3b82f6' } }, // 22.7%
|
|
|
+ { value: 520, name: '巨峰', itemStyle: { color: '#f59e0b' } }, // 20.3%
|
|
|
+ { value: 260, name: '夏黑', itemStyle: { color: '#8b5cf6' } } // 10.2%
|
|
|
+ ],
|
|
|
+ season: [
|
|
|
+ { value: 3800, name: '阳光玫瑰', itemStyle: { color: '#10b981' } }, // 50.0%
|
|
|
+ { value: 1520, name: '玫瑰香', itemStyle: { color: '#3b82f6' } }, // 20.0%
|
|
|
+ { value: 1520, name: '巨峰', itemStyle: { color: '#f59e0b' } }, // 20.0%
|
|
|
+ { value: 760, name: '夏黑', itemStyle: { color: '#8b5cf6' } } // 10.0%
|
|
|
+ ]
|
|
|
+ };
|
|
|
+
|
|
|
+ return cropDataMap[timeRange] || cropDataMap.month;
|
|
|
+ },
|
|
|
+
|
|
|
+ getProductionDataByTimeRange(timeRange) {
|
|
|
+ // 根据时间范围返回不同的生产数据
|
|
|
+ const productionDataMap = {
|
|
|
+ month: {
|
|
|
+ xAxis: ['1月', '2月', '3月', '4月', '5月', '6月'],
|
|
|
+ data: [120, 200, 150, 80, 70, 110]
|
|
|
+ },
|
|
|
+ quarter: {
|
|
|
+ xAxis: ['第一季度', '第二季度', '第三季度', '第四季度'],
|
|
|
+ data: [470, 340, 380, 420]
|
|
|
+ },
|
|
|
+ year: {
|
|
|
+ xAxis: ['2019年', '2020年', '2021年', '2022年', '2023年', '2024年'],
|
|
|
+ data: [1350, 1480, 1620, 1750, 1680, 1800]
|
|
|
+ }
|
|
|
+ };
|
|
|
+ return productionDataMap[timeRange] || productionDataMap.month;
|
|
|
+ },
|
|
|
+
|
|
|
+ updateCropChart() {
|
|
|
+ // 更新葡萄生长状况图表
|
|
|
+ this.$nextTick(() => {
|
|
|
+ const chartElement = document.getElementById('cropStatusChart');
|
|
|
+ if (!chartElement || !this.$echarts) return;
|
|
|
+
|
|
|
+ const chart = this.$echarts.getInstanceByDom(chartElement);
|
|
|
+ if (!chart) {
|
|
|
+ this.initCropStatusChart();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const cropData = this.getCropDataByTimeRange(this.cropTimeRange);
|
|
|
+
|
|
|
+ chart.setOption({
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item',
|
|
|
+ formatter: '{b}: {c}亩 ({d}%)'
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ type: 'pie',
|
|
|
+ radius: ['45%', '75%'],
|
|
|
+ center: ['50%', '50%'],
|
|
|
+ data: cropData,
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'inside',
|
|
|
+ formatter: '{d}%',
|
|
|
+ fontSize: 12,
|
|
|
+ fontWeight: 'bold',
|
|
|
+ color: 'white'
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ itemStyle: {
|
|
|
+ shadowBlur: 10,
|
|
|
+ shadowOffsetX: 0,
|
|
|
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ });
|
|
|
+
|
|
|
+ // 强制触发Vue响应式更新
|
|
|
+ this.$forceUpdate();
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ updateProductionChart() {
|
|
|
+ // 更新生产数据统计图表
|
|
|
+ this.$nextTick(() => {
|
|
|
+ const chartElement = document.getElementById('productionChart');
|
|
|
+ if (!chartElement || !this.$echarts) return;
|
|
|
+
|
|
|
+ const chart = this.$echarts.getInstanceByDom(chartElement);
|
|
|
+ if (!chart) return;
|
|
|
+
|
|
|
+ const productionData = this.getProductionDataByTimeRange(this.productionTimeRange);
|
|
|
+
|
|
|
+ chart.setOption({
|
|
|
+ xAxis: {
|
|
|
+ data: productionData.xAxis
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ data: productionData.data
|
|
|
+ }]
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ updateCropLegend() {
|
|
|
+ // 根据当前时间范围更新葡萄品种图例显示
|
|
|
+ const cropData = this.getCropDataByTimeRange(this.cropTimeRange);
|
|
|
+ // 这里可以动态更新页面上的图例显示,如果需要的话
|
|
|
+ },
|
|
|
+
|
|
|
+ getCurrentCropLegend() {
|
|
|
+ // 获取当前时间范围的葡萄品种图例数据
|
|
|
+ const cropData = this.getCropDataByTimeRange(this.cropTimeRange);
|
|
|
+ return cropData.map(item => ({
|
|
|
+ name: item.name,
|
|
|
+ value: item.value,
|
|
|
+ color: item.itemStyle.color
|
|
|
+ }));
|
|
|
}
|
|
|
+
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
-.home {
|
|
|
- blockquote {
|
|
|
- padding: 10px 20px;
|
|
|
- margin: 0 0 20px;
|
|
|
- font-size: 17.5px;
|
|
|
- border-left: 5px solid #eee;
|
|
|
- }
|
|
|
- hr {
|
|
|
- margin-top: 20px;
|
|
|
- margin-bottom: 20px;
|
|
|
- border: 0;
|
|
|
- border-top: 1px solid #eee;
|
|
|
+.dashboard-container {
|
|
|
+ padding: 24px;
|
|
|
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
|
|
+ min-height: 100vh;
|
|
|
+}
|
|
|
+
|
|
|
+.dashboard-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: flex-start;
|
|
|
+ margin-bottom: 32px;
|
|
|
+
|
|
|
+ .title-section {
|
|
|
+ .dashboard-title {
|
|
|
+ font-size: 32px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f2937;
|
|
|
+ margin: 0 0 8px 0;
|
|
|
+ background: linear-gradient(135deg, #10b981, #059669);
|
|
|
+ -webkit-background-clip: text;
|
|
|
+ -webkit-text-fill-color: transparent;
|
|
|
+ background-clip: text;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dashboard-subtitle {
|
|
|
+ font-size: 16px;
|
|
|
+ color: #6b7280;
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
}
|
|
|
- .col-item {
|
|
|
- margin-bottom: 20px;
|
|
|
+
|
|
|
+ .action-buttons {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ .location-selector-group {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ align-items: center;
|
|
|
+ padding: 8px;
|
|
|
+ background: #f8fafc;
|
|
|
+ border-radius: 8px;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+
|
|
|
+ .el-cascader {
|
|
|
+ background: white;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- ul {
|
|
|
- padding: 0;
|
|
|
- margin: 0;
|
|
|
+.overview-section {
|
|
|
+ margin-bottom: 32px;
|
|
|
+
|
|
|
+ .overview-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(4, 1fr);
|
|
|
+ gap: 24px;
|
|
|
}
|
|
|
+
|
|
|
+ .overview-card {
|
|
|
+ background: white;
|
|
|
+ border-radius: 16px;
|
|
|
+ padding: 24px;
|
|
|
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ transform: translateY(-4px);
|
|
|
+ box-shadow: 0 10px 20px -5px rgba(0, 0, 0, 0.15);
|
|
|
+ }
|
|
|
+
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16px;
|
|
|
+
|
|
|
+ .card-icon {
|
|
|
+ width: 64px;
|
|
|
+ height: 64px;
|
|
|
+ border-radius: 16px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: white;
|
|
|
+ font-size: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-content {
|
|
|
+ flex: 1;
|
|
|
+
|
|
|
+ .card-value {
|
|
|
+ font-size: 28px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f2937;
|
|
|
+ line-height: 1;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-label {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-change {
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+
|
|
|
+ &.up {
|
|
|
+ color: #10b981;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.down {
|
|
|
+ color: #ef4444;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
|
- font-size: 13px;
|
|
|
- color: #676a6c;
|
|
|
- overflow-x: hidden;
|
|
|
-
|
|
|
- ul {
|
|
|
- list-style-type: none;
|
|
|
+.main-content {
|
|
|
+ .content-section {
|
|
|
+ margin-bottom: 32px;
|
|
|
+
|
|
|
+ .section-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 20px;
|
|
|
+
|
|
|
+ .section-title {
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1f2937;
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .content-row {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1fr;
|
|
|
+ gap: 24px;
|
|
|
+ margin-bottom: 32px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .content-card {
|
|
|
+ background: white;
|
|
|
+ border-radius: 16px;
|
|
|
+ padding: 24px;
|
|
|
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+
|
|
|
+ &.half-width {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 20px;
|
|
|
+
|
|
|
+ .card-title {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1f2937;
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .device-summary {
|
|
|
+ display: flex;
|
|
|
+ gap: 16px;
|
|
|
+ font-size: 14px;
|
|
|
+
|
|
|
+ .device-count {
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .device-online {
|
|
|
+ color: #10b981;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-container {
|
|
|
+ height: 300px;
|
|
|
+ width: 100%;
|
|
|
+ min-height: 300px;
|
|
|
+
|
|
|
+ #cropStatusChart,
|
|
|
+ #deviceStatusChart,
|
|
|
+ #productionChart {
|
|
|
+ width: 100% !important;
|
|
|
+ height: 100% !important;
|
|
|
+ min-height: 300px;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- h4 {
|
|
|
- margin-top: 0px;
|
|
|
+.environment-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(4, 1fr);
|
|
|
+ gap: 20px;
|
|
|
+
|
|
|
+ .env-card {
|
|
|
+ background: white;
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 20px;
|
|
|
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+
|
|
|
+ .env-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 16px;
|
|
|
+
|
|
|
+ .env-title {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #6b7280;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .env-value {
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f2937;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .env-chart {
|
|
|
+ height: 80px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .env-status {
|
|
|
+ text-align: center;
|
|
|
+ padding: 6px 12px;
|
|
|
+ border-radius: 20px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+
|
|
|
+ &.normal {
|
|
|
+ background: #d1fae5;
|
|
|
+ color: #065f46;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.good {
|
|
|
+ background: #dbeafe;
|
|
|
+ color: #1e40af;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.warning {
|
|
|
+ background: #fef3c7;
|
|
|
+ color: #92400e;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- h2 {
|
|
|
- margin-top: 10px;
|
|
|
- font-size: 26px;
|
|
|
- font-weight: 100;
|
|
|
+.alert-list {
|
|
|
+ max-height: 300px;
|
|
|
+ overflow-y: auto;
|
|
|
+
|
|
|
+ .alert-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 16px;
|
|
|
+ border-bottom: 1px solid #f3f4f6;
|
|
|
+ transition: background-color 0.2s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: #f9fafb;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ border-bottom: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .alert-icon {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ border-radius: 8px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.warning .alert-icon {
|
|
|
+ background: #f59e0b;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.error .alert-icon {
|
|
|
+ background: #ef4444;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.info .alert-icon {
|
|
|
+ background: #3b82f6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .alert-content {
|
|
|
+ flex: 1;
|
|
|
+
|
|
|
+ .alert-title {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #1f2937;
|
|
|
+ font-weight: 500;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .alert-time {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .alert-status {
|
|
|
+ font-size: 12px;
|
|
|
+ padding: 4px 8px;
|
|
|
+ border-radius: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+
|
|
|
+ &.pending {
|
|
|
+ background: #fef3c7;
|
|
|
+ color: #92400e;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.processing {
|
|
|
+ background: #dbeafe;
|
|
|
+ color: #1e40af;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.resolved {
|
|
|
+ background: #d1fae5;
|
|
|
+ color: #065f46;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- p {
|
|
|
- margin-top: 10px;
|
|
|
+.map-container {
|
|
|
+ background: white;
|
|
|
+ border-radius: 16px;
|
|
|
+ padding: 24px;
|
|
|
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ height: 400px;
|
|
|
+
|
|
|
+ #farmMap {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- b {
|
|
|
- font-weight: 700;
|
|
|
+.chart-legend {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 16px;
|
|
|
+ margin-top: 16px;
|
|
|
+ justify-content: center;
|
|
|
+
|
|
|
+ .legend-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #6b7280;
|
|
|
+
|
|
|
+ .legend-color {
|
|
|
+ width: 12px;
|
|
|
+ height: 12px;
|
|
|
+ border-radius: 2px;
|
|
|
}
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- .update-log {
|
|
|
- ol {
|
|
|
+.device-stats {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-around;
|
|
|
+ margin-top: 16px;
|
|
|
+ padding-top: 16px;
|
|
|
+ border-top: 1px solid #f3f4f6;
|
|
|
+
|
|
|
+ .stats-item {
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ .stats-label {
|
|
|
display: block;
|
|
|
- list-style-type: decimal;
|
|
|
- margin-block-start: 1em;
|
|
|
- margin-block-end: 1em;
|
|
|
- margin-inline-start: 0;
|
|
|
- margin-inline-end: 0;
|
|
|
- padding-inline-start: 40px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stats-value {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #10b981;
|
|
|
+
|
|
|
+ &.offline {
|
|
|
+ color: #ef4444;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.alert-badge {
|
|
|
+ .alert-text {
|
|
|
+ color: #6b7280;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 响应式设计
|
|
|
+@media (max-width: 1200px) {
|
|
|
+ .overview-grid {
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ }
|
|
|
+
|
|
|
+ .environment-grid {
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .dashboard-container {
|
|
|
+ padding: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dashboard-header {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16px;
|
|
|
+
|
|
|
+ .title-section .dashboard-title {
|
|
|
+ font-size: 24px;
|
|
|
}
|
|
|
+
|
|
|
+ .action-buttons {
|
|
|
+ flex-direction: column;
|
|
|
+ width: 100%;
|
|
|
+ gap: 16px;
|
|
|
+
|
|
|
+ .location-selector-group {
|
|
|
+ flex-direction: column;
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ .el-cascader {
|
|
|
+ width: 100% !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-button {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .overview-grid,
|
|
|
+ .environment-grid {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+
|
|
|
+ .content-row {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
}
|
|
|
}
|
|
|
</style>
|