yes-yes-yes-k 4 dní pred
rodič
commit
35742d24b5
1 zmenil súbory, kde vykonal 323 pridanie a 89 odobranie
  1. 323 89
      src/views/User/acidModel/acidmodelmap.vue

+ 323 - 89
src/views/User/acidModel/acidmodelmap.vue

@@ -136,55 +136,77 @@
   </div>
           </div>
 
-          <!-- 反酸预测参数输入 -->
-<div v-if="showAcidInversionInput" class="acid-inversion-input">
+         <!-- 反酸预测参数输入 -->
+<div v-if="showAcidInversionInput" class="acid-inversion-input" style="width: 100%; padding: 8px 0;">
   <div class="section-title">反酸预测参数</div>
   <el-form 
     :model="acidInversionParams" 
-    :rules="acidInversionRules" 
+    :rules="currentAcidInversionRules"  
     ref="acidInversionFormRef"  
     label-width="80px" 
     size="small"
+    class="inversion-form" 
   >
-    <!-- 当前pH单独一行 -->
-    <div class="params-row full-width">
-      <el-form-item label="当前pH" class="form-item-compact readonly-item">
-        <div class="current-ph-value">
-          <span v-if="phLoading" class="loading-text">加载中...</span>
-          <span v-else-if="currentPH !== null">{{ currentPH.toFixed(2) }}</span>
-          <span v-else class="no-data-text">未获取</span>
-        </div>
-      </el-form-item>
-    </div>
-
-    <!-- NO3和NH4在同一行 -->
-    <div class="params-row">
-      <el-form-item label="硝酸盐" prop="NO3" class="form-item-compact">
-        
-        <el-input
-          v-model.number="acidInversionParams.NO3"
-          type="number"
-          step="0.01"
-          min="0"
-          placeholder="9-70"
-          :disabled="phLoading"
-        />
-      </el-form-item>
-      <el-form-item label="铵盐" prop="NH4" class="form-item-compact">
-        
-        <el-input
-          v-model.number="acidInversionParams.NH4"
-          type="number"
-          step="0.01"
-          min="0"
-          placeholder="0-18"
-          :disabled="phLoading"
-        />
-      </el-form-item>
-    </div>
+    <!-- 1. 当前pH:独占一行(非水田) / 2×2第一格(水田) -->
+    <el-form-item 
+      label="当前pH" 
+      prop="currentPH" 
+      class="form-item-grid" 
+      :class="{ 'full-width': !isPaddyField }"
+    >
+      <div class="current-ph-value">
+        <span v-if="phLoading" class="loading-text">加载中...</span>
+        <span v-else-if="currentPH !== null">{{ currentPH.toFixed(2) }}</span>
+        <span v-else class="no-data-text">未获取</span>
+      </div>
+    </el-form-item>
+
+    <!-- 2. NO3:非水田第二行左 / 水田2×2第二格 -->
+    <el-form-item label="硝酸盐" prop="NO3" class="form-item-grid">
+      <el-input
+        v-model.number="acidInversionParams.NO3"
+        type="number"
+        step="0.01"
+        min="0"
+        placeholder="9-70"
+        :disabled="phLoading"
+        style="width: 100%;"
+      />
+    </el-form-item>
+
+    <!-- 3. NH4:非水田第二行右 / 水田2×2第三格 -->
+    <el-form-item label="铵盐" prop="NH4" class="form-item-grid">
+      <el-input
+        v-model.number="acidInversionParams.NH4"
+        type="number"
+        step="0.01"
+        min="0"
+        placeholder="0-18"
+        :disabled="phLoading"
+        style="width: 100%;"
+      />
+    </el-form-item>
+
+    <!-- 4. FeO:仅水田显示,2×2第四格 -->
+    <el-form-item 
+      v-if="isPaddyField" 
+      label="氧化铁" 
+      prop="FeO" 
+      class="form-item-grid"
+    >
+      <el-input
+        v-model.number="acidInversionParams.FeO"
+        type="number"
+        step="0.01"
+        min="0"
+        placeholder="0-120"  
+        :disabled="phLoading"
+        style="width: 100%;"
+      />
+    </el-form-item>
   </el-form>
 
-  <div class="input-buttons">
+  <div class="input-buttons" style="margin-top: 16px;">
     <el-button size="small" @click="cancelAcidInversion" :disabled="phLoading">取消</el-button>
     <el-button size="small" type="primary" @click="confirmAcidInversion" :loading="predictionLoading || phLoading">
       开始预测
