|
|
@@ -2,71 +2,56 @@
|
|
|
<el-card class="box-card">
|
|
|
<template #header>
|
|
|
<div class="card-header">
|
|
|
- <span>{{ $t('Calculation.refluxTitle') }}</span>
|
|
|
+ <span>反酸模型</span>
|
|
|
+ <div class="mode-switch">
|
|
|
+ <el-button type="primary" :class="['mode-button', 'dry-mode', currentMode === 'dry' ? 'active' : '']"
|
|
|
+ @click="setMode('dry')" icon="el-icon-crop" round>旱地</el-button>
|
|
|
+ <el-button type="primary" :class="['mode-button', 'paddy-mode', currentMode === 'paddy' ? 'active' : '']"
|
|
|
+ @click="setMode('paddy')" icon="el-icon-watermelon" round>水田</el-button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
- <el-form
|
|
|
- :model="form"
|
|
|
- ref="predictForm"
|
|
|
- label-width="240px"
|
|
|
- label-position="left"
|
|
|
- >
|
|
|
- <el-form-item :label="$t('Calculation.exchangeableHydrogen')" prop="H" :error="errorMessages.H" required>
|
|
|
- <el-input
|
|
|
- v-model="form.H"
|
|
|
- size="large"
|
|
|
- :placeholder="$t('Calculation.reflux.exchangeableHydrogenPlaceholder')"
|
|
|
- @input="handleInput('H', $event, 0, 5)"
|
|
|
- ></el-input>
|
|
|
+ <el-form :model="form" ref="predictForm" label-width="240px" label-position="left" class="form-container"
|
|
|
+ :class="currentMode === 'dry' ? 'dry-form' : 'paddy-form'">
|
|
|
+ <!-- 旱地模式下显示交换性氢 -->
|
|
|
+ <el-form-item v-if="currentMode === 'dry'" label="交换性氢(cmol/kg)" prop="H" :error="errorMessages.H" required>
|
|
|
+ <el-input v-model="form.H" size="large" placeholder="请输入交换性氢0~5(cmol/kg)"
|
|
|
+ @input="handleInput('H', $event, 0, 5)"></el-input>
|
|
|
</el-form-item>
|
|
|
- <el-form-item :label="$t('Calculation.exchangeableAluminum')" prop="Al" :error="errorMessages.Al" required>
|
|
|
- <el-input
|
|
|
- v-model="form.Al"
|
|
|
- size="large"
|
|
|
- :placeholder="$t('Calculation.reflux.exchangeableAluminumPlaceholder')"
|
|
|
- @input="handleInput('Al', $event, 0, 10)"
|
|
|
- ></el-input>
|
|
|
+ <el-form-item label="交换性铝(cmol/kg)" prop="Al" :error="errorMessages.Al" required>
|
|
|
+ <el-input v-model="form.Al" size="large" placeholder="请输入交换性铝0~10(cmol/kg)"
|
|
|
+ @input="handleInput('Al', $event, 0, 10)"></el-input>
|
|
|
</el-form-item>
|
|
|
- <el-form-item :label="$t('Calculation.soilOrganicMatter')" prop="OM" :error="errorMessages.OM" required>
|
|
|
- <el-input
|
|
|
- v-model="form.OM"
|
|
|
- size="large"
|
|
|
- :placeholder="$t('Calculation.reflux.soilOrganicMatterPlaceholder')"
|
|
|
- @input="handleInput('OM', $event, 0, 35)"
|
|
|
- ></el-input>
|
|
|
+ <el-form-item label="土壤有机质(g/kg)" prop="OM" :error="errorMessages.OM" required>
|
|
|
+ <el-input v-model="form.OM" size="large" placeholder="请输入土壤有机质0~35(g/kg)"
|
|
|
+ @input="handleInput('OM', $event, 0, 35)"></el-input>
|
|
|
</el-form-item>
|
|
|
- <el-form-item :label="$t('Calculation.nitrate')" prop="NO3" :error="errorMessages.NO3" required>
|
|
|
- <el-input
|
|
|
- v-model="form.NO3"
|
|
|
- size="large"
|
|
|
- :placeholder="$t('Calculation.reflux.nitratePlaceholder')"
|
|
|
- @input="handleInput('NO3', $event, 0, 70)"
|
|
|
- ></el-input>
|
|
|
+ <el-form-item label="硝酸盐(mg/kg)" prop="NO3" :error="errorMessages.NO3" required>
|
|
|
+ <el-input v-model="form.NO3" size="large" placeholder="请输入硝酸盐0~70(mg/kg)"
|
|
|
+ @input="handleInput('NO3', $event, 0, 70)"></el-input>
|
|
|
</el-form-item>
|
|
|
- <el-form-item :label="$t('Calculation.ammoniumSalt')" prop="NH4" :error="errorMessages.NH4" required>
|
|
|
- <el-input
|
|
|
- v-model="form.NH4"
|
|
|
- size="large"
|
|
|
- :placeholder="$t('Calculation.reflux.ammoniumSaltPlaceholder')"
|
|
|
- @input="handleInput('NH4', $event, 0, 20)"
|
|
|
- ></el-input>
|
|
|
+ <el-form-item label="铵盐(mg/kg)" prop="NH4" :error="errorMessages.NH4" required>
|
|
|
+ <el-input v-model="form.NH4" size="large" placeholder="请输入铵盐0~20(mg/kg)"
|
|
|
+ @input="handleInput('NH4', $event, 0, 20)"></el-input>
|
|
|
</el-form-item>
|
|
|
- <el-form-item :label="$t('Calculation.cationExchangeCapacity')" prop="CEC" :error="errorMessages.CEC" required>
|
|
|
- <el-input
|
|
|
- v-model="form.CEC"
|
|
|
- size="large"
|
|
|
- :placeholder="$t('Calculation.reflux.cationExchangeCapacityPlaceholder')"
|
|
|
- @input="handleInput('CEC', $event, 0, 20)"
|
|
|
- ></el-input>
|
|
|
+ <el-form-item label="阳离子交换量(cmol/kg)" prop="CEC" :error="errorMessages.CEC" required>
|
|
|
+ <el-input v-model="form.CEC" size="large" placeholder="请输入阳离子交换量0~20(cmol/kg)"
|
|
|
+ @input="handleInput('CEC', $event, 0, 20)"></el-input>
|
|
|
+ </el-form-item>
|
|
|
+ <!-- 水田模式下显示FeO -->
|
|
|
+ <el-form-item v-if="currentMode === 'paddy'" label="氧化铁(g/kg)" prop="FeO" :error="errorMessages.FeO" required>
|
|
|
+ <el-input v-model="form.FeO" size="large" placeholder="请输入氧化铁 0~50(g/kg)"
|
|
|
+ @input="handleInput('FeO', $event, 0, 50)"></el-input>
|
|
|
</el-form-item>
|
|
|
|
|
|
- <el-button type="primary" @click="onSubmit" class="onSubmit">{{ $t('Calculation.calculateButton') }}</el-button>
|
|
|
+ <el-button type="primary" @click="onSubmit" class="onSubmit">计算</el-button>
|
|
|
|
|
|
- <el-dialog v-model="dialogVisible" @close="onDialogClose" :close-on-click-modal="false" width="500px" align-center :title="$t('Calculation.resultTitle')">
|
|
|
+ <el-dialog v-model="dialogVisible" @close="onDialogClose" :close-on-click-modal="false" width="500px" align-center
|
|
|
+ title="计算结果">
|
|
|
<span class="dialog-class">ΔpH: {{ result }}</span>
|
|
|
<template #footer>
|
|
|
- <el-button @click="dialogVisible = false">{{ $t('Calculation.closeButton') }}</el-button>
|
|
|
+ <el-button @click="dialogVisible = false">关闭</el-button>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
</el-form>
|
|
|
@@ -74,12 +59,12 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { reactive, ref, nextTick, onMounted } from "vue";
|
|
|
-import { ElMessage } from "element-plus";
|
|
|
-import { api5000 } from "../../../utils/request";
|
|
|
-import { useI18n } from 'vue-i18n';
|
|
|
+import { reactive, ref, nextTick, onMounted } from 'vue';
|
|
|
+import { ElMessage } from 'element-plus';
|
|
|
+import { api5000 } from '../../../utils/request'; // 使用api5000
|
|
|
|
|
|
-const { t } = useI18n();
|
|
|
+// 计算模式类型
|
|
|
+type CalculationMode = 'dry' | 'paddy';
|
|
|
|
|
|
// 表单接口 - 根据文档修改
|
|
|
interface Form {
|
|
|
@@ -89,6 +74,7 @@ interface Form {
|
|
|
NO3: number | null;
|
|
|
NH4: number | null;
|
|
|
CEC: number | null;
|
|
|
+ FeO?: number | null; // 水田模式下的参数
|
|
|
}
|
|
|
|
|
|
// 表单数据 - 根据文档修改
|
|
|
@@ -99,43 +85,62 @@ const form = reactive<Form>({
|
|
|
NO3: null,
|
|
|
NH4: null,
|
|
|
CEC: null,
|
|
|
+ FeO: null, // 水田模式下的参数
|
|
|
});
|
|
|
|
|
|
const result = ref<number | null>(null);
|
|
|
const dialogVisible = ref(false);
|
|
|
const predictForm = ref<any>(null);
|
|
|
const errorMessages = reactive<Record<string, string>>({
|
|
|
- H: "",
|
|
|
- Al: "",
|
|
|
- OM: "",
|
|
|
- NO3: "",
|
|
|
- NH4: "",
|
|
|
- CEC: "",
|
|
|
+ H: '',
|
|
|
+ Al: '',
|
|
|
+ OM: '',
|
|
|
+ NO3: '',
|
|
|
+ NH4: '',
|
|
|
+ CEC: '',
|
|
|
+ FeO: '', // 水田模式下的参数错误信息
|
|
|
});
|
|
|
|
|
|
+// 计算模式状态 - 默认旱地模式
|
|
|
+const currentMode = ref<CalculationMode>('dry');
|
|
|
+
|
|
|
+// 设置计算模式
|
|
|
+const setMode = (mode: CalculationMode) => {
|
|
|
+ if (currentMode.value === mode) return;
|
|
|
+ currentMode.value = mode;
|
|
|
+ // 切换模式时重置表单
|
|
|
+ Object.keys(form).forEach((key) => (form[key as keyof Form] = null));
|
|
|
+ Object.keys(errorMessages).forEach((key) => (errorMessages[key as keyof typeof errorMessages] = ''));
|
|
|
+ nextTick(() => {
|
|
|
+ if (predictForm.value) (predictForm.value as any).resetFields();
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
// 当前选中模型
|
|
|
-const selectedModelId = ref<number>(35); // 根据文档修改为 35
|
|
|
-const selectedModelName = ref<string>("反酸预测模型");
|
|
|
+const selectedModelId = ref<number>(35); // 根据文档修改为35
|
|
|
+const selectedModelName = ref<string>('反酸预测模型');
|
|
|
|
|
|
// 输入校验
|
|
|
const handleInput = (field: keyof Form, event: Event, min: number, max: number) => {
|
|
|
const target = event.target as HTMLInputElement;
|
|
|
- let value = target.value.replace(/[^0-9.]/g, "");
|
|
|
+ let value = target.value.replace(/[^0-9.]/g, '');
|
|
|
(form as any)[field] = value ? parseFloat(value) : null;
|
|
|
|
|
|
if (!value) {
|
|
|
- errorMessages[field] = "";
|
|
|
+ errorMessages[field] = '';
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const numValue = parseFloat(value);
|
|
|
- errorMessages[field] = (isNaN(numValue) || numValue < min || numValue > max)
|
|
|
- ? t('Calculation.validationRange', { min, max })
|
|
|
- : "";
|
|
|
+ errorMessages[field] =
|
|
|
+ isNaN(numValue) || numValue < min || numValue > max ? `输入值应在 ${min} 到 ${max} 之间且为有效数字` : '';
|
|
|
};
|
|
|
|
|
|
-const validateInput = (value: string, min: number, max: number): boolean => {
|
|
|
- const numValue = parseFloat(value);
|
|
|
+const validateInput = (value: number | null | undefined, min: number, max: number): boolean => {
|
|
|
+ if (value === null || value === undefined) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ const numValue = typeof value === 'string' ? parseFloat(value) : value;
|
|
|
return !isNaN(numValue) && numValue >= min && numValue <= max;
|
|
|
};
|
|
|
|
|
|
@@ -148,72 +153,94 @@ const fetchSelectedModel = async () => {
|
|
|
selectedModelName.value = resp.data.data.model_name;
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- console.warn("获取当前模型失败,使用默认 model_id:", selectedModelId.value);
|
|
|
+ console.warn('获取当前模型失败,使用默认 model_id:', selectedModelId.value);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 计算方法 - 根据文档修改参数
|
|
|
const onSubmit = async () => {
|
|
|
- // 输入校验 - 根据新字段修改
|
|
|
- const inputConfigs = [
|
|
|
- { field: "H" as keyof Form, min: 0, max: 5 },
|
|
|
- { field: "Al" as keyof Form, min: 0, max: 10},
|
|
|
- { field: "OM" as keyof Form, min: 0, max: 35 },
|
|
|
- { field: "NO3" as keyof Form, min: 0, max: 70 },
|
|
|
- { field: "NH4" as keyof Form, min: 0, max: 20 },
|
|
|
- { field: "CEC" as keyof Form, min: 0, max: 20 },
|
|
|
- ];
|
|
|
+ // 根据当前模式设置需要验证的参数
|
|
|
+ let inputConfigs;
|
|
|
+ if (currentMode.value === 'dry') {
|
|
|
+ inputConfigs = [
|
|
|
+ { field: 'H' as keyof Form, min: 0, max: 5 },
|
|
|
+ { field: 'Al' as keyof Form, min: 0, max: 10 },
|
|
|
+ { field: 'OM' as keyof Form, min: 0, max: 35 },
|
|
|
+ { field: 'NO3' as keyof Form, min: 0, max: 70 },
|
|
|
+ { field: 'NH4' as keyof Form, min: 0, max: 20 },
|
|
|
+ { field: 'CEC' as keyof Form, min: 0, max: 20 },
|
|
|
+ ];
|
|
|
+ } else {
|
|
|
+ inputConfigs = [
|
|
|
+ { field: 'Al' as keyof Form, min: 0, max: 10 },
|
|
|
+ { field: 'OM' as keyof Form, min: 0, max: 35 },
|
|
|
+ { field: 'NO3' as keyof Form, min: 0, max: 70 },
|
|
|
+ { field: 'NH4' as keyof Form, min: 0, max: 20 },
|
|
|
+ { field: 'CEC' as keyof Form, min: 0, max: 20 },
|
|
|
+ { field: 'FeO' as keyof Form, min: 0, max: 50 },
|
|
|
+ ];
|
|
|
+ }
|
|
|
|
|
|
let isValid = true;
|
|
|
for (const config of inputConfigs) {
|
|
|
const { field, min, max } = config;
|
|
|
const value = form[field];
|
|
|
- if (value === null || !validateInput(value.toString(), min, max)) {
|
|
|
+ if (!validateInput(value, min, max)) {
|
|
|
isValid = false;
|
|
|
- errorMessages[field] = t('Calculation.validationRange', { min, max });
|
|
|
+ errorMessages[field] = `输入值应在 ${min} 到 ${max} 之间且为有效数字`;
|
|
|
} else {
|
|
|
- errorMessages[field] = "";
|
|
|
+ errorMessages[field] = '';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (!isValid) {
|
|
|
- ElMessage.error(t('Calculation.validationError'));
|
|
|
+ ElMessage.error('输入值不符合要求,请检查输入');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 根据文档修改参数结构
|
|
|
+ // 根据当前模式设置参数结构和model_id
|
|
|
const data = {
|
|
|
- model_id: selectedModelId.value,
|
|
|
- parameters: {
|
|
|
- H: form.H,
|
|
|
- Al: form.Al,
|
|
|
- OM: form.OM,
|
|
|
- NO3: form.NO3,
|
|
|
- NH4: form.NH4,
|
|
|
- CEC: form.CEC,
|
|
|
- },
|
|
|
+ model_id: currentMode.value === 'dry' ? 35 : 36,
|
|
|
+ parameters:
|
|
|
+ currentMode.value === 'dry'
|
|
|
+ ? {
|
|
|
+ H: form.H,
|
|
|
+ Al: form.Al,
|
|
|
+ OM: form.OM,
|
|
|
+ NO3: form.NO3,
|
|
|
+ NH4: form.NH4,
|
|
|
+ CEC: form.CEC,
|
|
|
+ }
|
|
|
+ : {
|
|
|
+ CEC: form.CEC,
|
|
|
+ NH4: form.NH4,
|
|
|
+ NO3: form.NO3,
|
|
|
+ Al: form.Al,
|
|
|
+ FeO: form.FeO,
|
|
|
+ OM: form.OM,
|
|
|
+ },
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
try {
|
|
|
const response = await api5000.post('/predict', data, {
|
|
|
headers: {
|
|
|
- "Content-Type": "application/json",
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
},
|
|
|
});
|
|
|
if (response.data && response.data.result && response.data.result.length > 0) {
|
|
|
result.value = parseFloat(response.data.result[0].toFixed(2));
|
|
|
dialogVisible.value = true;
|
|
|
} else {
|
|
|
- ElMessage.error(t('Calculation.invalidResult'));
|
|
|
+ ElMessage.error('未获取到有效的预测结果');
|
|
|
}
|
|
|
} catch (error: any) {
|
|
|
- console.error("请求失败:", error);
|
|
|
+ console.error('请求失败:', error);
|
|
|
if (error.response) {
|
|
|
- ElMessage.error(`${t('Calculation.requestFailed')}${error.response.status}`);
|
|
|
+ ElMessage.error(`请求失败,状态码: ${error.response.status}`);
|
|
|
} else if (error.request) {
|
|
|
- ElMessage.error(t('Calculation.noResponse'));
|
|
|
+ ElMessage.error('请求发送成功,但没有收到响应');
|
|
|
} else {
|
|
|
- ElMessage.error(`${t('Calculation.requestError')}${error.message}`);
|
|
|
+ ElMessage.error('请求过程中发生错误: ' + error.message);
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
@@ -222,7 +249,7 @@ const onSubmit = async () => {
|
|
|
const onDialogClose = () => {
|
|
|
dialogVisible.value = false;
|
|
|
Object.keys(form).forEach((key) => (form[key as keyof Form] = null));
|
|
|
- Object.keys(errorMessages).forEach((key) => (errorMessages[key as keyof typeof errorMessages] = ""));
|
|
|
+ Object.keys(errorMessages).forEach((key) => (errorMessages[key as keyof typeof errorMessages] = ''));
|
|
|
nextTick(() => {
|
|
|
if (predictForm.value) (predictForm.value as any).resetFields();
|
|
|
});
|
|
|
@@ -242,6 +269,7 @@ onMounted(() => {
|
|
|
background-color: #f0f5ff;
|
|
|
border-radius: 10px;
|
|
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
|
+ transition: all 0.3s ease;
|
|
|
}
|
|
|
|
|
|
.card-header {
|
|
|
@@ -249,10 +277,63 @@ onMounted(() => {
|
|
|
text-align: center;
|
|
|
color: #333;
|
|
|
margin-bottom: 30px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+/* 模式切换按钮样式 */
|
|
|
+.mode-switch {
|
|
|
+ margin-left: auto;
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.mode-button {
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ font-weight: bold;
|
|
|
+ background-color: #cccccc;
|
|
|
+ border-color: #cccccc;
|
|
|
+ color: #666666;
|
|
|
+}
|
|
|
+
|
|
|
+.mode-button:hover {
|
|
|
+ background-color: #b3b3b3;
|
|
|
+ border-color: #b3b3b3;
|
|
|
+}
|
|
|
+
|
|
|
+.mode-button.active {
|
|
|
+ opacity: 1;
|
|
|
+ transform: scale(1.05);
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.dry-mode.active {
|
|
|
+ background-color: #2e7d32;
|
|
|
+ border-color: #2e7d32;
|
|
|
+ color: white;
|
|
|
+}
|
|
|
+
|
|
|
+.dry-mode.active:hover {
|
|
|
+ background-color: #388e3c;
|
|
|
+ border-color: #388e3c;
|
|
|
+}
|
|
|
+
|
|
|
+.paddy-mode.active {
|
|
|
+ background-color: #0d47a1;
|
|
|
+ border-color: #0d47a1;
|
|
|
+ color: white;
|
|
|
+}
|
|
|
+
|
|
|
+.paddy-mode.active:hover {
|
|
|
+ background-color: #1565c0;
|
|
|
+ border-color: #1565c0;
|
|
|
}
|
|
|
|
|
|
.el-form-item {
|
|
|
margin-bottom: 20px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ animation: fadeIn 0.5s ease;
|
|
|
}
|
|
|
|
|
|
:deep(.el-form-item__label) {
|
|
|
@@ -260,6 +341,40 @@ onMounted(() => {
|
|
|
color: #666;
|
|
|
}
|
|
|
|
|
|
+/* 表单容器过渡效果 */
|
|
|
+.form-container {
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+/* 旱地模式表单样式 */
|
|
|
+.dry-form {
|
|
|
+ --form-bg: rgba(240, 245, 255, 0.9);
|
|
|
+}
|
|
|
+
|
|
|
+/* 水田模式表单样式 */
|
|
|
+.paddy-form {
|
|
|
+ --form-bg: rgba(220, 240, 255, 0.9);
|
|
|
+}
|
|
|
+
|
|
|
+/* 表单淡入动画 */
|
|
|
+@keyframes fadeIn {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(10px);
|
|
|
+ }
|
|
|
+
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 输入框焦点样式 */
|
|
|
+:deep(.el-input__inner:focus) {
|
|
|
+ box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.2);
|
|
|
+ border-color: #2196f3;
|
|
|
+}
|
|
|
+
|
|
|
.model-info {
|
|
|
text-align: center;
|
|
|
margin-bottom: 20px;
|
|
|
@@ -290,7 +405,8 @@ onMounted(() => {
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
align-items: center;
|
|
|
- height: 100%; /* 确保容器占满对话框内容区域 */
|
|
|
+ height: 100%;
|
|
|
+ /* 确保容器占满对话框内容区域 */
|
|
|
font-size: 22px;
|
|
|
}
|
|
|
|
|
|
@@ -299,4 +415,4 @@ onMounted(() => {
|
|
|
--el-form-label-width: 100px;
|
|
|
}
|
|
|
}
|
|
|
-</style>
|
|
|
+</style>
|