|
|
@@ -57,6 +57,21 @@
|
|
|
<el-icon><Picture /></el-icon>
|
|
|
<p>暂无地图数据</p>
|
|
|
</div>
|
|
|
+
|
|
|
+ <div v-if="loadingTownshipMap" class="loading-container">
|
|
|
+ <el-icon class="loading-icon"><Loading /></el-icon>
|
|
|
+ <span>乡镇地图加载中...</span>
|
|
|
+ </div>
|
|
|
+ <!-- ECharts地图容器 -->
|
|
|
+ <div
|
|
|
+ v-show="!loadingTownshipMap"
|
|
|
+ ref="townshipMapRef"
|
|
|
+ class="township-map-container"
|
|
|
+ ></div>
|
|
|
+ <div v-if="!loadingTownshipMap && !townshipMapInstance" class="no-data">
|
|
|
+ <el-icon><Location /></el-icon>
|
|
|
+ <p>暂无乡镇边界数据</p>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 统计图表区域 -->
|
|
|
@@ -114,10 +129,11 @@
|
|
|
|
|
|
<script>
|
|
|
import * as XLSX from 'xlsx';
|
|
|
+import * as echarts from 'echarts';
|
|
|
import { saveAs } from 'file-saver';
|
|
|
import { api8000 } from '@/utils/request';
|
|
|
import {
|
|
|
- Loading, Upload, Picture, Histogram, Download, Document, Box, DataAnalysis
|
|
|
+ Location,Loading, Upload, Picture, Histogram, Download, Document, Box, DataAnalysis
|
|
|
} from '@element-plus/icons-vue';
|
|
|
|
|
|
export default {
|
|
|
@@ -144,7 +160,16 @@ export default {
|
|
|
mapBlob: null,
|
|
|
histogramBlob: null,
|
|
|
selectedFile: null,
|
|
|
+ // 新增:乡镇地图相关数据
|
|
|
+ loadingTownshipMap: false, // 乡镇地图加载状态
|
|
|
+ townshipMapInstance: null, // ECharts实例
|
|
|
+ townshipGeoJson: null, // 本地边界数据(TopoJSON/GeoJSON)
|
|
|
+ townshipData: [], // 接口获取的乡镇Cd数据
|
|
|
countyName: '乐昌市', // 默认县市名称
|
|
|
+ townshipMapRef :null,
|
|
|
+ currentTooltipTown: '', // 当前悬浮的乡镇
|
|
|
+ currentTooltipData: null, // 当前乡镇的详情数据
|
|
|
+ isTooltipLoading: false, // tooltip 是否在加载中
|
|
|
};
|
|
|
},
|
|
|
|
|
|
@@ -152,11 +177,17 @@ export default {
|
|
|
// 组件挂载时获取最新数据
|
|
|
this.fetchLatestResults();
|
|
|
this.fetchStatistics();
|
|
|
+ this.initTownshipMap();
|
|
|
},
|
|
|
|
|
|
beforeDestroy() {
|
|
|
if (this.mapImageUrl) URL.revokeObjectURL(this.mapImageUrl);
|
|
|
if (this.histogramImageUrl) URL.revokeObjectURL(this.histogramImageUrl);
|
|
|
+ // 新增:销毁ECharts实例,避免内存泄漏
|
|
|
+ if (this.townshipMapInstance) {
|
|
|
+ this.townshipMapInstance.dispose();
|
|
|
+ this.townshipMapInstance = null;
|
|
|
+ }
|
|
|
},
|
|
|
methods: {
|
|
|
// 触发文件选择
|
|
|
@@ -313,6 +344,189 @@ export default {
|
|
|
this.loadingStats = false;
|
|
|
}
|
|
|
},
|
|
|
+
|
|
|
+ // 新增方法:根据乡镇名请求接口
|
|
|
+ async fetchTownshipDetailByName(townName) {
|
|
|
+ try {
|
|
|
+ // 调用对应乡镇的接口,假设接口地址为 /api/township-details/{townName}
|
|
|
+ const encodedTownName = encodeURIComponent(townName);
|
|
|
+ const response = await api8000.get(`/api/cd-prediction/crop-cd/statistics/town/${encodedTownName}`);
|
|
|
+ if (response.data && response.data.success) {
|
|
|
+ return response.data.data;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`获取${townName}详情失败:`, error);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 新增1:初始化乡镇边界地图(核心逻辑,仅加载边界)
|
|
|
+ async initTownshipMap() {
|
|
|
+ try {
|
|
|
+ this.loadingTownshipMap = true;
|
|
|
+ // 步骤1:加载本地 GeoJSON 边界文件
|
|
|
+ await this.loadLocalGeoJson();
|
|
|
+ // 步骤2:渲染边界地图(不关联接口数据)
|
|
|
+ this.renderTownshipMap();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('乡镇边界加载失败:', error);
|
|
|
+ this.townshipMapInstance = null; // 标记加载失败
|
|
|
+ } finally {
|
|
|
+ this.loadingTownshipMap = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 新增2:加载本地 GeoJSON 文件(关键:路径必须正确)
|
|
|
+ async loadLocalGeoJson() {
|
|
|
+ try {
|
|
|
+ // 1. 确认文件路径:public/data/韶关市乡镇划分图5.geojson
|
|
|
+ const geoJsonPath = '/data/韶关市乡镇划分图5.geojson';
|
|
|
+
|
|
|
+ // 2. 发送请求加载 GeoJSON
|
|
|
+ const response = await fetch(geoJsonPath);
|
|
|
+ // 3. 检查请求是否成功(状态码 200-299 为成功)
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error(`文件加载失败:状态码 ${response.status}(路径:${geoJsonPath})`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 解析 GeoJSON 数据
|
|
|
+ let originalGeoJson = await response.json();
|
|
|
+
|
|
|
+ // 关键修改:过滤只保留 FXZQMC 为"乐昌市"的特征数据
|
|
|
+ this.townshipGeoJson = {
|
|
|
+ ...originalGeoJson, // 保留原有属性(如 type、crs 等)
|
|
|
+ features: originalGeoJson.features// 过滤出乐昌市的乡镇
|
|
|
+ .filter(feature => feature.properties?.FXZQMC === '乐昌市')
|
|
|
+ // 为每个乡镇添加name字段,值为TXZQMC(ECharts默认读取name字段)
|
|
|
+ .map(feature => ({
|
|
|
+ ...feature,
|
|
|
+ properties: {
|
|
|
+ ...feature.properties,
|
|
|
+ name: feature.properties?.TXZQMC || '未知乡镇' // 核心:映射TXZQMC到name
|
|
|
+ }
|
|
|
+ }))
|
|
|
+ };
|
|
|
+
|
|
|
+ // 5. 验证 GeoJSON 格式(必须包含 features 数组,否则是无效格式)
|
|
|
+ if (!this.townshipGeoJson.features || !Array.isArray(this.townshipGeoJson.features)) {
|
|
|
+ throw new Error('GeoJSON 格式错误:缺少 features 数组');
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('GeoJSON 加载成功,包含乡镇数量:', this.townshipGeoJson.features.length);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('GeoJSON 加载异常:', error);
|
|
|
+ throw error; // 抛出错误,让 initTownshipMap 捕获
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 新增3:渲染乡镇边界(仅显示边界和乡镇名,不关联数据)
|
|
|
+ renderTownshipMap() {
|
|
|
+ // 1. 获取地图容器 DOM(必须存在)
|
|
|
+ const mapContainer = this.$refs.townshipMapRef;
|
|
|
+ if (!mapContainer) {
|
|
|
+ throw new Error('ECharts 容器不存在:请检查 ref="townshipMapRef" 是否正确');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 初始化 ECharts 实例
|
|
|
+ this.townshipMapInstance = echarts.init(mapContainer);
|
|
|
+
|
|
|
+ // 3. 注册地图:将 GeoJSON 数据注册到 ECharts(名称用 countyName:乐昌市)
|
|
|
+ echarts.registerMap(this.countyName, this.townshipGeoJson);
|
|
|
+
|
|
|
+ // 4. ECharts 配置项(仅显示边界和乡镇名,无数据关联)
|
|
|
+ const option = {
|
|
|
+ // 标题(可选,显示在地图上方)
|
|
|
+ title: {
|
|
|
+ text: '乐昌市乡镇边界',
|
|
|
+ left: 'center',
|
|
|
+ textStyle: { fontSize: 16, fontWeight: 'bold' }
|
|
|
+ },
|
|
|
+ // 提示框(鼠标悬浮时显示乡镇名)
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item', // 按乡镇区域触发
|
|
|
+ formatter: () => {
|
|
|
+ if (this.isTooltipLoading) {
|
|
|
+ return '<div style="padding: 5px;">加载中...</div>';
|
|
|
+ } else if (this.currentTooltipData) {
|
|
|
+ const detail = this.currentTooltipData;
|
|
|
+ let content = `
|
|
|
+ <div class="town-tooltip">
|
|
|
+ <h3 style="margin: 0 0 5px; color: #0066CC; text-align : center;">${this.currentTooltipTown}</h3>
|
|
|
+ <div style="height: 1px; background-color: #0066CC; margin-bottom: 8px;"></div>
|
|
|
+ <p>采样点数量: ${detail.基础统计.采样点数量}</p>
|
|
|
+ <p>平均值: ${detail.基础统计.平均值.toFixed(4)} mg/kg</p>
|
|
|
+ <p>最小值: ${detail.基础统计.最小值.toFixed(4)} mg/kg</p>
|
|
|
+ <p>最大值: ${detail.基础统计.最大值.toFixed(4)} mg/kg</p>
|
|
|
+ <p>数据更新时间: ${new Date(detail.数据更新时间).toLocaleString()}</p>
|
|
|
+ <div style="height: 1px; background-color: #0066CC; margin-bottom: 8px;"></div>
|
|
|
+ <p style="color:black; font-size:16px;">分布统计:</p>
|
|
|
+ <p>安全区间占比: ${detail.分布统计表格.汇总.安全区间占比}</p>
|
|
|
+ <p>预警区间占比: ${detail.分布统计表格.汇总.预警区间占比}</p>
|
|
|
+ <p>超标区间占比: ${detail.分布统计表格.汇总.超标区间占比}</p>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ return content;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ },
|
|
|
+ // 地图系列(核心:渲染边界)
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: 'map',
|
|
|
+ map: this.countyName, // 对应注册的地图名称(乐昌市)
|
|
|
+ roam: true, // 允许鼠标缩放、平移地图(方便查看)
|
|
|
+ label: {
|
|
|
+ show: true, // 显示乡镇名称标签
|
|
|
+ fontSize: 10, // 标签字体大小(避免重叠)
|
|
|
+ color: '#333' // 标签颜色
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ color: 'transparent', // 乡镇区域填充色(透明,仅显示边界)
|
|
|
+ borderColor: '#000000', // 边界颜色(青色,醒目)
|
|
|
+ borderWidth: 1.5 // 边界宽度
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ // 鼠标悬浮时的样式(高亮边界和标签)
|
|
|
+ label: { color: '#fff', fontSize: 11 },
|
|
|
+ itemStyle: { color: 'rgba(71, 195, 185, 0.3)' } // 悬浮区域填充色
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+
|
|
|
+ this.townshipMapInstance.setOption(option);
|
|
|
+ // 监听鼠标悬浮事件
|
|
|
+ this.townshipMapInstance.on('mouseover', async (params) => {
|
|
|
+ if (params.componentType === 'series' && params.seriesType === 'map') {
|
|
|
+ const townName = params.name;
|
|
|
+ this.currentTooltipTown = townName;
|
|
|
+ this.isTooltipLoading = true;
|
|
|
+ this.currentTooltipData = null;
|
|
|
+ // 手动触发 tooltip 更新
|
|
|
+ this.townshipMapInstance.setOption({ tooltip: {} });
|
|
|
+ const detail = await this.fetchTownshipDetailByName(townName);
|
|
|
+ this.isTooltipLoading = false;
|
|
|
+ this.currentTooltipData = detail;
|
|
|
+ // 再次手动触发 tooltip 更新,显示获取到的数据
|
|
|
+ this.townshipMapInstance.setOption({ tooltip: {} });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ // 监听鼠标移出事件,重置状态
|
|
|
+ this.townshipMapInstance.on('mouseout', () => {
|
|
|
+ this.currentTooltipTown = '';
|
|
|
+ this.currentTooltipData = null;
|
|
|
+ this.isTooltipLoading = false;
|
|
|
+ });
|
|
|
+ // 5. 渲染地图
|
|
|
+ this.townshipMapInstance.setOption(option);
|
|
|
+
|
|
|
+ // 6. 监听窗口 resize(地图自适应)
|
|
|
+ window.addEventListener('resize', () => {
|
|
|
+ this.townshipMapInstance && this.townshipMapInstance.resize();
|
|
|
+ });
|
|
|
+ },
|
|
|
|
|
|
// 上传并计算
|
|
|
async calculate() {
|
|
|
@@ -353,6 +567,7 @@ export default {
|
|
|
await this.fetchStatistics();
|
|
|
|
|
|
this.$message.success('计算完成!');
|
|
|
+ await this.initTownshipMap();
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('计算失败:', error);
|
|
|
@@ -409,6 +624,7 @@ export default {
|
|
|
await this.fetchStatistics();
|
|
|
|
|
|
this.$message.success('数据库计算完成!');
|
|
|
+ await this.initTownshipMap();
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('从数据库计算失败:', error);
|
|
|
@@ -504,6 +720,28 @@ export default {
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
+/* 新增:乡镇地图样式 */
|
|
|
+.township-map-section {
|
|
|
+ background-color: rgba(255, 255, 255, 0.8);
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 15px;
|
|
|
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
|
+ position: relative;
|
|
|
+ min-height: 500px; /* 与原有地图高度一致 */
|
|
|
+ backdrop-filter: blur(5px);
|
|
|
+ margin-bottom: 20px; /* 与下方地图间距 */
|
|
|
+}
|
|
|
+
|
|
|
+.township-map-container {
|
|
|
+ width: 1000px;
|
|
|
+ height: 500px; /* 减去标题高度 */
|
|
|
+ min-height: 470px;
|
|
|
+ border-radius: 4px;
|
|
|
+ background-color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
.toolbar {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|