|
|
@@ -1,8 +1,7 @@
|
|
|
<template>
|
|
|
<div class="container">
|
|
|
<!-- 顶部操作栏 -->
|
|
|
- <div class="toolbar">
|
|
|
- <!-- 文件上传区域 -->
|
|
|
+ <!-- <div class="toolbar">
|
|
|
<div class="upload-section">
|
|
|
<input type="file" ref="fileInput" accept=".csv" @change="handleFileUpload" style="display: none">
|
|
|
<el-button class="custom-button" @click="triggerFileUpload">
|
|
|
@@ -19,7 +18,6 @@
|
|
|
<el-icon class="upload-icon"><Document /></el-icon>
|
|
|
上传并计算
|
|
|
</el-button>
|
|
|
- <!-- 添加从数据库计算按钮 -->
|
|
|
<el-button
|
|
|
class="custom-button"
|
|
|
:loading="isCalculatingFromDB"
|
|
|
@@ -28,7 +26,7 @@
|
|
|
<el-icon class="upload-icon"><Box /></el-icon>
|
|
|
从数据库计算
|
|
|
</el-button>
|
|
|
- </div>
|
|
|
+ </div> -->
|
|
|
<!-- 操作按钮 -->
|
|
|
<!-- <div class="action-buttons">
|
|
|
<el-button class="custom-button" :disabled="!mapBlob" @click="exportMap">
|
|
|
@@ -41,28 +39,14 @@
|
|
|
<el-icon class="upload-icon"><Download /></el-icon>
|
|
|
导出数据</el-button>
|
|
|
</div> -->
|
|
|
- </div>
|
|
|
+ <!-- </div> -->
|
|
|
|
|
|
<!-- 主体内容区 -->
|
|
|
<div class="content-area">
|
|
|
<!-- 地图区域 - 单独一行 -->
|
|
|
<div class="map-section">
|
|
|
<h3>水稻Cd预测地图</h3>
|
|
|
- <div v-if="loadingTownshipMap" class="loading-container">
|
|
|
- <el-icon class="loading-icon"><Loading /></el-icon>
|
|
|
- <span>乡镇地图加载中...</span>
|
|
|
- </div>
|
|
|
- <div class="township-map-wrapper">
|
|
|
- <!-- Leaflet地图容器 -->
|
|
|
- <div class="town-map-title">水稻Cd含量预测镇域统计分布图</div>
|
|
|
- <div
|
|
|
- v-show="!loadingTownshipMap"
|
|
|
- ref="townshipMapRef"
|
|
|
- class="township-map-container"
|
|
|
- ></div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 颜色图例 -->
|
|
|
+ <!-- 颜色图例 -->
|
|
|
<div class="legend">
|
|
|
<div class="legend-item">
|
|
|
<div class="color-box" style="background-color: #15b392;"></div>
|
|
|
@@ -77,6 +61,43 @@
|
|
|
<span>超标区间 (≥0.3 mg/kg)</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ <div v-if="loadingTownshipMap" class="loading-container">
|
|
|
+ <el-icon class="loading-icon"><Loading /></el-icon>
|
|
|
+ <span>乡镇地图加载中...</span>
|
|
|
+ </div>
|
|
|
+ <div class="township-map-wrapper">
|
|
|
+ <!-- Leaflet地图容器 -->
|
|
|
+ <div class="town-map-title">水稻Cd含量预测镇域统计分布图</div>
|
|
|
+ <div
|
|
|
+ v-show="!loadingTownshipMap"
|
|
|
+ ref="townshipMapRef"
|
|
|
+ class="township-map-container"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+ <!-- 新增:要素详细信息栏 -->
|
|
|
+ <div v-if="currentFeatureInfo" class="feature-info-panel">
|
|
|
+ <h4 style="margin-top: 0; color: #0066CC; border-bottom: 1px solid #eee; padding-bottom: 10px;">
|
|
|
+ 地块详细信息
|
|
|
+ </h4>
|
|
|
+ <ul style="list-style: none; padding: 0; margin: 0;">
|
|
|
+ <li style="margin-bottom: 8px; font-size: 14px;">
|
|
|
+ <strong>水稻镉含量:</strong>{{ currentFeatureInfo.avgmean.toFixed(4) }} mg/kg
|
|
|
+ </li>
|
|
|
+ <li style="margin-bottom: 8px; font-size: 14px;">
|
|
|
+ <strong>镉输入通量:</strong>{{ currentFeatureInfo.lncd.toFixed(4) }} g/ha/a
|
|
|
+ <!-- 若有单位可补充:{{ currentFeatureInfo.lncdUnit }} -->
|
|
|
+ </li>
|
|
|
+ <li style="margin-bottom: 8px; font-size: 14px;">
|
|
|
+ <strong>镉输出通量:</strong>{{ currentFeatureInfo.outcd.toFixed(4) }} g/ha/a
|
|
|
+ </li>
|
|
|
+ <li style="margin-bottom: 8px; font-size: 14px;">
|
|
|
+ <strong>镉净通量:</strong>{{ currentFeatureInfo.netcd.toFixed(4) }} g/ha/a
|
|
|
+ </li>
|
|
|
+ <li style="margin-bottom: 8px; font-size: 14px;">
|
|
|
+ <strong>当年镉浓度:</strong>{{ currentFeatureInfo.endcd.toFixed(4) }} g/ha/a
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
<div v-if="!loadingTownshipMap && !townshipMapInstance" class="no-data">
|
|
|
<el-icon><Location /></el-icon>
|
|
|
<p>暂无乡镇边界数据</p>
|
|
|
@@ -188,6 +209,7 @@ export default {
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
+ currentFeatureInfo: null, // 存储当前点击要素的详细信息
|
|
|
mapContainerStyle: {
|
|
|
width: '100%',
|
|
|
height: '800px'
|
|
|
@@ -255,6 +277,7 @@ export default {
|
|
|
// 组件销毁前移除事件监听,避免内存泄漏
|
|
|
window.removeEventListener('resize', this.handleResize);
|
|
|
if (this.townshipMapInstance) {
|
|
|
+ this.townshipMapInstance.off('click', this.handleWmsFeatureClick);
|
|
|
this.townshipMapInstance.remove();
|
|
|
this.townshipMapInstance = null;
|
|
|
}
|
|
|
@@ -724,8 +747,8 @@ renderTownshipMap() {
|
|
|
}).addTo(this.townshipMapInstance);
|
|
|
|
|
|
// 添加GeoServer WMS图层作为底图
|
|
|
- const wmsLayer = L.tileLayer.wms('https://www.soilgd.com/geoserver/soilgd/wms', {
|
|
|
- layers: 'soilgd:farmland_data', // 替换为你的图层名称
|
|
|
+ const wmsLayer = L.tileLayer.wms('http://localhost:8080/geoserver/soilgd/wms', {
|
|
|
+ layers: 'soilgd:shapeMap', // 替换为你的图层名称
|
|
|
format: 'image/png',
|
|
|
transparent: true,
|
|
|
version: '1.1.0',
|
|
|
@@ -749,6 +772,7 @@ renderTownshipMap() {
|
|
|
}
|
|
|
|
|
|
console.log('地图渲染完成');
|
|
|
+ this.townshipMapInstance.on('click', this.handleWmsFeatureClick);
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('渲染地图时发生错误:', error);
|
|
|
@@ -757,6 +781,74 @@ renderTownshipMap() {
|
|
|
},
|
|
|
|
|
|
|
|
|
+async handleWmsFeatureClick(e) {
|
|
|
+ try {
|
|
|
+ const { lat, lng } = e.latlng;
|
|
|
+ const mapSize = this.townshipMapInstance.getSize();
|
|
|
+
|
|
|
+ // 1. 转换坐标:地理坐标 → 地图像素坐标(Leaflet 默认 EPSG:4326)
|
|
|
+ const containerPoint = this.townshipMapInstance.latLngToContainerPoint([lat, lng]);
|
|
|
+ const x = Math.round(containerPoint.x);
|
|
|
+ const y = Math.round(containerPoint.y);
|
|
|
+
|
|
|
+ // 2. 构造 GetFeatureInfo 请求 URL(强制 JSON + 修正参数)
|
|
|
+ const wmsBaseUrl = 'http://localhost:8080/geoserver/soilgd/wms'; // 确保工作区和服务名正确
|
|
|
+ const params = new URLSearchParams({
|
|
|
+ SERVICE: 'WMS',
|
|
|
+ VERSION: '1.1.1', // 建议用 1.1.1(GeoServer 更兼容)
|
|
|
+ REQUEST: 'GetFeatureInfo',
|
|
|
+ LAYERS: 'soilgd:shapeMap',
|
|
|
+ QUERY_LAYERS: 'soilgd:shapeMap',
|
|
|
+ FORMAT: 'image/png',
|
|
|
+ TRANSPARENT: 'true',
|
|
|
+ INFO_FORMAT: 'application/json', // 强制返回 JSON
|
|
|
+ bbox: this.townshipMapInstance.getBounds().toBBoxString(),
|
|
|
+ X: x,
|
|
|
+ Y: y,
|
|
|
+ WIDTH: mapSize.x,
|
|
|
+ HEIGHT: mapSize.y,
|
|
|
+ SRS: 'EPSG:4326', // 与地图 CRS 一致
|
|
|
+ FEATURE_COUNT: 1 // 限制返回 1 个要素(关键:确保日志中 FeatureCount=1)
|
|
|
+ });
|
|
|
+
|
|
|
+ // 打印完整请求 URL(用于调试)
|
|
|
+ const requestUrl = `${wmsBaseUrl}?${params.toString()}`;
|
|
|
+ console.log('GetFeatureInfo 完整请求 URL:', requestUrl);
|
|
|
+
|
|
|
+ // 3. 发送请求并解析 JSON
|
|
|
+ const response = await fetch(requestUrl);
|
|
|
+ if (!response.ok) throw new Error(`请求失败:${response.status} ${response.statusText}`);
|
|
|
+
|
|
|
+ // 4. 验证响应类型(必须为 JSON)
|
|
|
+ const contentType = response.headers.get('Content-Type');
|
|
|
+ if (!contentType.includes('application/json')) {
|
|
|
+ throw new Error(`非 JSON 响应:${contentType},请检查 GeoServer 配置`);
|
|
|
+ }
|
|
|
+ const featureData = await response.json();
|
|
|
+
|
|
|
+ if (!featureData?.features || !Array.isArray(featureData.features) || featureData.features.length === 0) {
|
|
|
+ this.$message.warning('该区域无要素数据');
|
|
|
+ return; // 无要素时直接返回,不继续处理
|
|
|
+ }
|
|
|
+
|
|
|
+ const properties = featureData.features[0].properties || {};
|
|
|
+ this.currentFeatureInfo = {
|
|
|
+ avgmean: properties._avgmean, // 平均镉含量
|
|
|
+ lncd: properties.Ln_Cd, // 输入镉通量
|
|
|
+ outcd: properties.Out_Cd, // 输出镉通量
|
|
|
+ netcd: properties.Net_Cd, // 净镉通量
|
|
|
+ endcd: properties.End_Cd // 最终镉浓度
|
|
|
+ };
|
|
|
+
|
|
|
+ // 打印验证(可选)
|
|
|
+ console.log('当前点击要素信息:', this.currentFeatureInfo);
|
|
|
+ console.log('WMS 要素点击信息(JSON):', featureData);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('WMS 要素点击交互失败:', error);
|
|
|
+ this.$message.warning('获取要素信息失败:' + error.message);
|
|
|
+ }
|
|
|
+},
|
|
|
+
|
|
|
// 新增:显示tooltip的方法
|
|
|
showTooltip(layer, townName, latlng) {
|
|
|
const riceData = this.dataMap[townName]; // 水稻Cd数据
|
|
|
@@ -1085,7 +1177,7 @@ renderTownshipMap() {
|
|
|
.township-map-container {
|
|
|
width: 100% !important;
|
|
|
max-width: 1000px;
|
|
|
- height: 800px !important;
|
|
|
+ height: 500px !important;
|
|
|
border-radius: 4px;
|
|
|
background-color: #f5f5f5;
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
@@ -1299,4 +1391,61 @@ renderTownshipMap() {
|
|
|
height: 500px !important;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+/* 地图容器包裹层:确保内部元素垂直排列 */
|
|
|
+.township-map-wrapper {
|
|
|
+ position: relative;
|
|
|
+ width: 90%;
|
|
|
+ max-width: 1000px;
|
|
|
+ margin: 15px auto;
|
|
|
+ border: 1px solid #e0e0e0;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: white;
|
|
|
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
|
+ overflow: hidden;
|
|
|
+ display: flex; /* 新增:包裹层使用 Flex 布局 */
|
|
|
+ flex-direction: column; /* 子元素垂直排列 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 要素信息面板:水平平铺 */
|
|
|
+.feature-info-panel {
|
|
|
+ background-color: #f8f9fa;
|
|
|
+ border: 1px solid #e9ecef;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 15px;
|
|
|
+ margin-top: 15px; /* 与地图容器间距 */
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
|
+ width: 100%; /* 占满包裹层宽度 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 信息面板标题:占满宽度 */
|
|
|
+.feature-info-panel h4 {
|
|
|
+ width: 100%;
|
|
|
+ margin-bottom: 10px; /* 标题与内容间距 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 信息列表:水平排列 */
|
|
|
+.feature-info-panel ul {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap; /* 允许换行 */
|
|
|
+ gap: 15px; /* 项之间的间距 */
|
|
|
+ list-style: none;
|
|
|
+ padding: 0;
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 信息项:水平平铺 */
|
|
|
+.feature-info-panel li {
|
|
|
+ flex: 1; /* 每项占据等宽空间 */
|
|
|
+ min-width: 150px; /* 最小宽度,防止过窄 */
|
|
|
+ margin-bottom: 0; /* 移除垂直间距 */
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+/* 强调文字样式 */
|
|
|
+.feature-info-panel li strong {
|
|
|
+ color: #0066CC;
|
|
|
+ margin-right: 5px;
|
|
|
+}
|
|
|
</style>
|