Przeglądaj źródła

灌溉水输入通量地图展示

yangtaodemon 4 tygodni temu
rodzic
commit
39fb728520

+ 4 - 0
components.d.ts

@@ -22,8 +22,12 @@ declare module 'vue' {
     ElDropdown: typeof import('element-plus/es')['ElDropdown']
     ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
     ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
+    ElForm: typeof import('element-plus/es')['ElForm']
+    ElFormItem: typeof import('element-plus/es')['ElFormItem']
     ElHeader: typeof import('element-plus/es')['ElHeader']
     ElIcon: typeof import('element-plus/es')['ElIcon']
+    ElInput: typeof import('element-plus/es')['ElInput']
+    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
     ElMain: typeof import('element-plus/es')['ElMain']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']

+ 582 - 113
src/views/User/heavyMetalFluxCalculation/inputFluxCalculation/irrigationWater.vue

@@ -2,11 +2,159 @@
   <div class="page-container">
     <!-- 上半部分:地图 + 柱状图 -->
     <div class="top-content">
-      <div class="map-module">
-        <div class="map-box">地图模块</div>
+      <!-- 灌溉水输入通量计算模块 -->
+      <div class="irrigation-form">
+        <div class="form-header">
+          <h3>灌溉水输入通量计算</h3>
+        </div>
+        
+        <div class="form-body">
+          <div class="land-form-row">
+            <!-- 水田 -->
+            <div class="land-form">
+              <h4>水田</h4>
+              <el-form :model="irrigationData.paddy" label-position="top">
+                <el-form-item label="灌溉水用量 (吨/公顷)">
+                  <el-input-number 
+                    v-model="irrigationData.paddy.usage" 
+                    :min="0" 
+                    :step="100"
+                  ></el-input-number>
+                </el-form-item>
+                <el-form-item label="灌溉水有效利用率 (%)">
+                  <el-input-number 
+                    v-model="irrigationData.paddy.efficiency" 
+                    :min="0" 
+                    :max="100" 
+                    :step="5"
+                  ></el-input-number>
+                </el-form-item>
+                <el-form-item label="计算通量(吨/公顷·年)">
+                  <el-input 
+                    v-model="irrigationData.paddy.flux" 
+                    readonly
+                    class="flux-input"
+                  >
+                  </el-input>
+                </el-form-item>
+                <el-button 
+                  type="primary" 
+                  @click="calculateFlux('paddy')"
+                  class="calculate-btn"
+                >计算水田通量</el-button>
+              </el-form>
+            </div>
+            
+            <!-- 水浇地 -->
+            <div class="land-form">
+              <h4>水浇地</h4>
+              <el-form :model="irrigationData.irrigated" label-position="top">
+                <el-form-item label="灌溉水用量 (吨/公顷)">
+                  <el-input-number 
+                    v-model="irrigationData.irrigated.usage" 
+                    :min="0" 
+                    :step="100"
+                  ></el-input-number>
+                </el-form-item>
+                <el-form-item label="灌溉水有效利用率 (%)">
+                  <el-input-number 
+                    v-model="irrigationData.irrigated.efficiency" 
+                    :min="0" 
+                    :max="100" 
+                    :step="5"
+                  ></el-input-number>
+                </el-form-item>
+                <el-form-item label="计算通量(吨/公顷·年)">
+                  <el-input 
+                    v-model="irrigationData.irrigated.flux" 
+                    readonly
+                    class="flux-input"
+                  >
+                  </el-input>
+                </el-form-item>
+                <el-button 
+                  type="primary" 
+                  @click="calculateFlux('irrigated')"
+                  class="calculate-btn"
+                >计算水浇地通量</el-button>
+              </el-form>
+            </div>
+            
+            <!-- 旱地 -->
+            <div class="land-form">
+              <h4>旱地</h4>
+              <el-form :model="irrigationData.dryland" label-position="top">
+                <el-form-item label="灌溉水用量 (吨/公顷)">
+                  <el-input-number 
+                    v-model="irrigationData.dryland.usage" 
+                    :min="0" 
+                    :step="100"
+                  ></el-input-number>
+                </el-form-item>
+                <el-form-item label="灌溉水有效利用率 (%)">
+                  <el-input-number 
+                    v-model="irrigationData.dryland.efficiency" 
+                    :min="0" 
+                    :max="100" 
+                    :step="5"
+                  ></el-input-number>
+                </el-form-item>
+                <el-form-item label="计算通量(吨/公顷·年)">
+                  <el-input 
+                    v-model="irrigationData.dryland.flux" 
+                    readonly
+                    class="flux-input"
+                  >
+                  </el-input>
+                </el-form-item>
+                <el-button 
+                  type="primary" 
+                  @click="calculateFlux('dryland')"
+                  class="calculate-btn"
+                >计算旱地通量</el-button>
+              </el-form>
+            </div>
+          </div>
+        </div>
       </div>
-      <div class="chart-module">
-        <div ref="chartRef" class="chart-box"></div>
+      
+      <!-- 右侧图表区域 -->
+      <div class="graphics-container">
+        <!-- 下拉选择栏 -->
+        <div class="land-type-selector">
+          <el-select v-model="selectedLandType" placeholder="选择土地类型" @change="handleLandTypeChange">
+            <el-option label="水田" value="paddy"></el-option>
+            <el-option label="水浇地" value="irrigated"></el-option>
+            <el-option label="旱地" value="dryland"></el-option>
+          </el-select>
+        </div>
+        
+        <!-- 图表容器 -->
+        <div class="graphics-row">
+          <!-- 地图模块 -->
+          <div class="map-module">
+            <div v-if="rasterMapImage" class="map-box">
+              <img :src="rasterMapImage" alt="土地类型Cd分布图" class="raster-image">
+            </div>
+            <div v-else-if="mapLoading" class="map-box">地图加载中...</div>
+            <div v-else class="map-box error-message">
+              地图加载失败: {{ mapError }}
+              <el-button type="primary" size="small" @click="retryMap">重试</el-button>
+            </div>
+          </div>
+          
+          <!-- 图表模块 -->
+          <div class="chart-module">
+            <div v-if="histogramImage" class="chart-box">
+              <img :src="histogramImage" alt="数据分布直方图" class="histogram-image">
+            </div>
+            <div v-else-if="histogramLoading" class="chart-box">直方图加载中...</div>
+            <div v-else class="chart-box error-message">
+              直方图加载失败: {{ histogramError }}
+              <el-button type="primary" size="small" @click="retryHistogram">重试</el-button>
+            </div>
+          </div>
+        </div>
       </div>
     </div>
 
@@ -15,183 +163,504 @@
 
     <!-- 下半部分:表格 -->
     <div class="table-module">
+      <div class="table-header">
+        <h3>计算结果汇总</h3>
+        <el-button type="primary" @click="exportData">导出数据</el-button>
+      </div>
+      
       <el-table
         :data="tableData"
         style="width: 100%"
-        max-height="100%"
         border
         stripe
-        class="scrollable-table"
+        class="compact-table"
       >
-        <el-table-column prop="name" label="项目" width="150" />
-        <el-table-column prop="value" label="数值" width="150" />
-        <el-table-column prop="unit" label="单位" width="100" />
+        <el-table-column prop="name" label="土地类型" width="120" />
+        <el-table-column prop="area" label="面积 (公顷)" width="100" />
+        <el-table-column prop="quality" label="质量等级" width="100" />
+        <el-table-column prop="productivity" label="生产力指数" width="120" />
+        <el-table-column prop="waterUsage" label="灌溉水用量" width="120" />
+        <el-table-column prop="waterEfficiency" label="利用率" width="100" />
+        <el-table-column prop="waterFlux" label="通量" width="120" />
+        <el-table-column label="操作" width="80">
+          <template #default="{ row }">
+            <el-button type="primary" size="small" @click="editItem(row)">修改</el-button>
+          </template>
+        </el-table-column>
       </el-table>
     </div>
   </div>
 </template>
 
 <script setup>
-import { ref, onMounted, watch, computed } from 'vue';
-import * as echarts from 'echarts';
-
-const form = ref({
-  nutrients: 10,
-  heavyMetals: 5,
-  phValue: 7,
-  yieldQuality: 60,
-  pestRate: 3,
+import { ref, onMounted, reactive, watch } from 'vue';
+import axios from 'axios';
+
+// 配置API基础URL
+const BASE_URL = 'http://localhost:8000/api/water';
+
+// 创建自定义axios实例
+const http = axios.create({
+  timeout: 60000, // 60秒超时
 });
 
-const chartRef = ref(null);
-let chartInstance = null;
+// 土地类型数据映射
+const landTypeData = {
+  paddy: {
+    shp_base_name: 'lechang',
+    tif_type: '水田',
+    properties: {
+      area: 1200,
+      quality: '优',
+      productivity: 850
+    }
+  },
+  dryland: {
+    shp_base_name: 'lechang',
+    tif_type: '旱地',
+    properties: {
+      area: 800,
+      quality: '良',
+      productivity: 600
+    }
+  },
+  irrigated: {
+    shp_base_name: 'lechang',
+    tif_type: '水浇地',
+    properties: {
+      area: 950,
+      quality: '中',
+      productivity: 720
+    }
+  }
+};
 
-const initChart = () => {
-  if (!chartRef.value) return;
-  chartInstance = echarts.init(chartRef.value);
+// 灌溉水计算数据
+const irrigationData = reactive({
+  paddy: {
+    usage: 8000,
+    efficiency: 60,
+    flux: 4800
+  },
+  irrigated: {
+    usage: 6000,
+    efficiency: 70,
+    flux: 4200
+  },
+  dryland: {
+    usage: 4000,
+    efficiency: 50,
+    flux: 2000
+  }
+});
 
-  const option = {
-    title: {
-      text: '传统耕种风险比例',
-      left: 'center',
-    },
-    tooltip: {
-      trigger: 'item',
+// 响应式数据
+const selectedLandType = ref('paddy');
+const rasterMapImage = ref(null);
+const histogramImage = ref(null);
+const tableData = ref([]);
+
+// 加载状态和错误信息
+const mapLoading = ref(false);
+const histogramLoading = ref(false);
+const mapError = ref('');
+const histogramError = ref('');
+
+// 初始化数据和表格
+const initTableData = () => {
+  tableData.value = [
+    {
+      id: 1,
+      name: '水田',
+      ...landTypeData.paddy.properties,
+      waterUsage: irrigationData.paddy.usage,
+      waterEfficiency: irrigationData.paddy.efficiency + '%',
+      waterFlux: irrigationData.paddy.flux
     },
-    legend: {
-      orient: 'vertical',
-      left: 'left',
+    {
+      id: 2,
+      name: '水浇地',
+      ...landTypeData.irrigated.properties,
+      waterUsage: irrigationData.irrigated.usage,
+      waterEfficiency: irrigationData.irrigated.efficiency + '%',
+      waterFlux: irrigationData.irrigated.flux
     },
-    series: [
-      {
-        name: '风险因素',
-        type: 'pie',
-        radius: '50%',
-        data: [
-          { value: form.value.nutrients, name: '养分含量' },
-          { value: form.value.heavyMetals, name: '重金属污染' },
-          { value: form.value.phValue, name: 'pH 值' },
-          { value: form.value.yieldQuality, name: '产量或品质' },
-          { value: form.value.pestRate, name: '病虫害率' },
-        ],
-        emphasis: {
-          itemStyle: {
-            shadowBlur: 10,
-            shadowOffsetX: 0,
-            shadowColor: 'rgba(0,0,0,0.5)',
-          },
-        },
-      },
-    ],
-  };
-  chartInstance.setOption(option);
+    {
+      id: 3,
+      name: '旱地',
+      ...landTypeData.dryland.properties,
+      waterUsage: irrigationData.dryland.usage,
+      waterEfficiency: irrigationData.dryland.efficiency + '%',
+      waterFlux: irrigationData.dryland.flux
+    }
+  ];
 };
 
-const updateChart = () => {
-  if (!chartInstance) return;
-  chartInstance.setOption({
-    series: [
-      {
-        data: [
-          { value: form.value.nutrients, name: '养分含量' },
-          { value: form.value.heavyMetals, name: '重金属污染' },
-          { value: form.value.phValue, name: 'pH 值' },
-          { value: form.value.yieldQuality, name: '产量或品质' },
-          { value: form.value.pestRate, name: '病虫害率' },
-        ],
-      },
-    ],
-  });
+// 计算灌溉水通量
+const calculateFlux = (landType) => {
+  const data = irrigationData[landType];
+  data.flux = Math.round(data.usage * data.efficiency / 100);
+  
+  // 更新表格数据
+  const row = tableData.value.find(item => 
+    landType === 'paddy' ? item.name === '水田' : 
+    landType === 'irrigated' ? item.name === '水浇地' : 
+    item.name === '旱地'
+  );
+  
+  if (row) {
+    row.waterUsage = data.usage;
+    row.waterEfficiency = data.efficiency + '%';
+    row.waterFlux = data.flux;
+  }
+};
+
+// 导出数据功能
+const exportData = () => {
+  alert('导出数据功能已触发,数据已准备好下载');
+  // 实际应用中这里会生成并下载CSV文件
+};
+
+// 修改条目功能
+const editItem = (row) => {
+  alert(`正在修改 ${row.name} 的数据`);
+  // 实际应用中这里会打开编辑模态框
 };
 
 onMounted(() => {
-  initChart();
+  initTableData();
+  handleLandTypeChange();
 });
 
-watch(form, () => {
-  updateChart();
+// 监听灌溉数据变化更新表格
+watch(irrigationData, (newVal) => {
+  tableData.value = tableData.value.map(item => {
+    if (item.name === '水田') {
+      return {...item, 
+        waterUsage: newVal.paddy.usage, 
+        waterEfficiency: newVal.paddy.efficiency + '%', 
+        waterFlux: newVal.paddy.flux};
+    } else if (item.name === '水浇地') {
+      return {...item, 
+        waterUsage: newVal.irrigated.usage, 
+        waterEfficiency: newVal.irrigated.efficiency + '%', 
+        waterFlux: newVal.irrigated.flux};
+    } else {
+      return {...item, 
+        waterUsage: newVal.dryland.usage, 
+        waterEfficiency: newVal.dryland.efficiency + '%', 
+        waterFlux: newVal.dryland.flux};
+    }
+  });
 }, { deep: true });
 
-const tableData = computed(() => [
-  { name: '养分含量', value: form.value.nutrients, unit: '%' },
-  { name: '重金属污染', value: form.value.heavyMetals, unit: '%' },
-  { name: 'pH 值', value: form.value.phValue, unit: '' },
-  { name: '产量或品质', value: form.value.yieldQuality, unit: '%' },
-  { name: '病虫害率', value: form.value.pestRate, unit: '%' },
-]);
+const handleLandTypeChange = async () => {
+  const landType = selectedLandType.value;
+  const data = landTypeData[landType];
+  
+  // 重置状态
+  rasterMapImage.value = null;
+  histogramImage.value = null;
+  mapError.value = '';
+  histogramError.value = '';
+  
+  try {
+    // 设置加载状态
+    mapLoading.value = true;
+    histogramLoading.value = true;
+    
+    // 顺序请求,避免同时处理大文件导致问题
+    await generateRasterMap(data.shp_base_name, data.tif_type);
+    await generateHistogram(data.tif_type);
+  } catch (error) {
+    console.error('数据加载失败:', error);
+  } finally {
+    // 重置加载状态
+    mapLoading.value = false;
+    histogramLoading.value = false;
+  }
+};
+
+const generateRasterMap = async (shp_base_name, tif_type) => {
+  try {
+    const formData = new FormData();
+    formData.append('shp_base_name', shp_base_name);
+    formData.append('tif_type', tif_type);
+    formData.append('color_map_name', 'colormap6');
+    formData.append('title_name', `${getLandTypeName(selectedLandType.value)}Cd分布图`);
+    formData.append('output_size', '8'); // 减小输出尺寸
+    
+    const response = await http.post(`${BASE_URL}/generate-raster-map`, formData, {
+      headers: { 'Content-Type': 'multipart/form-data' },
+      responseType: 'blob'
+    });
+    
+    if (response.data.size === 0) {
+      throw new Error('后端返回空响应');
+    }
+    
+    const blob = new Blob([response.data], { type: 'image/jpeg' });
+    rasterMapImage.value = URL.createObjectURL(blob);
+    mapError.value = '';
+  } catch (error) {
+    console.error('栅格地图生成失败:', error);
+    mapError.value = error.response?.data?.message || error.message || '未知错误';
+    throw error;
+  }
+};
+
+const generateHistogram = async (tif_type) => {
+  try {
+    const formData = new FormData();
+    formData.append('tif_type', tif_type);
+    formData.append('figsize_width', '8'); // 减小宽度
+    formData.append('figsize_height', '6'); // 减小高度
+    formData.append('xlabel', '像元值');
+    formData.append('ylabel', '频率密度');
+    formData.append('title', `${getLandTypeName(selectedLandType.value)}数据分布`);
+    
+    const response = await http.post(`${BASE_URL}/generate-tif-histogram`, formData, {
+      headers: { 'Content-Type': 'multipart/form-data' },
+      responseType: 'blob'
+    });
+    
+    if (response.data.size === 0) {
+      throw new Error('后端返回空响应');
+    }
+    
+    const blob = new Blob([response.data], { type: 'image/jpeg' });
+    histogramImage.value = URL.createObjectURL(blob);
+    histogramError.value = '';
+  } catch (error) {
+    console.error('直方图生成失败:', error);
+    histogramError.value = error.response?.data?.message || error.message || '未知错误';
+    throw error;
+  }
+};
+
+const getLandTypeName = (value) => {
+  switch (value) {
+    case 'paddy': return '水田';
+    case 'dryland': return '旱地';
+    case 'irrigated': return '水浇地';
+    default: return '';
+  }
+};
+
+// 重试功能
+const retryMap = async () => {
+  const landType = selectedLandType.value;
+  const data = landTypeData[landType];
+  
+  mapLoading.value = true;
+  mapError.value = '';
+  
+  try {
+    await generateRasterMap(data.shp_base_name, data.tif_type);
+  } catch (error) {
+    console.error('重试栅格地图失败:', error);
+  } finally {
+    mapLoading.value = false;
+  }
+};
+
+const retryHistogram = async () => {
+  const landType = selectedLandType.value;
+  const data = landTypeData[landType];
+  
+  histogramLoading.value = true;
+  histogramError.value = '';
+  
+  try {
+    await generateHistogram(data.tif_type);
+  } catch (error) {
+    console.error('重试直方图失败:', error);
+  } finally {
+    histogramLoading.value = false;
+  }
+};
 </script>
 
 <style scoped>
 .page-container {
   display: flex;
   flex-direction: column;
-  height: 100vh; /* 整屏高度 */
+  height: 100vh;
   padding: 20px;
   box-sizing: border-box;
   background-color: #f5f7fa;
 }
 
-/* 上半部分占47%高度 */
 .top-content {
   display: flex;
-  height: 47%;
-  gap: 20px;
-}
-
-/* 中间间距 */
-.middle-gap {
-  height: 10px;
+  height: 45%;
+  gap: 20px; /* 增加间距 */
 }
 
-/* 下半部分表格占50%高度 */
-.table-module {
-  height: 50%;
+.irrigation-form {
+  flex: 0 0 45%;
   background: white;
   border-radius: 12px;
-  box-shadow: 0 0 10px rgb(0 0 0 / 0.1);
-  overflow: auto;
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+  display: flex;
+  flex-direction: column;
+}
+
+.form-header {
+  background-color: #2c3e50;
+  color: white;
+  padding: 10px 15px;
+  text-align: center;
+  font-size: 16px;
+  font-weight: bold;
+}
+
+.form-body {
+  padding: 15px;
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+}
+
+.land-form-row {
+  display: flex;
+  gap: 20px; /* 增加间距 */
+  height: 100%;
+}
+
+.land-form {
+  flex: 1;
+  border: 1px solid #e1e4e8;
+  border-radius: 8px;
+  padding: 15px; /* 增加内边距 */
+  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
+}
+
+.land-form h4 {
+  margin-top: 0;
+  margin-bottom: 15px; /* 增加下边距 */
+  color: #1a73e8;
+  border-bottom: 1px solid #e1e4e8;
+  padding-bottom: 10px; /* 增加下边距 */
+  font-size: 14px;
+}
+
+/* 修复通量数字显示不全 */
+.flux-input {
+  width: 100%; /* 确保输入框宽度填满 */
+}
+
+.flux-input >>> .el-input__inner {
+  text-align: left; /* 数字右对齐 */
+  padding-right: 10px; /* 增加右边距 */
+  font-weight: bold; /* 加粗显示 */
+}
+
+.calculate-btn {
+  margin-top: 15px; /* 增加按钮上边距 */
 }
 
-.map-module {
+.graphics-container {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 15px; /* 增加间距 */
+}
+
+.land-type-selector {
+  margin-bottom: 15px; /* 增加下边距 */
+}
+
+.graphics-row {
+  display: flex;
+  gap: 15px; /* 增加间距 */
+  height: calc(100% - 45px); /* 减去选择器高度 */
+}
+
+.map-module, .chart-module {
   flex: 1;
   background: white;
-  border-radius: 12px;
-  box-shadow: 0 0 10px rgb(0 0 0 / 0.1);
+  border-radius: 8px;
+  box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
   display: flex;
   justify-content: center;
   align-items: center;
+  overflow: hidden;
+  height: 100%; /* 固定高度 */
 }
 
-.map-box {
+.map-box, .chart-box {
   width: 100%;
   height: 100%;
-  border: 2px dashed #bbb;
-  border-radius: 12px;
   display: flex;
   justify-content: center;
   align-items: center;
-  font-weight: bold;
-  color: #999;
+  position: relative;
+  padding: 10px; /* 增加内边距 */
 }
 
-.chart-module {
-  flex: 1;
+.raster-image, .histogram-image {
+  width: 100%; /* 宽度固定为容器宽度 */
+  height: 100%; /* 高度固定为容器高度 */
+  object-fit: contain; /* 保持比例 */
+}
+
+.error-message {
+  color: #f56c6c;
+  font-size: 13px;
+  padding: 10px; /* 增加内边距 */
+  text-align: center;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 10px; /* 增加间距 */
+}
+
+.middle-gap {
+  height: 20px; /* 增加中间间距高度 */
+}
+
+.table-module {
+  height: 50%;
   background: white;
   border-radius: 12px;
-  box-shadow: 0 0 10px rgb(0 0 0 / 0.1);
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
   display: flex;
-  justify-content: center;
+  flex-direction: column;
+  margin-top: 50px; /* 增加上边距 */
+}
+
+.table-header {
+  display: flex;
+  justify-content: space-between;
   align-items: center;
+  padding: 15px 20px; /* 增加内边距 */
+  background-color: #f8f9fa;
+  border-bottom: 1px solid #e1e4e8;
 }
 
-.chart-box {
-  width: 100%;
-  height: 100%;
-  min-height: 100%;
+.table-header h3 {
+  margin: 0;
+  font-size: 16px; /* 增加字体大小 */
+  color: #2c3e50;
 }
 
-.scrollable-table {
+.compact-table {
   width: 100%;
   height: 100%;
+  font-size: 13px;
+}
+
+.compact-table >>> .el-table__cell {
+  padding: 10px 0; /* 增加单元格内边距 */
+}
+
+/* 修复表格中数字显示 */
+.compact-table >>> .el-table__cell .cell {
+  padding: 0 10px; /* 增加单元格内边距 */
+  text-align: center; /* 居中显示 */
+  white-space: nowrap; /* 防止换行 */
+  overflow: hidden; /* 隐藏溢出 */
+  text-overflow: ellipsis; /* 显示省略号 */
 }
-</style>
+</style>