@@ -233,6 +255,7 @@ import { reactive, ref, nextTick, onMounted, onUnmounted, computed } from "vue";
 import { ElMessage } from "element-plus";
 import type { FormInstance } from "element-plus";
 import { api8000 } from "@/utils/request";
+import { api5000 } from "../../../utils/request";
 
 // 新增:反酸预测相关状态(区分降酸/反酸的显示)
 const showInversionPrediction = ref(false); // 控制反酸预测区域显示
@@ -249,6 +272,7 @@ const highlightLayer = ref<any>(null);
 // 新增:控制连接线显示的标志
 const showConnectionLine = ref(false);
 
+
 // 预测状态
 const predictionLoading = ref(false);
 const predictionError = ref('');
@@ -303,10 +327,70 @@ interface AcidInversionParams {
   NO3?: number;
   NH4?: number;
 }
-// 反酸预测参数(指定类型为 AcidInversionParams)
+
+const LandTypeEnum = {
+  DRY_LAND: '旱地',        // 旱地
+  IRRIGATED_LAND: '水浇地', // 水浇地
+  PADDY_FIELD: '水田'      // 水田
+} as const;
+
+// 扩展反酸预测参数接口(新增FeO字段)
+interface AcidInversionParams {
+  NO3?: number;
+  NH4?: number;
+  FeO?: number; // 水田新增参数:氧化铁
+}
+
+// 初始化反酸参数(包含FeO默认值)
 const acidInversionParams = reactive<AcidInversionParams>({
   NO3: undefined,
-  NH4: undefined
+  NH4: undefined,
+  FeO: undefined // 水田专用参数
+});
+
+// 新增水田专用表单验证规则
+const acidInversionPaddyRules = reactive({
+  NO3: [
+    { required: true, message: '请输入NO3', trigger: 'change' },
+    { type: 'number', message: '请输入有效数字', trigger: 'change' },
+    { validator: (_rule: any, value: number, callback: any) => {
+        if (value < 0) callback(new Error('值不能为负数'));
+        else callback();
+      }, trigger: 'change' 
+    }
+  ],
+  NH4: [
+    { required: true, message: '请输入NH4', trigger: 'change' },
+    { type: 'number', message: '请输入有效数字', trigger: 'change' },
+    { validator: (_rule: any, value: number, callback: any) => {
+        if (value < 0) callback(new Error('值不能为负数'));
+        else callback();
+      }, trigger: 'change' 
+    }
+  ],
+  FeO: [
+    { required: true, message: '请输入FeO', trigger: 'change' },
+    { type: 'number', message: '请输入有效数字', trigger: 'change' },
+    { validator: (_rule: any, value: number, callback: any) => {
+        if (value < 0) callback(new Error('值不能为负数'));
+        else callback();
+      }, trigger: 'change' 
+    }
+  ]
+});
+
+// 计算属性:判断当前是否为水田
+const isPaddyField = computed(() => {
+  return featureInfo.data?.landType === LandTypeEnum.PADDY_FIELD;
+});
+
+// 计算属性:动态获取反酸验证规则(区分水田/非水田)
+const currentAcidInversionRules = computed(() => {
+  if (isPaddyField.value) {
+    return acidInversionPaddyRules;
+  }
+  // 非水田使用原有规则(仅NO3、NH4)
+  return acidInversionRules;
 });
 
 // 添加反酸预测的显示状态
@@ -579,15 +663,47 @@ const getFeatureInfo = async (_latlng: any, point: any): Promise<boolean> => {
     featureInfo.loading = false;
   }
 };
