Selaa lähdekoodia

Merge branch 'ding'

yangtaodemon 15 tuntia sitten
vanhempi
commit
5ae9a52c95

+ 0 - 1
src/components/layout/AppLayout.vue

@@ -52,7 +52,6 @@
       </div>
     </el-header>
 
-    <!-- Tabs -->
     <div class="tabs-row" v-if="!isFullScreen">
       <el-tabs
         v-if="showTabs"

+ 5 - 0
src/components/layout/menuItems.ts

@@ -44,6 +44,11 @@ export const tabMenuMap: Record<string, MenuItem[]> = {
       label: "土壤酸化地块级预测",
       icon: Location,
     },
+    {
+      index: "/pHPrediction",
+      label: "土壤pH预测",
+      icon: Location
+    }
   ],
   Calculation: [
     {

+ 9 - 0
src/router/index.ts

@@ -96,6 +96,15 @@ const routes = [
             "@/views/User/acidModel/acidmodelmap.vue"
           ),
         meta: { title: "土壤酸化地图" }
+      },
+       {
+        path: "pHPrediction",
+        name: "pHPrediction",
+        component: () =>
+          import(
+            "@/views/User/acidModel/pHPrediction.vue"
+          ),
+        meta: { title: "土壤pH预测" }
       },
       {
         path: "SoilAcidificationData",

+ 191 - 29
src/views/User/acidModel/ModelIterationVisualization.vue

@@ -1,5 +1,5 @@
 <script setup lang='ts'>
-import { ref, onMounted, nextTick, onUnmounted, defineProps } from 'vue';
+import { ref, onMounted, nextTick, onUnmounted, watch } from 'vue';
 import VueEcharts from 'vue-echarts';
 import 'echarts';
 import { api5000 } from '../../../utils/request';
@@ -76,6 +76,18 @@ const ecInitScatterOptionRef = ref<InstanceType<typeof VueEcharts>>();
 const ecMidScatterOptionRef = ref<InstanceType<typeof VueEcharts>>();
 const ecFinalScatterOptionRef = ref<InstanceType<typeof VueEcharts>>();
 
+// 新增:图片URL和模型类型选择
+const learningCurveImageUrl = ref('');
+const dataIncreaseCurveImageUrl = ref('');
+const selectedModelType = ref('rf'); // 默认选择随机森林
+
+// 模型类型选项
+const modelTypeOptions = [
+  { label: '随机森林', value: 'rf' },
+  { label: 'XGBoost', value: 'xgbr' },
+  { label: '梯度提升', value: 'gbst' },
+];
+
 // 计算数据范围的函数
 const calculateDataRange = (data: [number, number][]) => {
   const xValues = data.map(item => item[0]);
@@ -91,8 +103,6 @@ const calculateDataRange = (data: [number, number][]) => {
 // 获取折线图数据
 const fetchLineData = async () => {
   try {
-
-    // 修改后
     const response = await api5000.get<HistoryDataResponse>(`/get-model-history/${props.lineChartPathParam}`);    
     const data = response.data;
 
@@ -219,6 +229,47 @@ const fetchScatterData = async (modelId: number, optionRef: any) => {
   }
 };
 
+// 新增:获取学习曲线图片
+const fetchLearningCurveImage = async () => {
+  try {
+    const response = await api5000.get('/latest-learning-curve', {
+      params: {
+        data_type: 'reduce',
+        model_type: selectedModelType.value
+      },
+      responseType: 'blob'
+    });
+    
+    const blob = new Blob([response.data], { type: 'image/png' });
+    learningCurveImageUrl.value = URL.createObjectURL(blob);
+  } catch (error) {
+    console.error('获取学习曲线图片失败:', error);
+  }
+};
+
+// 新增:获取数据增长曲线图片
+const fetchDataIncreaseCurveImage = async () => {
+  try {
+    const response = await api5000.get('/latest-data-increase-curve', {
+      params: {
+        data_type: 'reduce',
+      },
+      responseType: 'blob'
+    });
+    
+    const blob = new Blob([response.data], { type: 'image/png' });
+    dataIncreaseCurveImageUrl.value = URL.createObjectURL(blob);
+  } catch (error) {
+    console.error('获取数据增长曲线图片失败:', error);
+  }
+};
+
+// 监听模型类型变化,重新获取图片
+watch(selectedModelType, () => {
+  fetchLearningCurveImage();
+  fetchDataIncreaseCurveImage();
+});
+
 // 定义调整图表大小的函数
 const resizeCharts = () => {
   nextTick(() => {
@@ -235,6 +286,10 @@ onMounted(async () => {
   if (props.showMidScatterChart) await fetchScatterData(props.midScatterModelId, ecMidScatterOption);
   if (props.showFinalScatterChart) await fetchScatterData(props.finalScatterModelId, ecFinalScatterOption);
 
+  // 新增:获取图片
+  await fetchLearningCurveImage();
+  await fetchDataIncreaseCurveImage();
+
   // 页面加载完成后调整图表大小
   resizeCharts();
 
@@ -245,38 +300,58 @@ onMounted(async () => {
 // 组件卸载时移除事件监听器
 onUnmounted(() => {
   window.removeEventListener('resize', resizeCharts);
+  
+  // 清理Blob URL
+  if (learningCurveImageUrl.value) {
+    URL.revokeObjectURL(learningCurveImageUrl.value);
+  }
+  if (dataIncreaseCurveImageUrl.value) {
+    URL.revokeObjectURL(dataIncreaseCurveImageUrl.value);
+  }
 });
 </script>
 
 <template>
   <div class="container">
-    <!-- <template v-if="showLineChart">
-       折线图表头
-      <div class="chart-container">
-        <VueEcharts :option="ecLineOption" ref="ecLineOptionRef" />
-      </div>
-    </template> -->
     <template v-if="showInitScatterChart">
-      <!-- 初代散点图表头 -->
-      <h2 class="chart-header">初代散点图</h2>
+      <!-- 散点图 -->
+      <h2 class="chart-header">散点图</h2>
       <div class="chart-container">
         <VueEcharts :option="ecInitScatterOption" ref="ecInitScatterOptionRef" />
       </div>
     </template>
-    <template v-if="showMidScatterChart">
-      <!-- 中间代散点图表头 -->
-      <h2 class="chart-header">中间代散点图</h2>
-      <div class="chart-container">
-        <VueEcharts :option="ecMidScatterOption" ref="ecMid极简白OptionRef" />
+
+    <h2 class="chart-header">模型性能分析</h2>
+    <!-- 模型性能分析部分 -->
+    <div class="analysis-section">
+       <!-- 数据增长曲线模块 -->
+      <div class="chart-module">
+        <h2 class="chart-header">数据增长曲线</h2>
+        <div class="image-chart-item">
+          <div class="image-container">
+            <img v-if="dataIncreaseCurveImageUrl" :src="dataIncreaseCurveImageUrl" alt="数据增长曲线" >
+            <div v-else class="image-placeholder">加载中...</div>
+          </div>
+        </div>
       </div>
-    </template>
-    <template v-if="showFinalScatterChart">
-      <!-- 最终代散点图表头 -->
-      <h2 class="chart-header">最终代散点图</h2>
-      <div class="chart-container">
-        <VueEcharts :option="ecFinalScatterOption" ref="ecFinalScatterOptionRef" />
+      <!-- 学习曲线模块 -->
+      <div class="chart-module">
+        <h2 class="chart-header">学习曲线</h2>
+        <div class="model-selector-wrapper">
+          <select v-model="selectedModelType" class="model-select">
+            <option v-for="option in modelTypeOptions" :key="option.value" :value="option.value">
+              {{ option.label }}
+            </option>
+          </select>
+        </div>
+        <div class="image-chart-item">
+          <div class="image-container">
+            <img v-if="learningCurveImageUrl" :src="learningCurveImageUrl" alt="学习曲线" >
+            <div v-else class="image-placeholder">加载中...</div>
+          </div>
+        </div>
       </div>
-    </template>
+    </div>
   </div>
 </template>
 
@@ -290,7 +365,7 @@ onUnmounted(() => {
   height: 100%;
   gap: 20px;
   color: #000;
-  background-color: white; /* 添加白色背景 */
+  background-color: white;
 }
 
 .chart-header {
@@ -310,16 +385,89 @@ onUnmounted(() => {
   height: 450px;
   margin: 0 auto;
   margin-bottom: 20px;
-  background-color: white; /* 图表容器背景为白色 */
+  background-color: white;
   border-radius: 8px;
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* 添加轻微阴影增强层次感 */
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
 }
 
 .VueEcharts {
   width: 100%;
   height: 100%;
   margin: 0 10px;
-  background-color: white; /* ECharts图表背景为白色 */
+  background-color: white;
+}
+
+/* 新增:图片图表样式 */
+.image-charts-section {
+  width: 90%;
+  margin: 30px auto;
+  padding: 20px;
+  background-color: #f8f9fa;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.model-type-selector {
+  margin: 20px 0;
+  text-align: center;
+}
+
+.model-type-selector label {
+  margin-right: 10px;
+  font-weight: bold;
+  color: #2c3e50;
+}
+
+.model-select {
+  padding: 8px 12px;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  background-color: white;
+  font-size: 14px;
+}
+
+.image-charts-container {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 30px;
+  margin-top: 20px;
+}
+
+.image-chart-item {
+  background-color: white;
+  padding: 20px;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.image-chart-title {
+  font-size: 16px;
+  font-weight: bold;
+  margin-bottom: 15px;
+  color: #2c3e50;
+  text-align: center;
+}
+
+.image-container {
+  width: 100%;
+  height: 400px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #f8f9fa;
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+.image-container img {
+  max-width: 100%;
+  max-height: 100%;
+  object-fit: contain;
+}
+
+.image-placeholder {
+  color: #6c757d;
+  font-style: italic;
 }
 
 /* 确保图表内部也是白色背景 */
@@ -342,6 +490,20 @@ onUnmounted(() => {
 :deep(.echarts-legend) {
   color: #333;
 }
-</style>
-
 
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .image-charts-container {
+    grid-template-columns: 1fr;
+  }
+  
+  .image-container {
+    height: 300px;
+  }
+  
+  .chart-container {
+    width: 95%;
+    height: 400px;
+  }
+}
+</style>

+ 377 - 0
src/views/User/acidModel/pHPrediction.vue

@@ -0,0 +1,377 @@
+<template>
+  <el-card class="box-card">
+    <template #header>
+      <div class="card-header">
+        <span>pH预测模型</span>
+      </div>
+    </template>
+
+    <el-form
+      :model="form"
+      ref="predictForm"
+      label-width="240px"
+      label-position="left"
+    >
+      <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="氯离子(g/kg)" prop="CL" :error="errorMessages.CL" required>
+        <el-input
+          v-model="form.CL"
+          size="large"
+          placeholder="请输入氯离子0~10(g/kg)"
+          @input="handleInput('CL', $event, 0, 10)"
+        ></el-input>
+      </el-form-item>
+
+      <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>
+
+      <el-form-item label="H+浓度(cmol/kg)" prop="H" :error="errorMessages.H" required>
+        <el-input
+          v-model="form.H"
+          size="large"
+          placeholder="请输入H+浓度0~1(cmol/kg)"
+          @input="handleInput('H', $event, 0, 1)"
+        ></el-input>
+      </el-form-item>
+
+      <el-form-item label="铵态氮(mg/kg)" prop="HN" :error="errorMessages.HN" required>
+        <el-input
+          v-model="form.HN"
+          size="large"
+          placeholder="请输入铵态氮0~30(mg/kg)"
+          @input="handleInput('HN', $event, 0, 30)"
+        ></el-input>
+      </el-form-item>
+
+      <el-form-item label="游离氧化铝(g/kg)" prop="Al3" :error="errorMessages.Al3" required>
+        <el-input
+          v-model="form.Al3"
+          size="large"
+          placeholder="请输入游离氧化铝0~2(g/kg)"
+          @input="handleInput('Al3', $event, 0, 2)"
+        ></el-input>
+      </el-form-item>
+
+       <el-form-item label="氧化铝(g/kg)" prop="AlOx" :error="errorMessages.AlOx" required>
+        <el-input
+          v-model="form.AlOx"
+          size="large"
+          placeholder="请输入氧化铝0~2(g/kg)"
+          @input="handleInput('AlOx', $event, 0, 2)"
+        ></el-input>
+      </el-form-item>
+
+      <el-form-item label="游离铁氧化物(g/kg)" prop="FeOx" :error="errorMessages.FeOx" required>
+        <el-input
+          v-model="form.FeOx"
+          size="large"
+          placeholder="请输入游离铁氧化物0~3(g/kg)"
+          @input="handleInput('FeOx', $event, 0, 3)"
+        ></el-input>
+      </el-form-item>
+
+      <el-form-item label="无定形铁(g/Kg)" prop="AmFe" :error="errorMessages.AmFe" required>
+        <el-input
+          v-model="form.AmFe"
+          size="large"
+          placeholder="请输入无定形铁0~1(g/Kg)"
+          @input="handleInput('AmFe', $event, 0, 1)"
+        ></el-input>
+      </el-form-item>
+
+      <el-form-item label="初始pH值" prop="initpH" :error="errorMessages.initpH" required>
+        <el-input
+          v-model="form.initpH"
+          size="large"
+          placeholder="请输入初始pH值0~14"
+          @input="handleInput('initpH', $event, 0, 14)"
+        ></el-input>
+      </el-form-item>
+
+      <el-button type="primary" @click="onSubmit" class="onSubmit">预测pH曲线</el-button>
+      <el-dialog v-model="dialogVisible" @close="onDialogClose" width="800px" align-center title="pH预测结果">
+        <div class="image-container">
+            <img v-if="imageSrc" :src="imageSrc" alt="预测图片" class="full-image"/>
+        </div>
+        <template #footer>
+          <el-button @click="dialogVisible = false">关闭</el-button>
+        </template>
+      </el-dialog>
+    </el-form>
+  </el-card>
+</template>
+
+<script setup lang="ts">
+import { reactive, ref, nextTick } from "vue";
+import { ElMessage } from "element-plus";
+import { api5000 } from "../../../utils/request";
+
+interface Form {
+  OM: number | null;
+  CL: number | null;
+  CEC: number | null;
+  H: number | null;
+  HN: number | null;
+  Al3: number | null;
+  AlOx: number | null;
+  FeOx: number | null;
+  AmFe: number | null;
+  initpH: number | null;
+}
+
+const form = reactive<Form>({
+  OM: null,
+  CL: null,
+  CEC: null,
+  H: null,
+  HN: null,
+  Al3: null,
+  AlOx: null,
+  FeOx: null,
+  AmFe: null,
+  initpH: null,
+});
+
+const imageSrc = ref<string | null>(null);
+const dialogVisible = ref(false);
+const predictForm = ref<any>(null);
+const errorMessages = reactive<Record<string, string>>({
+  OM: "",
+  CL: "",
+  CEC: "",
+  H: "",
+  HN: "",
+  Al3: "",
+  AlOx: "",
+  FeOx: "",
+  AmFe: "",
+  initpH: "",
+});
+
+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, "");
+  (form as any)[field] = value ? parseFloat(value) : null;
+
+  if (!value) {
+    errorMessages[field] = "";
+    return;
+  }
+
+  const numValue = parseFloat(value);
+  errorMessages[field] = (isNaN(numValue) || numValue < min || numValue > max)
+    ? `输入值应在 ${min} 到 ${max} 之间`
+    : "";
+};
+
+const validateInput = (value: string, min: number, max: number): boolean => {
+  const numValue = parseFloat(value);
+  return !isNaN(numValue) && numValue >= min && numValue <= max;
+};
+
+const onSubmit = async () => {
+  const inputConfigs = [
+    { field: "OM" as keyof Form, min: 0, max: 35 },
+    { field: "CL" as keyof Form, min: 0, max: 10 },
+    { field: "CEC" as keyof Form, min: 0, max: 20 },
+    { field: "H" as keyof Form, min: 0, max: 1 },
+    { field: "HN" as keyof Form, min: 0, max: 30 },
+    { field: "Al3" as keyof Form, min: 0, max: 2 },
+    { field: "AlOx" as keyof Form, min: 0, max: 2},
+    { field: "FeOx" as keyof Form, min: 0, max: 3 },
+    { field: "AmFe" as keyof Form, min: 0, max: 1 },
+    { field: "initpH" as keyof Form, min: 0, max: 14 },
+  ];
+
+  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)) {
+      isValid = false;
+      errorMessages[field] = `输入值应在 ${min} 到 ${max} 之间`;
+    } else {
+      errorMessages[field] = "";
+    }
+  }
+
+  if (!isValid) {
+    ElMessage.error("输入值不符合要求,请检查输入");
+    return;
+  }
+
+  const features = {
+    "OM g/kg": form.OM,
+    "CL g/kg": form.CL,
+    "CEC cmol/kg": form.CEC,
+    "H+ cmol/kg": form.H,
+    "HN mg/kg": form.HN,
+    "Al3+ cmol/kg": form.Al3,
+    "Free alumina g/kg": form.AlOx,                                                              
+    "Free iron oxides g/kg": form.FeOx,
+    "Amorphous iron g/Kg": form.AmFe,
+    "0 day": form.initpH
+  };
+
+  const curve =  {                                                                               
+      "start_day": 0,                                                                        
+      "end_day": 200,                                                                        
+      "num_points": 80,                                                                      
+      "return_binary": true                                                     
+    };           
+
+  try {
+    const response = await api5000.post('/api/ph/predict', {
+      day: 50,
+      features,
+      curve
+    }, {
+      responseType: 'arraybuffer'
+    });
+
+    const blob = new Blob([response.data], { type: 'image/png' });
+    imageSrc.value = URL.createObjectURL(blob);
+    dialogVisible.value = true;
+  } catch (error) {
+    ElMessage.error(`请求失败: ${error}`);
+  }
+};
+
+const onDialogClose = () => {
+  dialogVisible.value = false;
+  imageSrc.value = null;
+  Object.keys(form).forEach(key => form[key as keyof Form] = null);
+  Object.keys(errorMessages).forEach(key => errorMessages[key] = "");
+};
+
+
+</script>
+
+<style scoped>
+.box-card {
+  max-width: 850px;
+  margin: 0 auto;
+  padding: 20px;
+  background-color: #f0f5ff;
+  border-radius: 10px;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
+
+.card-header {
+  font-size: 25px;
+  text-align: center;
+  color: #333;
+  margin-bottom: 30px;
+}
+
+.el-form-item {
+  margin-bottom: 20px;
+}
+
+:deep(.el-form-item__label) {
+  font-size: 18px;
+  color: #666;
+}
+
+.model-info {
+  text-align: center;
+  margin-bottom: 20px;
+  font-size: 16px;
+  color: #555;
+}
+
+.el-input {
+  width: 80%;
+}
+
+.onSubmit {
+  display: block;
+  margin: 0 auto;
+  background-color: #007bff;
+  color: white;
+  padding: 10px 20px;
+  border-radius: 5px;
+  font-size: 16px;
+  transition: background-color 0.3s ease;
+}
+
+.onSubmit:hover {
+  background-color: #0056b3;
+}
+
+.dialog-class {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%; /* 确保容器占满对话框内容区域 */
+  font-size: 22px;
+}
+
+@media (max-width: 576px) {
+  .el-form {
+    --el-form-label-width: 100px;
+  }
+}
+
+.full-image {
+  max-width: 100%;
+  max-height: 100vh; /* 最大高度不超过视口 */
+  width: auto;
+  height: auto;
+  object-fit: contain; /* 保持比例 */
+  border: 2px solid #409eff;
+  box-shadow: 0 0 20px rgba(0,0,0,0.3);
+}
+
+.image-container {
+  width: 100%;
+  height: 100vh; /* 占满视口高度 */
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  overflow: auto; /* 添加滚动条防止极端情况 */
+}
+
+.el-dialog {
+  margin-top: 0 !important;
+  margin-bottom: 0 !important;
+}
+
+.el-dialog__header {
+  padding: 15px 20px;
+  background-color: #f5f7fa;
+  border-bottom: 1px solid #e4e7ed;
+}
+
+.el-dialog__body {
+  padding: 0 !important;
+  overflow: hidden;
+}
+
+/* 移动端适配 */
+@media (max-width: 768px) {
+  .image-container {
+    height: auto;
+    padding: 10px;
+  }
+  
+  .full-image {
+    max-width: calc(100vw - 20px);
+    max-height: calc(100vh - 40px);
+  }
+}
+</style>

+ 193 - 28
src/views/User/neutralizationModel/ModelIterationVisualization.vue

@@ -1,5 +1,5 @@
 <script setup lang='ts'>
-import { ref, onMounted, nextTick, onUnmounted, defineProps } from 'vue';
+import { ref, onMounted, nextTick, onUnmounted, watch } from 'vue';
 import VueEcharts from 'vue-echarts';
 import 'echarts';
 import { api5000 } from '../../../utils/request';
@@ -48,7 +48,7 @@ const props = defineProps({
   },
   lineChartPathParam: {
     type: String,
-    default: 'reduce'
+    default: 'reflux'
   },
   initScatterModelId: {
     type: Number,
@@ -76,6 +76,19 @@ const ecInitScatterOptionRef = ref<InstanceType<typeof VueEcharts>>();
 const ecMidScatterOptionRef = ref<InstanceType<typeof VueEcharts>>();
 const ecFinalScatterOptionRef = ref<InstanceType<typeof VueEcharts>>();
 
+// 新增:图片URL和模型类型选择
+const learningCurveImageUrl = ref('');
+const dataIncreaseCurveImageUrl = ref('');
+const selectedModelType = ref('rf'); // 默认选择随机森林
+
+// 模型类型选项
+const modelTypeOptions = [
+  { label: '随机森林', value: 'rf' },
+  { label: 'XGBoost', value: 'xgbr' },
+  { label: '梯度提升', value: 'gbst' },
+];
+
+
 // 计算数据范围的函数
 const calculateDataRange = (data: [number, number][]) => {
   const xValues = data.map(item => item[0]);
@@ -217,6 +230,46 @@ const fetchScatterData = async (modelId: number, optionRef: any) => {
   }
 };
 
+// 新增:获取学习曲线图片
+const fetchLearningCurveImage = async () => {
+  try {
+    const response = await api5000.get('/latest-learning-curve', {
+      params: {
+        data_type: 'reflux',
+        model_type: selectedModelType.value
+      },
+      responseType: 'blob'
+    });
+    
+    const blob = new Blob([response.data], { type: 'image/png' });
+    learningCurveImageUrl.value = URL.createObjectURL(blob);
+  } catch (error) {
+    console.error('获取学习曲线图片失败:', error);
+  }
+};
+
+// 新增:获取数据增长曲线图片
+const fetchDataIncreaseCurveImage = async () => {
+  try {
+    const response = await api5000.get('/latest-data-increase-curve', {
+      params: {
+        data_type: 'reflux',
+      },
+      responseType: 'blob'
+    });
+    
+    const blob = new Blob([response.data], { type: 'image/png' });
+    dataIncreaseCurveImageUrl.value = URL.createObjectURL(blob);
+  } catch (error) {
+    console.error('获取数据增长曲线图片失败:', error);
+  }
+};
+
+// 监听模型类型变化,重新获取图片
+watch(selectedModelType, () => {
+  fetchLearningCurveImage();
+  fetchDataIncreaseCurveImage();
+});
 // 定义调整图表大小的函数
 const resizeCharts = () => {
   nextTick(() => {
@@ -233,6 +286,10 @@ onMounted(async () => {
   if (props.showMidScatterChart) await fetchScatterData(props.midScatterModelId, ecMidScatterOption);
   if (props.showFinalScatterChart) await fetchScatterData(props.finalScatterModelId, ecFinalScatterOption);
 
+  // 新增:获取图片
+  await fetchLearningCurveImage();
+  await fetchDataIncreaseCurveImage();
+
   // 页面加载完成后调整图表大小
   resizeCharts();
 
@@ -243,37 +300,58 @@ onMounted(async () => {
 // 组件卸载时移除事件监听器
 onUnmounted(() => {
   window.removeEventListener('resize', resizeCharts);
+
+  // 清理Blob URL
+  if (learningCurveImageUrl.value) {
+    URL.revokeObjectURL(learningCurveImageUrl.value);
+  }
+  if (dataIncreaseCurveImageUrl.value) {
+    URL.revokeObjectURL(dataIncreaseCurveImageUrl.value);
+  }
 });
 </script>
+
 <template>
   <div class="container">
-    <!-- <template v-if="showLineChart">
-      折线图表头 
-      <div class="chart-container">
-        <VueEcharts :option="ecLineOption" ref="ecLineOptionRef" />
-      </div>
-    </template> -->
     <template v-if="showInitScatterChart">
-      <!-- 初代散点图表头 -->
-      <h2 class="chart-header">初代散点图</h2>
+      <!-- 散点图 -->
+      <h2 class="chart-header">散点图</h2>
       <div class="chart-container">
         <VueEcharts :option="ecInitScatterOption" ref="ecInitScatterOptionRef" />
       </div>
     </template>
-    <template v-if="showMidScatterChart">
-      <!-- 中间代散点图表头 -->
-      <h2 class="chart-header">中间代散点图</h2>
-      <div class="chart-container">
-        <VueEcharts :option="ecMidScatterOption" ref="ecMid极简白OptionRef" />
+
+   <h2 class="chart-header">模型性能分析</h2>
+    <!-- 模型性能分析部分 -->
+    <div class="analysis-section">
+       <!-- 数据增长曲线模块 -->
+      <div class="chart-module">
+        <h2 class="chart-header">数据增长曲线</h2>
+        <div class="image-chart-item">
+          <div class="image-container">
+            <img v-if="dataIncreaseCurveImageUrl" :src="dataIncreaseCurveImageUrl" alt="数据增长曲线" >
+            <div v-else class="image-placeholder">加载中...</div>
+          </div>
+        </div>
       </div>
-    </template>
-    <template v-if="showFinalScatterChart">
-      <!-- 最终代散点图表头 -->
-      <h2 class="chart-header">最终代散点图</h2>
-      <div class="chart-container">
-        <VueEcharts :option="ecFinalScatterOption" ref="ecFinalScatterOptionRef" />
+      <!-- 学习曲线模块 -->
+      <div class="chart-module">
+        <h2 class="chart-header">学习曲线</h2>
+        <div class="model-selector-wrapper">
+          <select v-model="selectedModelType" class="model-select">
+            <option v-for="option in modelTypeOptions" :key="option.value" :value="option.value">
+              {{ option.label }}
+            </option>
+          </select>
+        </div>
+        <div class="image-chart-item">
+          <div class="image-container">
+            <img v-if="learningCurveImageUrl" :src="learningCurveImageUrl" alt="学习曲线" >
+            <div v-else class="image-placeholder">加载中...</div>
+          </div>
+        </div>
       </div>
-    </template>
+    </div>
   </div>
 </template>
 
@@ -287,7 +365,7 @@ onUnmounted(() => {
   height: 100%;
   gap: 20px;
   color: #000;
-  background-color: white; /* 添加白色背景 */
+  background-color: white;
 }
 
 .chart-header {
@@ -307,16 +385,89 @@ onUnmounted(() => {
   height: 450px;
   margin: 0 auto;
   margin-bottom: 20px;
-  background-color: white; /* 图表容器背景为白色 */
+  background-color: white;
   border-radius: 8px;
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* 添加轻微阴影增强层次感 */
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
 }
 
 .VueEcharts {
   width: 100%;
   height: 100%;
   margin: 0 10px;
-  background-color: white; /* ECharts图表背景为白色 */
+  background-color: white;
+}
+
+/* 新增:图片图表样式 */
+.image-charts-section {
+  width: 90%;
+  margin: 30px auto;
+  padding: 20px;
+  background-color: #f8f9fa;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.model-type-selector {
+  margin: 20px 0;
+  text-align: center;
+}
+
+.model-type-selector label {
+  margin-right: 10px;
+  font-weight: bold;
+  color: #2c3e50;
+}
+
+.model-select {
+  padding: 8px 12px;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  background-color: white;
+  font-size: 14px;
+}
+
+.image-charts-container {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 30px;
+  margin-top: 20px;
+}
+
+.image-chart-item {
+  background-color: white;
+  padding: 20px;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.image-chart-title {
+  font-size: 16px;
+  font-weight: bold;
+  margin-bottom: 15px;
+  color: #2c3e50;
+  text-align: center;
+}
+
+.image-container {
+  width: 100%;
+  height: 400px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #f8f9fa;
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+.image-container img {
+  max-width: 100%;
+  max-height: 100%;
+  object-fit: contain;
+}
+
+.image-placeholder {
+  color: #6c757d;
+  font-style: italic;
 }
 
 /* 确保图表内部也是白色背景 */
@@ -339,6 +490,20 @@ onUnmounted(() => {
 :deep(.echarts-legend) {
   color: #333;
 }
-</style>
-
 
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .image-charts-container {
+    grid-template-columns: 1fr;
+  }
+  
+  .image-container {
+    height: 300px;
+  }
+  
+  .chart-container {
+    width: 95%;
+    height: 400px;
+  }
+}
+</style>