|
|
@@ -2,19 +2,19 @@
|
|
|
<el-card class="map-card">
|
|
|
<div class="title">
|
|
|
<div class="section-icon">🗺️</div>
|
|
|
- <p class="map-title">韶关市细粒度地块级酸化预测</p>
|
|
|
+ <p class="map-title">{{ $t('AcidModelMap.ShaoguanmapTitle') }}</p>
|
|
|
</div>
|
|
|
|
|
|
<div id="map-container" class="map-container">
|
|
|
<div v-if="mapLoading" class="loading">
|
|
|
<div class="loading-spinner"></div>
|
|
|
- <span>地图加载中...</span>
|
|
|
+ <span>{{ $t('AcidModelMap.loadingTip') }}</span>
|
|
|
</div>
|
|
|
<div v-if="mapError" class="error-tip">
|
|
|
- <el-alert title="地图加载失败" type="error" show-icon>
|
|
|
+ <el-alert :title="$t('AcidModelMap.errorTitle')" type="error" show-icon>
|
|
|
<template #description>
|
|
|
- <p>请检查GeoServer服务和配置</p>
|
|
|
- <el-button @click="reloadMap" type="primary">重试加载</el-button>
|
|
|
+ <p>{{ $t('AcidModelMap.errorDesc') }}</p>
|
|
|
+ <el-button @click="reloadMap" type="primary">{{ $t('AcidModelMap.retryButton') }}</el-button>
|
|
|
</template>
|
|
|
</el-alert>
|
|
|
</div>
|
|
|
@@ -34,20 +34,20 @@
|
|
|
<!-- 地块信息 -->
|
|
|
<div v-if="featureInfo.loading" class="loading-info">
|
|
|
<div class="loading-spinner small"></div>
|
|
|
- <span>加载地块信息中...</span>
|
|
|
+ <span>{{ $t('AcidModelMap.loadingFeatureInfo') }}</span>
|
|
|
</div>
|
|
|
- <div v-else-if="featureInfo.error" class="error-info">获取地块信息失败</div>
|
|
|
+ <div v-else-if="featureInfo.error" class="error-info">{{ $t('AcidModelMap.featureInfoError') }}</div>
|
|
|
<div v-else-if="featureInfo.data" class="feature-info">
|
|
|
<div class="info-item">
|
|
|
- <label>所属村:</label>
|
|
|
- <span>{{ featureInfo.data.village || '未知' }}</span>
|
|
|
+ <label>{{ $t('AcidModelMap.villageLabel') }}</label>
|
|
|
+ <span>{{ featureInfo.data.village || $t('AcidModelMap.unknown') }}</span>
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
- <label>用地类型:</label>
|
|
|
- <span>{{ featureInfo.data.landType || '未知' }}</span>
|
|
|
+ <label>{{ $t('AcidModelMap.landTypeLabel') }}</label>
|
|
|
+ <span>{{ featureInfo.data.landType || $t('AcidModelMap.unknown') }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div v-else class="no-data">无地块信息</div>
|
|
|
+ <div v-else class="no-data">{{ $t('AcidModelMap.noData') }}</div>
|
|
|
<button @click="closePopup" class="close-btn">×</button>
|
|
|
</div>
|
|
|
|
|
|
@@ -59,20 +59,20 @@
|
|
|
type="success"
|
|
|
@click="startAcidInversionPrediction"
|
|
|
>
|
|
|
- 反酸预测
|
|
|
+ {{ $t('AcidModelMap.acidInversionPrediction') }}
|
|
|
</el-button>
|
|
|
<el-button
|
|
|
size="small"
|
|
|
type="primary"
|
|
|
@click="startAcidReductionPrediction"
|
|
|
>
|
|
|
- 降酸预测
|
|
|
+ {{ $t('AcidModelMap.acidReductionPrediction') }}
|
|
|
</el-button>
|
|
|
</div>
|
|
|
|
|
|
<!-- 降酸预测参数输入 -->
|
|
|
<div v-if="showAcidReductionInput" class="acid-reduction-input">
|
|
|
- <div class="section-title">降酸预测参数</div>
|
|
|
+ <div class="section-title">{{ $t('AcidModelMap.acidReductionParams') }}</div>
|
|
|
|
|
|
<!-- 关键修复:el-form 绑定 rules 和 ref -->
|
|
|
<el-form
|
|
|
@@ -84,14 +84,14 @@
|
|
|
>
|
|
|
<!-- 下面的 params-row 放在 el-form 内部 -->
|
|
|
<div class="params-row">
|
|
|
- <el-form-item label="当前pH" class="form-item-compact readonly-item">
|
|
|
+ <el-form-item :label="$t('AcidModelMap.currentPH')" class="form-item-compact readonly-item">
|
|
|
<div class="current-ph-value">
|
|
|
- <span v-if="phLoading" class="loading-text">加载中...</span>
|
|
|
+ <span v-if="phLoading" class="loading-text">{{ $t('AcidModelMap.phLoading') }}</span>
|
|
|
<span v-else-if="currentPH !== null">{{ currentPH.toFixed(2) }}</span>
|
|
|
- <span v-else class="no-data-text">未获取</span>
|
|
|
+ <span v-else class="no-data-text">{{ $t('AcidModelMap.noDataText') }}</span>
|
|
|
</div>
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="目标pH" prop="target_pH" class="form-item-compact">
|
|
|
+ <el-form-item :label="$t('AcidModelMap.targetPH')" prop="target_pH" class="form-item-compact">
|
|
|
<el-input
|
|
|
v-model.number="acidReductionParams.target_pH"
|
|
|
type="number"
|
|
|
@@ -103,7 +103,7 @@
|
|
|
</div>
|
|
|
|
|
|
<div class="params-row">
|
|
|
- <el-form-item label="硝酸盐" prop="NO3" class="form-item-compact">
|
|
|
+ <el-form-item :label="$t('AcidModelMap.nitrate')" prop="NO3" class="form-item-compact">
|
|
|
|
|
|
<el-input
|
|
|
v-model.number="acidReductionParams.NO3"
|
|
|
@@ -114,7 +114,7 @@
|
|
|
:disabled="phLoading"
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="铵盐" prop="NH4" class="form-item-compact">
|
|
|
+ <el-form-item :label="$t('AcidModelMap.ammonium')" prop="NH4" class="form-item-compact">
|
|
|
|
|
|
<el-input
|
|
|
v-model.number="acidReductionParams.NH4"
|
|
|
@@ -129,16 +129,16 @@
|
|
|
</el-form>
|
|
|
|
|
|
<div class="input-buttons">
|
|
|
- <el-button size="small" @click="cancelAcidReduction" :disabled="phLoading">取消</el-button>
|
|
|
+ <el-button size="small" @click="cancelAcidReduction" :disabled="phLoading">{{ $t('AcidModelMap.cancel') }}</el-button>
|
|
|
<el-button size="small" type="primary" @click="confirmAcidReduction" :loading="predictionLoading || phLoading">
|
|
|
- 开始预测
|
|
|
+ {{ $t('AcidModelMap.confirm') }}
|
|
|
</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 反酸预测参数输入 -->
|
|
|
<div v-if="showAcidInversionInput" class="acid-inversion-input" style="width: 100%; padding: 8px 0;">
|
|
|
- <div class="section-title">反酸预测参数</div>
|
|
|
+ <div class="section-title">{{ $t('AcidModelMap.acidInversionParams') }}</div>
|
|
|
<el-form
|
|
|
:model="acidInversionParams"
|
|
|
:rules="currentAcidInversionRules"
|
|
|
@@ -147,22 +147,22 @@
|
|
|
size="small"
|
|
|
class="inversion-form"
|
|
|
>
|
|
|
- <!-- 1. 当前pH:独占一行(非水田) / 2×2第一格(水田) -->
|
|
|
+ <!-- 1. 当前 pH:独占一行(非水田)/ 2×2 第一格(水田) -->
|
|
|
<el-form-item
|
|
|
- label="当前pH"
|
|
|
+ :label="$t('AcidModelMap.currentPH')"
|
|
|
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-if="phLoading" class="loading-text">{{ $t('AcidModelMap.phLoading') }}</span>
|
|
|
<span v-else-if="currentPH !== null">{{ currentPH.toFixed(2) }}</span>
|
|
|
- <span v-else class="no-data-text">未获取</span>
|
|
|
+ <span v-else class="no-data-text">{{ $t('AcidModelMap.noDataText') }}</span>
|
|
|
</div>
|
|
|
</el-form-item>
|
|
|
|
|
|
- <!-- 2. NO3:非水田第二行左 / 水田2×2第二格 -->
|
|
|
- <el-form-item label="硝酸盐" prop="NO3" class="form-item-grid">
|
|
|
+ <!-- 2. NO3:非水田第二行左 / 水田 2×2 第二格 -->
|
|
|
+ <el-form-item :label="$t('AcidModelMap.nitrate')" prop="NO3" class="form-item-grid">
|
|
|
<el-input
|
|
|
v-model.number="acidInversionParams.NO3"
|
|
|
type="number"
|
|
|
@@ -174,8 +174,8 @@
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
|
|
|
- <!-- 3. NH4:非水田第二行右 / 水田2×2第三格 -->
|
|
|
- <el-form-item label="铵盐" prop="NH4" class="form-item-grid">
|
|
|
+ <!-- 3. NH4:非水田第二行右 / 水田 2×2 第三格 -->
|
|
|
+ <el-form-item :label="$t('AcidModelMap.ammonium')" prop="NH4" class="form-item-grid">
|
|
|
<el-input
|
|
|
v-model.number="acidInversionParams.NH4"
|
|
|
type="number"
|
|
|
@@ -187,10 +187,10 @@
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
|
|
|
- <!-- 4. FeO:仅水田显示,2×2第四格 -->
|
|
|
+ <!-- 4. FeO:仅水田显示,2×2 第四格 -->
|
|
|
<el-form-item
|
|
|
v-if="isPaddyField"
|
|
|
- label="氧化铁"
|
|
|
+ :label="$t('AcidModelMap.ferricOxide')"
|
|
|
prop="FeO"
|
|
|
class="form-item-grid"
|
|
|
>
|
|
|
@@ -207,9 +207,9 @@
|
|
|
</el-form>
|
|
|
|
|
|
<div class="input-buttons" style="margin-top: 16px;">
|
|
|
- <el-button size="small" @click="cancelAcidInversion" :disabled="phLoading">取消</el-button>
|
|
|
+ <el-button size="small" @click="cancelAcidInversion" :disabled="phLoading">{{ $t('AcidModelMap.cancel') }}</el-button>
|
|
|
<el-button size="small" type="primary" @click="confirmAcidInversion" :loading="predictionLoading || phLoading">
|
|
|
- 开始预测
|
|
|
+ {{ $t('AcidModelMap.confirm') }}
|
|
|
</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -219,7 +219,7 @@
|
|
|
<div class="section-title">{{ predictionTitle }}</div>
|
|
|
<div v-if="predictionLoading" class="loading-info">
|
|
|
<div class="loading-spinner small"></div>
|
|
|
- <span>预测中...</span>
|
|
|
+ <span>{{ $t('AcidModelMap.predicting') }}</span>
|
|
|
</div>
|
|
|
<div v-else-if="predictionError" class="error-info">
|
|
|
{{ predictionError }}
|
|
|
@@ -227,15 +227,15 @@
|
|
|
<div v-else-if="predictionResult" class="prediction-result">
|
|
|
<!--降酸结果-->
|
|
|
<div class="result-item" v-if="currentPredictionType === 'reduction' && predictionResult.prediction_reduce !== undefined">
|
|
|
- <span class="prediction-value reduction">每亩地土壤表层20cm撒{{ formatPredictionValue(predictionResult.prediction_reduce)}} 吨</span>
|
|
|
+ <span class="prediction-value reduction">{{ $t('AcidModelMap.resultUnit') }}{{ formatPredictionValue(predictionResult.prediction_reduce)}} {{ $t('AcidModelMap.ton') }}</span>
|
|
|
</div>
|
|
|
<!-- 反酸预测结果 -->
|
|
|
<div class="result-item" v-if="currentPredictionType === 'inversion' && predictionResult.prediction_reflux !== undefined">
|
|
|
- <span class="prediction-value inversion">ΔpH{{ formatPredictionValue(predictionResult.prediction_reflux)}} </span>
|
|
|
+ <span class="prediction-value inversion">{{ $t('AcidModelMap.deltaPH') }}{{ formatPredictionValue(predictionResult.prediction_reflux)}} </span>
|
|
|
</div>
|
|
|
<div class="result-buttons">
|
|
|
- <el-button size="small" @click="resetPrediction">重新预测</el-button>
|
|
|
- <el-button size="small" type="primary" @click="closePopup">关闭</el-button>
|
|
|
+ <el-button size="small" @click="resetPrediction">{{ $t('AcidModelMap.rerun') }}</el-button>
|
|
|
+ <el-button size="small" type="primary" @click="closePopup">{{ $t('AcidModelMap.close') }}</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -251,15 +251,18 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { reactive, ref, nextTick, onMounted, onUnmounted, computed } from "vue";
|
|
|
+import { reactive, ref, nextTick, computed, onMounted, onUnmounted } from "vue";
|
|
|
import { ElMessage } from "element-plus";
|
|
|
import type { FormInstance } from "element-plus";
|
|
|
import { api8000 } from "@/utils/request";
|
|
|
import { api5000 } from "../../../utils/request";
|
|
|
+import { useI18n } from 'vue-i18n';
|
|
|
+
|
|
|
+const { t } = useI18n();
|
|
|
|
|
|
// 新增:反酸预测相关状态(区分降酸/反酸的显示)
|
|
|
const showInversionPrediction = ref(false); // 控制反酸预测区域显示
|
|
|
-const phLoading = ref(false); // 控制当前pH的加载状态(点击预测后先加载pH)
|
|
|
+const phLoading = ref(false); // 控制当前 pH 的加载状态(点击预测后先加载 pH)
|
|
|
|
|
|
// 地图状态
|
|
|
const mapLoading = ref(true);
|
|
|
@@ -272,7 +275,6 @@ const highlightLayer = ref<any>(null);
|
|
|
// 新增:控制连接线显示的标志
|
|
|
const showConnectionLine = ref(false);
|
|
|
|
|
|
-
|
|
|
// 预测状态
|
|
|
const predictionLoading = ref(false);
|
|
|
const predictionError = ref('');
|
|
|
@@ -284,7 +286,7 @@ const currentPH = ref<number | null>(null);
|
|
|
let L: any = null;
|
|
|
|
|
|
const defaultParams = reactive({
|
|
|
- NO3: 34.05, // 随便填一个合理默认值(后端接口要求传,但不影响pH返回)
|
|
|
+ NO3: 34.05, // 随便填一个合理默认值(后端接口要求传,但不影响 pH 返回)
|
|
|
NH4: 7.87 // 同上,只要是合法数字即可
|
|
|
});
|
|
|
|
|
|
@@ -334,14 +336,14 @@ const LandTypeEnum = {
|
|
|
PADDY_FIELD: '水田' // 水田
|
|
|
} as const;
|
|
|
|
|
|
-// 扩展反酸预测参数接口(新增FeO字段)
|
|
|
+// 扩展反酸预测参数接口(新增 FeO 字段)
|
|
|
interface AcidInversionParams {
|
|
|
NO3?: number;
|
|
|
NH4?: number;
|
|
|
- FeO?: number; // 水田新增参数:氧化铁
|
|
|
+ FeO?: number; // 水田专用参数:氧化铁
|
|
|
}
|
|
|
|
|
|
-// 初始化反酸参数(包含FeO默认值)
|
|
|
+// 初始化反酸参数(包含 FeO 默认值)
|
|
|
const acidInversionParams = reactive<AcidInversionParams>({
|
|
|
NO3: undefined,
|
|
|
NH4: undefined,
|
|
|
@@ -351,28 +353,28 @@ const acidInversionParams = reactive<AcidInversionParams>({
|
|
|
// 新增水田专用表单验证规则
|
|
|
const acidInversionPaddyRules = reactive({
|
|
|
NO3: [
|
|
|
- { required: true, message: '请输入NO3', trigger: 'change' },
|
|
|
- { type: 'number', message: '请输入有效数字', trigger: 'change' },
|
|
|
+ { required: true, message: t('AcidModelMap.nitrate'), trigger: 'change' },
|
|
|
+ { type: 'number', message: t('validation.passwordLength'), trigger: 'change' },
|
|
|
{ validator: (_rule: any, value: number, callback: any) => {
|
|
|
- if (value < 0) callback(new Error('值不能为负数'));
|
|
|
+ if (value < 0) callback(new Error(t('validation.passwordLength')));
|
|
|
else callback();
|
|
|
}, trigger: 'change'
|
|
|
}
|
|
|
],
|
|
|
NH4: [
|
|
|
- { required: true, message: '请输入NH4', trigger: 'change' },
|
|
|
- { type: 'number', message: '请输入有效数字', trigger: 'change' },
|
|
|
+ { required: true, message: t('AcidModelMap.ammonium'), trigger: 'change' },
|
|
|
+ { type: 'number', message: t('validation.passwordLength'), trigger: 'change' },
|
|
|
{ validator: (_rule: any, value: number, callback: any) => {
|
|
|
- if (value < 0) callback(new Error('值不能为负数'));
|
|
|
+ if (value < 0) callback(new Error(t('validation.passwordLength')));
|
|
|
else callback();
|
|
|
}, trigger: 'change'
|
|
|
}
|
|
|
],
|
|
|
FeO: [
|
|
|
- { required: true, message: '请输入FeO', trigger: 'change' },
|
|
|
- { type: 'number', message: '请输入有效数字', trigger: 'change' },
|
|
|
+ { required: true, message: t('AcidModelMap.ferricOxide'), trigger: 'change' },
|
|
|
+ { type: 'number', message: t('validation.passwordLength'), trigger: 'change' },
|
|
|
{ validator: (_rule: any, value: number, callback: any) => {
|
|
|
- if (value < 0) callback(new Error('值不能为负数'));
|
|
|
+ if (value < 0) callback(new Error(t('validation.passwordLength')));
|
|
|
else callback();
|
|
|
}, trigger: 'change'
|
|
|
}
|
|
|
@@ -389,7 +391,7 @@ const currentAcidInversionRules = computed(() => {
|
|
|
if (isPaddyField.value) {
|
|
|
return acidInversionPaddyRules;
|
|
|
}
|
|
|
- // 非水田使用原有规则(仅NO3、NH4)
|
|
|
+ // 非水田使用原有规则(仅 NO3、NH4)
|
|
|
return acidInversionRules;
|
|
|
});
|
|
|
|
|
|
@@ -399,12 +401,12 @@ const showAcidInversionInput = ref(false);
|
|
|
// 添加反酸预测的表单验证规则
|
|
|
const acidInversionRules = reactive({
|
|
|
NO3: [
|
|
|
- { required: true, message: '请输入NO3', trigger: 'change' },
|
|
|
- { type: 'number', message: '请输入有效数字', trigger: 'change' },
|
|
|
+ { required: true, message: t('AcidModelMap.nitrate'), trigger: 'change' },
|
|
|
+ { type: 'number', message: t('validation.passwordLength'), trigger: 'change' },
|
|
|
{
|
|
|
validator: (_rule: any, value: number, callback: any) => {
|
|
|
if (value < 0) {
|
|
|
- callback(new Error('值不能为负数'));
|
|
|
+ callback(new Error(t('validation.passwordLength')));
|
|
|
} else {
|
|
|
callback();
|
|
|
}
|
|
|
@@ -413,12 +415,12 @@ const acidInversionRules = reactive({
|
|
|
}
|
|
|
],
|
|
|
NH4: [
|
|
|
- { required: true, message: '请输入NH4', trigger: 'change' },
|
|
|
- { type: 'number', message: '请输入有效数字', trigger: 'change' },
|
|
|
+ { required: true, message: t('AcidModelMap.ammonium'), trigger: 'change' },
|
|
|
+ { type: 'number', message: t('validation.passwordLength'), trigger: 'change' },
|
|
|
{
|
|
|
validator: (_rule: any, value: number, callback: any) => {
|
|
|
if (value < 0) {
|
|
|
- callback(new Error('值不能为负数'));
|
|
|
+ callback(new Error(t('validation.passwordLength')));
|
|
|
} else {
|
|
|
callback();
|
|
|
}
|
|
|
@@ -434,20 +436,20 @@ const acidInversionFormRef = ref<FormInstance | null>(null);
|
|
|
|
|
|
// 预测标题
|
|
|
const predictionTitle = computed(() => {
|
|
|
- if (currentPredictionType.value === 'reduction') return '降酸预测结果';
|
|
|
- if (currentPredictionType.value === 'inversion') return '反酸预测结果';
|
|
|
- return '预测结果';
|
|
|
+ if (currentPredictionType.value === 'reduction') return t('AcidModelMap.acidReductionResult');
|
|
|
+ if (currentPredictionType.value === 'inversion') return t('AcidModelMap.acidInversionResult');
|
|
|
+ return t('AcidModelMap.predictionResult');
|
|
|
});
|
|
|
|
|
|
// 输入校验规则
|
|
|
const acidReductionRules = reactive({
|
|
|
target_pH: [
|
|
|
- { required: true, message: '请输入目标pH值', trigger: 'blur' },
|
|
|
- { type: 'number', message: '请输入有效数字', trigger: 'blur' },
|
|
|
+ { required: true, message: t('AcidModelMap.targetPH'), trigger: 'blur' },
|
|
|
+ { type: 'number', message: t('validation.passwordLength'), trigger: 'blur' },
|
|
|
{
|
|
|
validator: (_rule: any, value: number, callback: any) => {
|
|
|
if (value < 0 || value > 14) {
|
|
|
- callback(new Error('值范围在0-14之间'));
|
|
|
+ callback(new Error(t('validation.passwordLength')));
|
|
|
} else {
|
|
|
callback();
|
|
|
}
|
|
|
@@ -456,12 +458,12 @@ const acidReductionRules = reactive({
|
|
|
}
|
|
|
],
|
|
|
NO3: [
|
|
|
- { required: true, message: '请输入NO3', trigger: 'blur' },
|
|
|
- { type: 'number', message: '请输入有效数字', trigger: 'blur' },
|
|
|
+ { required: true, message: t('AcidModelMap.nitrate'), trigger: 'blur' },
|
|
|
+ { type: 'number', message: t('validation.passwordLength'), trigger: 'blur' },
|
|
|
{
|
|
|
validator: (_rule: any, value: number, callback: any) => {
|
|
|
if (value < 0) {
|
|
|
- callback(new Error('值不能为负数'));
|
|
|
+ callback(new Error(t('validation.passwordLength')));
|
|
|
} else {
|
|
|
callback();
|
|
|
}
|
|
|
@@ -469,13 +471,13 @@ const acidReductionRules = reactive({
|
|
|
trigger: 'blur'
|
|
|
}
|
|
|
],
|
|
|
- NH4: [ // 修复:之前是nh4,和prop不一致
|
|
|
- { required: true, message: '请输入NH4', trigger: 'blur' },
|
|
|
- { type: 'number', message: '请输入有效数字', trigger: 'blur' },
|
|
|
+ NH4: [
|
|
|
+ { required: true, message: t('AcidModelMap.ammonium'), trigger: 'blur' },
|
|
|
+ { type: 'number', message: t('validation.passwordLength'), trigger: 'blur' },
|
|
|
{
|
|
|
validator: (_rule: any, value: number, callback: any) => {
|
|
|
if (value < 0) {
|
|
|
- callback(new Error('值不能为负数'));
|
|
|
+ callback(new Error(t('validation.passwordLength')));
|
|
|
} else {
|
|
|
callback();
|
|
|
}
|
|
|
@@ -489,21 +491,21 @@ const acidReductionRules = reactive({
|
|
|
const formatPredictionValue = (value: any): string => {
|
|
|
console.log('预测值原始数据:', value, '类型:', typeof value);
|
|
|
|
|
|
- if (value === null || value === undefined) return '无数据';
|
|
|
+ if (value === null || value === undefined) return t('AcidModelMap.noData');
|
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
- if (value.length === 0) return '无数据';
|
|
|
+ if (value.length === 0) return t('AcidModelMap.noData');
|
|
|
const num = Number(value[0]);
|
|
|
- return isNaN(num) ? '无效数据' : num.toFixed(4);
|
|
|
+ return isNaN(num) ? t('AcidModelMap.noData') : num.toFixed(4);
|
|
|
}
|
|
|
|
|
|
if (typeof value === 'object') {
|
|
|
console.warn('预测值是对象类型:', value);
|
|
|
- return '数据格式错误';
|
|
|
+ return t('AcidModelMap.noData');
|
|
|
}
|
|
|
|
|
|
const num = Number(value);
|
|
|
- return isNaN(num) ? '无效数据' : num.toFixed(4);
|
|
|
+ return isNaN(num) ? t('AcidModelMap.noData') : num.toFixed(4);
|
|
|
};
|
|
|
|
|
|
// 修改:在原地放大地块
|
|
|
@@ -556,10 +558,10 @@ const drawHighlightFeature = (geoJsonFeature: any) => {
|
|
|
return scaledFeature;
|
|
|
};
|
|
|
|
|
|
- // 创建放大1倍的地块
|
|
|
+ // 创建放大 1 倍的地块
|
|
|
const scaledFeature = scaleFeatureCoordinates(geoJsonFeature, 1);
|
|
|
|
|
|
- // 使用放大后的GeoJSON创建高亮图层
|
|
|
+ // 使用放大后的 GeoJSON 创建高亮图层
|
|
|
highlightLayer.value = L.geoJSON(scaledFeature, {
|
|
|
style: {
|
|
|
color: '#000',
|
|
|
@@ -586,8 +588,8 @@ const drawHighlightFeature = (geoJsonFeature: any) => {
|
|
|
// 调整地图视图以更好地显示放大的地块
|
|
|
// 添加一些内边距,让地块不会紧贴地图边缘
|
|
|
map.value.flyToBounds(scaledBounds, {
|
|
|
- padding: [50, 50], // 上下左右各50像素的内边距
|
|
|
- duration: 0.3, // 1秒动画
|
|
|
+ padding: [50, 50], // 上下左右各 50 像素的内边距
|
|
|
+ duration: 0.3, // 1 秒动画
|
|
|
maxZoom: 16 // 最大缩放级别限制,避免放得太大
|
|
|
});
|
|
|
|
|
|
@@ -663,10 +665,11 @@ 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的接口一致)
|
|
|
+ // 调用 nearest_point 接口(和获取 currentPH 的接口一致)
|
|
|
const response = await api8000.get('/api/vector/nearest-with-predictions', {
|
|
|
params: {
|
|
|
target_lon: lng.toString(),
|
|
|
@@ -678,24 +681,24 @@ const fetchPaddyFieldBaseParams = async (lng: number, lat: number) => {
|
|
|
});
|
|
|
|
|
|
const nearestPoint = response.data.nearest_point || {};
|
|
|
- // 返回接口中的基础参数(根据实际接口返回字段调整key)
|
|
|
+ // 返回接口中的基础参数(根据实际接口返回字段调整 key)
|
|
|
return {
|
|
|
- CEC: nearestPoint.CEC !== Number(nearestPoint.CEC) ,
|
|
|
- Al: nearestPoint.Al !== Number(nearestPoint.Al) ,
|
|
|
+ CEC: nearestPoint.CEC !== Number(nearestPoint.CEC),
|
|
|
+ Al: nearestPoint.Al !== Number(nearestPoint.Al),
|
|
|
OM: nearestPoint.OM !== Number(nearestPoint.OM)
|
|
|
};
|
|
|
} catch (error) {
|
|
|
console.error('获取水田基础参数失败:', error);
|
|
|
- ElMessage.error('获取地块基础参数失败,使用默认值');
|
|
|
+ ElMessage.error(t('AcidModelMap.fetchBaseParamsError'));
|
|
|
// 接口调用失败时返回默认值
|
|
|
return { CEC: 7.14, Al: 4.0, OM: 22.12 };
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-// 新增:缓存已获取的原始pH值(避免预测接口覆盖)
|
|
|
+// 新增:缓存已获取的原始 pH 值(避免预测接口覆盖)
|
|
|
const originalPH = ref<number | null>(null);
|
|
|
|
|
|
-// 新增:单独获取当前pH的方法(点击预测后先加载pH)
|
|
|
+// 新增:单独获取当前 pH 的方法(点击预测后先加载 pH)
|
|
|
const fetchCurrentPH = async () => {
|
|
|
if (originalPH.value !== null) {
|
|
|
currentPH.value = originalPH.value; // 优先用缓存值
|
|
|
@@ -718,12 +721,12 @@ const fetchCurrentPH = async () => {
|
|
|
? Number(response.data.nearest_point.ph)
|
|
|
: null;
|
|
|
|
|
|
- originalPH.value = phValue; // 缓存原始pH
|
|
|
+ originalPH.value = phValue; // 缓存原始 pH
|
|
|
currentPH.value = phValue;
|
|
|
return phValue !== null;
|
|
|
} catch (error) {
|
|
|
- console.error('获取当前pH失败:', error);
|
|
|
- ElMessage.error('获取土壤当前pH值失败,请重试');
|
|
|
+ console.error('获取当前 pH 失败:', error);
|
|
|
+ ElMessage.error(t('AcidModelMap.fetchPHError'));
|
|
|
currentPH.value = null;
|
|
|
return false;
|
|
|
} finally {
|
|
|
@@ -731,30 +734,30 @@ const fetchCurrentPH = async () => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-// 降酸预测:点击后先加载pH,再显示输入区
|
|
|
+// 降酸预测:点击后先加载 pH,再显示输入区
|
|
|
const startAcidReductionPrediction = async () => {
|
|
|
- // 先获取当前pH
|
|
|
+ // 先获取当前 pH
|
|
|
const fetchSuccess = await fetchCurrentPH();
|
|
|
if (!fetchSuccess) return;
|
|
|
|
|
|
- // pH获取成功,显示降酸输入区
|
|
|
+ // pH 获取成功,显示降酸输入区
|
|
|
currentPredictionType.value = 'reduction';
|
|
|
showAcidReductionInput.value = true;
|
|
|
showInversionPrediction.value = false;
|
|
|
showPredictionResult.value = false;
|
|
|
};
|
|
|
|
|
|
-// 反酸预测:点击后先加载pH,再显示反酸区域
|
|
|
+// 反酸预测:点击后先加载 pH,再显示反酸区域
|
|
|
const startAcidInversionPrediction = async () => {
|
|
|
if (!featureInfo.data?.landType) {
|
|
|
- ElMessage.error('请先选择有效的地块(需包含用地类型)');
|
|
|
+ ElMessage.error(t('AcidModelMap.missingLandType'));
|
|
|
return;
|
|
|
}
|
|
|
- // 先获取当前pH
|
|
|
+ // 先获取当前 pH
|
|
|
const fetchSuccess = await fetchCurrentPH();
|
|
|
if (!fetchSuccess) return;
|
|
|
|
|
|
- // pH获取成功,显示反酸区域(无输入参数)
|
|
|
+ // pH 获取成功,显示反酸区域(无输入参数)
|
|
|
currentPredictionType.value = 'inversion';
|
|
|
showAcidInversionInput.value = true;
|
|
|
showAcidReductionInput.value = false;
|
|
|
@@ -782,7 +785,7 @@ const cancelAcidInversion = () => {
|
|
|
const confirmAcidReduction = async () => {
|
|
|
if (!acidReductionFormRef.value) return;
|
|
|
if (currentPH.value === null) {
|
|
|
- ElMessage.error('请先获取当前pH值');
|
|
|
+ ElMessage.error(t('AcidModelMap.fetchPHError'));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -800,7 +803,7 @@ const confirmAcidReduction = async () => {
|
|
|
}
|
|
|
);
|
|
|
} catch (error) {
|
|
|
- ElMessage.error('输入参数不合法,请检查后重试');
|
|
|
+ ElMessage.error(t('AcidModelMap.inputError'));
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -808,19 +811,19 @@ const confirmAcidReduction = async () => {
|
|
|
const confirmAcidInversion = async () => {
|
|
|
if (!acidInversionFormRef.value) return;
|
|
|
if (currentPH.value === null) {
|
|
|
- ElMessage.error('请先获取当前pH值');
|
|
|
+ ElMessage.error(t('AcidModelMap.fetchPHError'));
|
|
|
return;
|
|
|
}
|
|
|
// 校验用地类型是否存在
|
|
|
if (!featureInfo.data?.landType) {
|
|
|
- ElMessage.error('未获取到地块用地类型,无法进行预测');
|
|
|
+ ElMessage.error(t('AcidModelMap.missingLandType'));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
await acidInversionFormRef.value.validate();
|
|
|
|
|
|
- // 调用预测接口 - 反酸预测需要传递NO3和NH4参数
|
|
|
+ // 调用预测接口 - 反酸预测需要传递 NO3 和 NH4 参数
|
|
|
await callPredictionAPI(
|
|
|
currentClickCoords.lng,
|
|
|
currentClickCoords.lat,
|
|
|
@@ -831,7 +834,7 @@ const confirmAcidInversion = async () => {
|
|
|
}
|
|
|
);
|
|
|
} catch (error) {
|
|
|
- ElMessage.error('输入参数不合法,请检查后重试');
|
|
|
+ ElMessage.error(t('AcidModelMap.inputError'));
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -857,26 +860,26 @@ const callPredictionAPI = async (
|
|
|
// 1. 先获取地块基础参数(CEC/Al/OM)
|
|
|
const baseParams = await fetchPaddyFieldBaseParams(lng, lat);
|
|
|
|
|
|
- // 2. 合并:基础参数(接口读取) + 用户输入参数(表单)
|
|
|
+ // 2. 合并:基础参数(接口读取)+ 用户输入参数(表单)
|
|
|
const requestData = {
|
|
|
- model_id: 36, // 水田反酸接口固定model_id
|
|
|
+ model_id: 36, // 水田反酸接口固定 model_id
|
|
|
parameters: {
|
|
|
- CEC: baseParams.CEC, // 从nearest_point读取
|
|
|
+ CEC: baseParams.CEC, // 从 nearest_point 读取
|
|
|
NH4: params?.NH4,
|
|
|
NO3: params?.NO3,
|
|
|
- Al: baseParams.Al, // 从nearest_point读取
|
|
|
+ Al: baseParams.Al, // 从 nearest_point 读取
|
|
|
FeO: params?.FeO,
|
|
|
- OM: baseParams.OM, // 从nearest_point读取
|
|
|
+ OM: baseParams.OM, // 从 nearest_point 读取
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- // 3. 调用水田反酸POST接口(替换为你的实际接口路径)
|
|
|
+ // 3. 调用水田反酸 POST 接口(替换为你的实际接口路径)
|
|
|
const response = await api5000.post('/predict', requestData);
|
|
|
|
|
|
// 4. 处理返回结果(根据实际接口返回字段调整)
|
|
|
// 假设接口返回 { prediction_reflux: 0.5 } 这类反酸结果
|
|
|
predictionResult.value = {
|
|
|
- prediction_reflux: response.data.result?.[0], // 取result数组的第一个元素
|
|
|
+ prediction_reflux: response.data.result?.[0], // 取 result 数组的第一个元素
|
|
|
nearest_point: { ph: originalPH.value }
|
|
|
};
|
|
|
currentPH.value = originalPH.value;
|
|
|
@@ -922,8 +925,8 @@ const callPredictionAPI = async (
|
|
|
console.error('调用预测接口失败:', error);
|
|
|
// 友好的错误提示
|
|
|
predictionError.value = currentPredictionType.value === 'inversion' && isPaddyField.value
|
|
|
- ? '水田反酸预测失败,请检查参数或重试'
|
|
|
- : '预测请求失败,请稍后重试';
|
|
|
+ ? t('AcidModelMap.inversionPredictError')
|
|
|
+ : t('AcidModelMap.generalPredictError');
|
|
|
} finally {
|
|
|
predictionLoading.value = false;
|
|
|
}
|
|
|
@@ -999,9 +1002,9 @@ const handleMapClick = async (e: any) => {
|
|
|
showConnectionLine.value=false;
|
|
|
|
|
|
ElMessage({
|
|
|
- message: '请点击有效地块',
|
|
|
+ message: t('AcidModelMap.clickValidLand'),
|
|
|
type: 'info',
|
|
|
- offset: 250, // 距离顶部偏移(默认20)
|
|
|
+ offset: 250, // 距离顶部偏移(默认 20)
|
|
|
duration: 2000, // 显示时长
|
|
|
customClass: 'custom-land-message', // 自定义样式类名
|
|
|
});
|
|
|
@@ -1031,7 +1034,7 @@ const closePopup = () => {
|
|
|
showConnectionLine.value=false;
|
|
|
resetPrediction();
|
|
|
featureInfo.data = null;
|
|
|
- // 清空pH缓存
|
|
|
+ // 清空 pH 缓存
|
|
|
originalPH.value = null;
|
|
|
currentPH.value = null;
|
|
|
// 清除高亮图层
|
|
|
@@ -1091,7 +1094,7 @@ const onDrag = (event: MouseEvent) => {
|
|
|
// 计算拖拽后的像素位置
|
|
|
const newX = event.clientX - dragStartPos.x;
|
|
|
const newY = event.clientY - dragStartPos.y;
|
|
|
- // 转成百分比(限制0-95,避免弹窗超出可视区)
|
|
|
+ // 转成百分比(限制 0-95,避免弹窗超出可视区)
|
|
|
popupPosition.x = Math.max(0, Math.min(95, (newX / viewportWidth) * 100));
|
|
|
popupPosition.y = Math.max(0, Math.min(95, (newY / viewportHeight) * 100));
|
|
|
updateConnectionLine();
|
|
|
@@ -1149,7 +1152,7 @@ const initMap = async () => {
|
|
|
mapError.value = false;
|
|
|
|
|
|
try {
|
|
|
- // 动态导入Leaflet
|
|
|
+ // 动态导入 Leaflet
|
|
|
if (!L) {
|
|
|
L = await import('leaflet');
|
|
|
await import('leaflet/dist/leaflet.css');
|
|
|
@@ -1176,14 +1179,14 @@ const initMap = async () => {
|
|
|
zoom: 10
|
|
|
});
|
|
|
|
|
|
- // WMS配置
|
|
|
+ // WMS 配置
|
|
|
const GEOSERVER_CONFIG = {
|
|
|
url: "/geoserver/wms",
|
|
|
workspace: "acidmap",
|
|
|
layerGroup: "mapwithboundary",
|
|
|
};
|
|
|
|
|
|
- // WMS图层配置
|
|
|
+ // WMS 图层配置
|
|
|
const wmsLayer = L.tileLayer.wms(GEOSERVER_CONFIG.url, {
|
|
|
layers: `${GEOSERVER_CONFIG.workspace}:${GEOSERVER_CONFIG.layerGroup}`,
|
|
|
format: "image/png",
|
|
|
@@ -1224,7 +1227,7 @@ const initMap = async () => {
|
|
|
mapError.value = true;
|
|
|
mapLoading.value = false;
|
|
|
|
|
|
- let errorMessage = '地图初始化失败';
|
|
|
+ let errorMessage = t('AcidModelMap.mapInitError');
|
|
|
if (error instanceof Error) {
|
|
|
errorMessage += ': ' + error.message;
|
|
|
}
|
|
|
@@ -1653,7 +1656,6 @@ const handleWindowResize = () => {
|
|
|
}
|
|
|
|
|
|
:deep(.form-item-compact .el-form-item__label) {
|
|
|
- width: 60px !important; /* 调整标签宽度 */
|
|
|
text-align: right;
|
|
|
padding-right: 8px;
|
|
|
}
|