+// 新增:获取水田地块基础参数(CEC/Al/OM)
+const fetchPaddyFieldBaseParams = async (lng: number, lat: number) => {
+  try {
+    // 调用nearest_point接口(和获取currentPH的接口一致)
+    const response = await api8000.get('/api/vector/nearest-with-predictions', {
+      params: {
+        target_lon: lng.toString(),
+        target_lat: lat.toString(),
+        NO3: defaultParams.NO3.toString(), // 传默认值不影响接口返回基础参数
+        NH4: defaultParams.NH4.toString()
+      }
+    });
+
+    const nearestPoint = response.data.nearest_point || {};
+    // 返回接口中的基础参数(根据实际接口返回字段调整key)
+    return {
+      CEC: nearestPoint.CEC !== undefined ? Number(nearestPoint.CEC) : 7.14, // 兜底默认值
+      Al: nearestPoint.Al !== undefined ? Number(nearestPoint.Al) : 4.0,
+      OM: nearestPoint.OM !== undefined ? Number(nearestPoint.OM) : 22.12
+    };
+  } catch (error) {
+    console.error('获取水田基础参数失败:', error);
+    ElMessage.error('获取地块基础参数失败,使用默认值');
+    // 接口调用失败时返回默认值
+    return { CEC: 7.14, Al: 4.0, OM: 22.12 };
+  }
+};
+
+// 新增:缓存已获取的原始pH值(避免预测接口覆盖)
+const originalPH = ref<number | null>(null);
 
 // 新增:单独获取当前pH的方法(点击预测后先加载pH)
 const fetchCurrentPH = async () => {
-  if (currentPH.value !== null) return true; // 已有pH,直接返回
-  if (phLoading.value) return false; // 正在加载,避免重复请求
+  if (originalPH.value !== null) {
+    currentPH.value = originalPH.value; // 优先用缓存值
+    return true; 
+  }
+  if (phLoading.value) return false;
 
   phLoading.value = true;
   try {
-    // 准备请求参数
     const params = {
       target_lon: currentClickCoords.lng.toString(),
       target_lat: currentClickCoords.lat.toString(),
@@ -595,15 +711,14 @@ const fetchCurrentPH = async () => {
       NH4: defaultParams.NH4.toString()
     };
 
-    // 使用api8000模块调用API
     const response = await api8000.get('/api/vector/nearest-with-predictions', { params });
-    
-    // 从接口提取当前pH
-    currentPH.value = response.data.nearest_point?.ph !== undefined 
+    const phValue = response.data.nearest_point?.ph !== undefined 
       ? Number(response.data.nearest_point.ph) 
       : null;
       
-    return currentPH.value !== null; // 返回是否获取成功
+    originalPH.value = phValue; // 缓存原始pH
+    currentPH.value = phValue;
+    return phValue !== null;
   } catch (error) {
     console.error('获取当前pH失败:', error);
     ElMessage.error('获取土壤当前pH值失败,请重试');
@@ -629,6 +744,10 @@ const startAcidReductionPrediction = async () => {
 
 // 反酸预测:点击后先加载pH,再显示反酸区域
 const startAcidInversionPrediction = async () => {
+  if (!featureInfo.data?.landType) {
+    ElMessage.error('请先选择有效的地块(需包含用地类型)');
+    return;
+  }
   // 先获取当前pH
   const fetchSuccess = await fetchCurrentPH();
   if (!fetchSuccess) return;
@@ -638,6 +757,11 @@ const startAcidInversionPrediction = async () => {
   showAcidInversionInput.value = true;
   showAcidReductionInput.value = false;
   showPredictionResult.value = false;
+  nextTick(() => {
+    if (acidInversionFormRef.value) {
+      acidInversionFormRef.value.clearValidate(); // 清除可能的校验残留
+    }
+  });
 };
 
 //  取消降酸预测
@@ -685,6 +809,11 @@ const confirmAcidInversion = async () => {
     ElMessage.error('请先获取当前pH值');
     return;
   }
+  // 校验用地类型是否存在
+  if (!featureInfo.data?.landType) {
+    ElMessage.error('未获取到地块用地类型,无法进行预测');
+    return;
+  }
 
   try {
     await acidInversionFormRef.value.validate();
@@ -695,7 +824,8 @@ const confirmAcidInversion = async () => {
       currentClickCoords.lat,
       {
         NO3: acidInversionParams.NO3!,
-        NH4: acidInversionParams.NH4!
+        NH4: acidInversionParams.NH4!,
+        ...(isPaddyField.value ? { FeO: acidInversionParams.FeO! } : {})
       }
     );
   } catch (error) {
@@ -711,6 +841,7 @@ const callPredictionAPI = async (
     target_pH?: number;
     NO3: number;
     NH4: number;
+    FeO?: number; 
   }
 ) => {
   predictionLoading.value = true;
@@ -718,43 +849,67 @@ const callPredictionAPI = async (
   predictionResult.value = null;
 
   try {
-    // 构建请求参数对象
-    const requestParams: Record<string, string | number> = {
-      target_lon: lng,
-      target_lat: lat
-    };
-
-    // 添加预测类型参数
-    if (currentPredictionType.value === 'reduction') {
-      requestParams.prediction_type = 'reduce';
-    } else if (currentPredictionType.value === 'inversion') {
-      requestParams.prediction_type = 'reflux';
-    }
-
-    // 添加可选参数
-    if (params) {
-      Object.entries(params).forEach(([key, value]) => {
-        if (value !== undefined && value !== null) {
-          requestParams[key] = value;
+    // 区分预测类型处理
+    if (currentPredictionType.value === 'inversion' && isPaddyField.value) {
+      // ========== 水田反酸预测逻辑 ==========
+      // 1. 先获取地块基础参数(CEC/Al/OM)
+      const baseParams = await fetchPaddyFieldBaseParams(lng, lat);
+      
+      // 2. 合并:基础参数(接口读取) + 用户输入参数(表单)
+      const requestData = {
+        model_id: 36, // 水田反酸接口固定model_id
+        parameters: {
+          CEC: baseParams.CEC, // 从nearest_point读取
+          NH4: params?.NH4,
+          NO3: params?.NO3,
+          Al: baseParams.Al,   // 从nearest_point读取
+          FeO: params?.FeO,
+          OM: baseParams.OM,   // 从nearest_point读取
         }
-      });
-    }
-
-    console.log('调用预测接口,参数:', requestParams);
+      };
 
-    // 使用api8000调用API
-    const response = await api8000.get('/api/vector/nearest-with-predictions', {
-      params: requestParams
-    });
+      // 3. 调用水田反酸POST接口(替换为你的实际接口路径)
+      const response = await api5000.post('/predict', requestData);
+      
+      // 4. 处理返回结果(根据实际接口返回字段调整)
+      // 假设接口返回 { prediction_reflux: 0.5 } 这类反酸结果
+      predictionResult.value = {
+        prediction_reflux: response.data.result?.[0], // 取result数组的第一个元素
+        nearest_point: { ph: originalPH.value }
+      };
+      currentPH.value = originalPH.value;
+
+    } else {
+      // ========== 其他预测类型(降酸/非水田反酸) ==========
+      const requestParams: Record<string, string | number> = {
+        target_lon: lng,
+        target_lat: lat
+      };
+
+      if (currentPredictionType.value === 'reduction') {
+        requestParams.prediction_type = 'reduce';
+        if (params?.target_pH) requestParams.target_pH = params.target_pH;
+        if (params?.NO3) requestParams.NO3 = params.NO3;
+        if (params?.NH4) requestParams.NH4 = params.NH4;
+      } else if (currentPredictionType.value === 'inversion') {
+        requestParams.prediction_type = 'reflux';
+        if (params?.NO3) requestParams.NO3 = params.NO3;
+        if (params?.NH4) requestParams.NH4 = params.NH4;
+      }
 
-    predictionResult.value = response.data;
-    console.log('预测结果:', response.data);
+      const apiUrl = '/api/vector/nearest-with-predictions';
+      const response = await api8000.get(apiUrl, { params: requestParams });
+      predictionResult.value = response.data;
 
-    currentPH.value = response.data.nearest_point?.ph !== undefined 
-      ? Number(response.data.nearest_point.ph) 
-      : null;
+      // 非水田分支保留原有逻辑,但同步缓存
+      const newPH = predictionResult.value.nearest_point?.ph !== undefined 
+        ? Number(predictionResult.value.nearest_point.ph) 
+        : originalPH.value;
+      currentPH.value = newPH;
+      originalPH.value = newPH; // 更新缓存
+    }
 
-    // 显示预测结果
+    // 通用结果处理
     showPredictionResult.value = true;
     showAcidReductionInput.value = false;
     showAcidInversionInput.value = false;
@@ -762,7 +917,10 @@ const callPredictionAPI = async (
   } catch (error) {
     currentPH.value = null;
     console.error('调用预测接口失败:', error);
-    
+    // 友好的错误提示
+    predictionError.value = currentPredictionType.value === 'inversion' && isPaddyField.value
+      ? '水田反酸预测失败,请检查参数或重试'
+      : '预测请求失败,请稍后重试';
   } finally {
     predictionLoading.value = false;
   }
@@ -775,7 +933,10 @@ const resetPrediction = () => {
   currentPredictionType.value = null;
   showAcidReductionInput.value = false;
   showAcidInversionInput.value = false;
-  currentPH.value = null;
+  currentPH.value = originalPH.value;
+  acidInversionParams.NO3 = undefined;
+  acidInversionParams.NH4 = undefined;
+  acidInversionParams.FeO = undefined;
 };
 
 // 地图点击事件处理
@@ -801,6 +962,8 @@ const handleMapClick = async (e: any) => {
   
   // 重置预测状态
   resetPrediction();
+  originalPH.value = null; // 点击新地块清空缓存
+  currentPH.value = null;
   featureInfo.data = null;
   
   // 先清除旧的高亮图层
@@ -831,7 +994,14 @@ const handleMapClick = async (e: any) => {
     }else{
       showPopup.value=false;
       showConnectionLine.value=false;
-      ElMessage.info('请点击有效地块');
+
+      ElMessage({
+       message: '请点击有效地块',
+       type: 'info',
+       offset: 250, // 距离顶部偏移(默认20)
+       duration: 2000, // 显示时长
+       customClass: 'custom-land-message', // 自定义样式类名
+      });
     }
   } catch (error) {
     console.error('处理地块信息失败:', error);
@@ -858,6 +1028,9 @@ const closePopup = () => {
   showConnectionLine.value=false;
   resetPrediction();
   featureInfo.data = null;
+  // 清空pH缓存
+  originalPH.value = null;
+  currentPH.value = null;
   // 清除高亮图层
   if (highlightLayer.value && map.value) {
     map.value.removeLayer(highlightLayer.value);
@@ -1102,7 +1275,9 @@ const handleWindowResize = () => {
   background: white;
   border-radius: 8px;
   box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
-  width: 250px;
+  width: 280px; /* 增大最小宽度 */
+  min-width: 280px; /* 强制最小宽度 */
+  min-height: 150px; /* 防止弹窗塌陷 */
 }
 
 .feature-popup:active {
@@ -1164,6 +1339,37 @@ const handleWindowResize = () => {
   margin: 10px 0;
 }
 
+.custom-land-message .el-icon-info {
+  color: red !important;
+}
+
+.inversion-form {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr); /* 默认2列 */
+  gap: 12px 8px; /* 行间距12px,列间距8px */
+  width: 100%;
+}
+
+/* 网格项通用样式 */
+.form-item-grid {
+  margin-bottom: 0 !important;
+}
+
+/* 非水田时:当前pH独占一行 */
+.full-width {
+  grid-column: span 2; /* 跨2列 */
+}
+
+/* 适配小屏幕,防止布局错乱 */
+@media (max-width: 300px) {
+  .inversion-form {
+    grid-template-columns: 1fr; /* 小屏幕改为1列 */
+  }
+  .full-width {
+    grid-column: span 1;
+  }
+}
+
 .error-info {
   color: #f56c6c;
 }
@@ -1177,7 +1383,6 @@ const handleWindowResize = () => {
 
 .info-item {
   display: flex;
-  justify-content: space-between;
   align-items: center;
 }
 
@@ -1375,7 +1580,8 @@ const handleWindowResize = () => {
   display: flex;
   gap: 0px;
   margin-bottom: 12px;
-  align-items: flex-start;
+  align-items: center;
+  width: 100%;
 }
 
 /* 只读参数项样式 */
@@ -1516,5 +1722,33 @@ const handleWindowResize = () => {
   transform-origin: center center;
   z-index: 1;
 }
+/* 新增FeO输入项样式(和其他参数统一) */
+:deep(.form-item-compact.feo-item .el-input) {
+  width: 100%;
+}
+.feo-placeholder {
+  color: #c0c4cc;
+  font-size: 11px;
+}
+</style>
+
+<style>
+/* 强制覆盖“请点击有效地块”提示的图标颜色 */
+.custom-land-message.el-message--info .el-icon-info {
+  color: red !important;
+}
 
+/* 可选:同时修改提示文字颜色 */
+.custom-land-message .el-message__content {
+  color: #ff0000 !important;
+  font-size: 14px !important;
+}
+
+/* 可选:修改提示框整体样式 */
+.custom-land-message {
+  top: 250px !important; /* 和你设置的 offset 一致 */
+  background: #fef0f0 !important;
+  border: 1px solid #fbc4c4 !important;
+  border-radius: 8px !important;
+}
 </style>