Browse Source

Merge branch 'ding' of http://139.9.51.218:3000/qw/12

# Conflicts:
#	src/components/detectionStatistics/atmsampleStatistics.vue
#	src/components/detectionStatistics/irrigationstatistics.vue
#	src/components/soilcdStatistics/cropcdStatictics.vue
#	src/components/soilcdStatistics/effcdStatistics.vue
yangtaodemon 7 months ago
parent
commit
5c6ed7bf93
86 changed files with 5957 additions and 4903 deletions
  1. 3 0
      .env.development
  2. 3 0
      .env.production
  3. 0 3
      components.d.ts
  4. BIN
      public/农业化肥采集(1).png
  5. BIN
      public/农膜采集(1).png
  6. BIN
      public/农药采集(1).png
  7. BIN
      public/图1(1).png
  8. BIN
      public/图片2(1).png
  9. BIN
      public/图片3(1).png
  10. BIN
      public/图片4(1).jpg
  11. BIN
      public/图片5(1).jpg
  12. BIN
      public/图片6(1).jpg
  13. BIN
      public/大气干湿沉降示意图(1).png
  14. BIN
      public/大气通量(1).jpg
  15. BIN
      public/干湿沉降收集装置(1).png
  16. BIN
      public/有机肥采集(1).png
  17. 16 33
      src/API/admin.ts
  18. 4 4
      src/API/menus.ts
  19. 8 8
      src/API/users.ts
  20. 0 1
      src/App.vue
  21. 18 22
      src/components/atmpollution/airsampleChart.vue
  22. 19 20
      src/components/atmpollution/airsampleLine.vue
  23. 18 18
      src/components/atmpollution/atmcompanyline.vue
  24. 11 8
      src/components/atmpollution/atmcompanymap.vue
  25. 52 53
      src/components/atmpollution/atmsamplemap.vue
  26. 13 12
      src/components/atmpollution/heavyMetalEnterprisechart.vue
  27. 64 20
      src/components/detectionStatistics/atmcompanyStatics.vue
  28. 6 8
      src/components/detectionStatistics/atmsampleStatistics.vue
  29. 66 20
      src/components/detectionStatistics/crosscetionStatistics.vue
  30. 38 41
      src/components/detectionStatistics/irrigationstatistics.vue
  31. 19 24
      src/components/irrpollution/crossSectionSamplelineData.vue
  32. 15 15
      src/components/irrpollution/crossSetionData1.vue
  33. 68 16
      src/components/irrpollution/crossSetionData2.vue
  34. 5 7
      src/components/irrpollution/crosssectionmap.vue
  35. 6 8
      src/components/irrpollution/irrwatermap.vue
  36. 2 2
      src/components/irrpollution/tencentMapView.vue
  37. 222 0
      src/components/irrpollution/waterassaydata1.vue
  38. 49 9
      src/components/irrpollution/waterassaydata2.vue
  39. 197 0
      src/components/irrpollution/waterassaydata3.vue
  40. 340 0
      src/components/irrpollution/waterassaydata4.vue
  41. 103 25
      src/components/irrpollution/waterdataline.vue
  42. 9 9
      src/components/layout/AppAside.vue
  43. 1 9
      src/components/layout/AppHeader.vue
  44. 71 27
      src/components/layout/AppLayout.vue
  45. 56 28
      src/components/layout/menuItems.ts
  46. 16 10
      src/components/soilStatictics/reducedataStatistics.vue
  47. 16 29
      src/components/soilStatictics/refluxcedataStatictics.vue
  48. 11 21
      src/components/soilcdStatistics/cropcdStatictics.vue
  49. 54 22
      src/components/soilcdStatistics/effcdStatistics.vue
  50. 40 62
      src/components/soilcdStatistics/fluxcdStatictics.vue
  51. 2 1
      src/locales/en.json
  52. 15 15
      src/locales/zh.json
  53. 50 36
      src/router/index.ts
  54. 1 0
      src/stores/mytoken.ts
  55. 140 41
      src/utils/request.ts
  56. 14 24
      src/views/User/HmOutFlux/agriInput/farmInputSamplingDesc.vue
  57. 761 532
      src/views/User/HmOutFlux/agriInput/prodInputFlux.vue
  58. 7 36
      src/views/User/HmOutFlux/atmosDeposition/AtmosDepositionSamplingDesc.vue
  59. 66 57
      src/views/User/HmOutFlux/atmosDeposition/airInputFlux.vue
  60. 502 348
      src/views/User/HmOutFlux/irrigationWater/irriWaterInputFlux.vue
  61. 6 6
      src/views/User/HmOutFlux/irrigationWater/samplingMethodDevice1.vue
  62. 367 0
      src/views/User/HmOutFlux/totalInputFluxDesc.vue
  63. 25 16
      src/views/User/acidModel/Calculation.vue
  64. 8 6
      src/views/User/acidModel/ModelIterationVisualization.vue
  65. 126 333
      src/views/User/cadmiumPrediction/CropCadmiumPrediction.vue
  66. 120 312
      src/views/User/cadmiumPrediction/EffectiveCadmiumPrediction.vue
  67. 3 3
      src/views/User/cadmiumPrediction/TotalCadmiumPrediction.vue
  68. 55 78
      src/views/User/cadmiumPrediction/currentYearConcentration.vue
  69. 52 77
      src/views/User/cadmiumPrediction/netFlux.vue
  70. 56 73
      src/views/User/cadmiumPrediction/totalInputFlux.vue
  71. 51 73
      src/views/User/cadmiumPrediction/totalOutputFlux.vue
  72. 46 21
      src/views/User/dataStatistics/LandCultivatedStatistics.vue
  73. 5 5
      src/views/User/farmlandQualityAssessment/farmlandQualityAssessment.vue
  74. 0 7
      src/views/User/heavyMetalFluxCalculation/inputFluxCalculation/irrigationWater.vue
  75. 271 288
      src/views/User/hmInFlux/grainRemoval/grainRemovalInputFlux.vue
  76. 151 285
      src/views/User/hmInFlux/grainRemoval/samplingDesc1.vue
  77. 146 286
      src/views/User/hmInFlux/strawRemoval/samplingDesc2.vue
  78. 251 261
      src/views/User/hmInFlux/strawRemoval/strawRemovalInputFlux.vue
  79. 146 285
      src/views/User/hmInFlux/subsurfaceLeakage/samplingDesc3.vue
  80. 118 241
      src/views/User/hmInFlux/subsurfaceLeakage/subsurfaceLeakageInputFlux.vue
  81. 148 286
      src/views/User/hmInFlux/surfaceRunoff/samplingDesc4.vue
  82. 119 242
      src/views/User/hmInFlux/surfaceRunoff/surfaceRunoffInputFlux.vue
  83. 371 0
      src/views/User/hmInFlux/totalOutputFluxDesc.vue
  84. 2 2
      src/views/User/neutralizationModel/AcidNeutralizationModel.vue
  85. 6 6
      src/views/User/neutralizationModel/ModelIterationVisualization.vue
  86. 111 27
      src/views/login/loginView.vue

+ 3 - 0
.env.development

@@ -0,0 +1,3 @@
+VUE_APP_API_BASE_URL_5000 = http://127.0.0.1:5000
+VUE_APP_API_BASE_URL_8000 = http://127.0.0.1:8000
+VUE_APP_API_BASE_URL_MAIN = http://127.0.0.1

+ 3 - 0
.env.production

@@ -0,0 +1,3 @@
+VUE_APP_API_BASE_URL_5000 = https://www.soilgd.com:5000
+VUE_APP_API_BASE_URL_8000 = https://www.soilgd.com:8000
+VUE_APP_API_BASE_URL_MAIN = https://www.soilgd.com

+ 0 - 3
components.d.ts

@@ -54,8 +54,6 @@ declare module 'vue' {
     ElOption: typeof import('element-plus/es')['ElOption']
     ElPagination: typeof import('element-plus/es')['ElPagination']
     ElProgress: typeof import('element-plus/es')['ElProgress']
-    ElRadio: typeof import('element-plus/es')['ElRadio']
-    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElSelect: typeof import('element-plus/es')['ElSelect']
@@ -79,7 +77,6 @@ declare module 'vue' {
     Irrwatermap: typeof import('./src/components/irrpollution/irrwatermap.vue')['default']
     PaginationComponent: typeof import('./src/components/PaginationComponent.vue')['default']
     ReducedataStatistics: typeof import('./src/components/soilStatictics/reducedataStatistics.vue')['default']
-    RefluxcdStatictics: typeof import('./src/components/soilStatictics/refluxcdStatictics.vue')['default']
     RefluxcedataStatictics: typeof import('./src/components/soilStatictics/refluxcedataStatictics.vue')['default']
     Riverwaterassay: typeof import('./src/components/irrpollution/riverwaterassay.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']

BIN
public/农业化肥采集(1).png


BIN
public/农膜采集(1).png


BIN
public/农药采集(1).png


BIN
public/图1(1).png


BIN
public/图片2(1).png


BIN
public/图片3(1).png


BIN
public/图片4(1).jpg


BIN
public/图片5(1).jpg


BIN
public/图片6(1).jpg


BIN
public/大气干湿沉降示意图(1).png


BIN
public/大气通量(1).jpg


BIN
public/干湿沉降收集装置(1).png


BIN
public/有机肥采集(1).png


+ 16 - 33
src/API/admin.ts

@@ -1,11 +1,11 @@
-// @/API/admin.ts(修复后)
-import requestAdmin from '@/utils/request';
+// @/API/admin.ts
+import { api8000 } from '@/utils/request';
 import { ElMessage } from 'element-plus';
 import 'element-plus/es/components/message/style/css';
 
 // 🔹 获取表格数据
 export const table = (params: { table: string }) => {
-  return requestAdmin({
+  return api8000({
     url: "/admin/table",
     method: "GET",
     params: params,
@@ -25,7 +25,7 @@ export const addItem = (data: {
   table: string;
   item: Record<string, any>;
 }) => {
-  return requestAdmin({
+  return api8000({
     url: "/admin/add_item",
     method: "POST",
     params: { table: data.table },
@@ -44,7 +44,7 @@ export const updateItem = (data: {
   id: number;
   update_data: Record<string, any>;
 }) => {
-  return requestAdmin({
+  return api8000({
     url: "/admin/update_item",
     method: "PUT",
     params: { table: data.table, id: data.id },
@@ -62,7 +62,7 @@ export const deleteItemApi = (params: {
   table: string;
   id: number;
 }) => {
-  return requestAdmin({
+  return api8000({
     url: "/admin/delete_item",
     method: "DELETE",
     params: params,
@@ -71,8 +71,8 @@ export const deleteItemApi = (params: {
 
 // 🔹 导出数据
 export const exportData = (table: string, format: string = "xlsx") => {
-  return requestAdmin({
-    url: "/api/admin/export_data",
+  return api8000({
+    url: "/admin/export_data",
     method: "GET",
     params: { table, fmt: format },
     responseType: "blob",
@@ -123,20 +123,18 @@ export const importData = (table: string, file: File) => {
   // 2. 创建FormData(与后端参数名对齐)
   const formData = new FormData();
   formData.append('table', table);
-  formData.append('file', file); // 与后端File(...)参数名一致
+  formData.append('file', file);
 
-  return requestAdmin({
+  return api8000({
     url: "/admin/import_data",
     method: "POST",
     data: formData,
-    // 浏览器自动处理multipart边界,无需手动设置Content-Type(避免边界符缺失问题)
     headers: {
-      'Content-Type': undefined 
+      'Content-Type': 'multipart/form-data'
     }
   })
   .then((response) => {
     const { total_data, new_data, duplicate_data } = response.data;
-    // 3. 成功场景提示(区分无数据/有数据)
     let successMsg = "";
     if (total_data === 0) {
       successMsg = "✅ 文件导入成功,但文件中无有效数据(空内容)";
@@ -148,36 +146,24 @@ export const importData = (table: string, file: File) => {
     return response.data;
   })
   .catch((error) => {
-    // 4. 错误场景分类提示(适配后端结构化异常)
     let errorMsg = "❌ 导入失败,请稍后重试";
 
-    // 4.1 后端返回的结构化错误(重复记录/字段缺失等)
     if (error.response?.data) {
       const resData = error.response.data;
-      // 处理重复记录详情(后端返回的duplicates数组)
       if (resData.detail?.duplicate_count && resData.detail?.duplicates) {
         const { duplicate_count, duplicates, check_fields } = resData.detail;
-        // 拼接重复记录信息(行号+重复字段)
         const duplicateDetails = duplicates.map((item: any) => 
           `第${item.row_number}行:${check_fields.map((f: string) => `${f}=${item.duplicate_fields[f] || '空值'}`).join(',')}`
         ).join('\n');
         errorMsg = `❌ 检测到${duplicate_count}条重复数据(基于${check_fields.join('、')}字段):\n${duplicateDetails}`;
-      } 
-      // 处理普通错误(字段无效/缺少列等)
-      else if (resData.detail) {
+      } else if (resData.detail) {
         errorMsg = `❌ ${resData.detail.message || resData.detail}`;
-      } 
-      // 处理后端简洁错误信息
-      else if (resData.message) {
+      } else if (resData.message) {
         errorMsg = `❌ ${resData.message}`;
       }
-    } 
-    // 4.2 网络异常/请求超时
-    else if (!error.response) {
+    } else if (!error.response) {
       errorMsg = "❌ 网络异常或服务器无响应,请检查网络连接后重试";
-    } 
-    // 4.3 其他未知错误
-    else {
+    } else {
       errorMsg = "❌ 未知错误,可能是文件损坏或服务器繁忙";
     }
 
@@ -189,23 +175,20 @@ export const importData = (table: string, file: File) => {
 
 // 🔹 下载模板
 export const downloadTemplate = (table: string, format: string = "excel") => {
-  return requestAdmin({
+  return api8000({
     url: "/admin/download_template",
     method: "GET",
     params: { table, format },
     responseType: "blob",
   })
     .then((response) => {
-      // 1. 从响应头提取带时间戳的文件名
       const contentDisposition = response.headers["content-disposition"];
       if (!contentDisposition) {
         throw new Error("无法获取文件名");
       }
-      // 解析 Content-Disposition: attachment; filename=atmo_company_template_20240520153045.xlsx
       const filenameMatch = contentDisposition.match(/filename=([^;]+)/);
       const filename = filenameMatch ? decodeURIComponent(filenameMatch[1]) : `${table}_template.${format === "excel" ? "xlsx" : "csv"}`;
 
-      // 2. 处理文件流
       const blob = new Blob([response.data], {
         type: format === "excel"
           ? "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"

+ 4 - 4
src/API/menus.ts

@@ -1,8 +1,8 @@
-import request from "@/utils/request";
+import { api8000 } from "@/utils/request";
 
-// 使用导入的 request 函数进行网络请求
+// 使用导入的 api8000 函数进行网络请求
 export const table = (params: { table: string }) => {
-  return request({
+  return api8000({
     url: "/admin/table",
     method: "GET",
     data: params,
@@ -10,7 +10,7 @@ export const table = (params: { table: string }) => {
 };
 
 const customRequest = (options: any) => {
-  return request(options)
+  return api8000(options)
     .then(response => response.data)
     .catch(error => {
       if (error.response && error.response.status === 409) {

+ 8 - 8
src/API/users.ts

@@ -1,5 +1,5 @@
 // src/API/users.ts
-import request from "@/utils/request";
+import { api8000 } from "@/utils/request";
 import { useTokenStore } from "@/stores/mytoken";
 
 // =========================
@@ -12,7 +12,7 @@ export interface LoginInfo {
 }
 
 export const login = (loginInfo: LoginInfo) => {
-  return request({
+  return api8000({
     url: "/admin/login",
     method: "POST",
     data: {
@@ -32,8 +32,8 @@ export interface RegisterInfo {
 }
 
 export const addUser = (data: RegisterInfo) => {
-  return request({
-    url: "/admin/register", // 或者根据后端实际路由修改
+  return api8000({
+    url: "/admin/register",
     method: "POST",
     data: {
       name: data.name,
@@ -50,7 +50,7 @@ export const register = addUser;
 // 获取用户列表
 // =========================
 export const getUsers = () => {
-  return request({
+  return api8000({
     url: "/admin/list_users",
     method: "GET",
   });
@@ -66,7 +66,7 @@ export interface UpdateUserInfo {
 }
 
 export const updateUser = (userId: number, data: UpdateUserInfo) => {
-  return request({
+  return api8000({
     url: `/admin/update_user/${userId}`,
     method: "PUT",
     data,
@@ -77,7 +77,7 @@ export const updateUser = (userId: number, data: UpdateUserInfo) => {
 // 删除用户
 // =========================
 export const deleteUser = (userId: number) => {
-  return request({
+  return api8000({
     url: `/admin/delete_user/${userId}`,
     method: "DELETE",
   });
@@ -91,4 +91,4 @@ export const logout = async () => {
   store.clearToken();
   localStorage.removeItem("userInfo");
   return { success: true, message: "已退出登录" };
-};
+};

+ 0 - 1
src/App.vue

@@ -1,7 +1,6 @@
 <script setup lang='ts'>
 import { RouterView } from "vue-router"
 import request from './utils/request';
-import ReducedataStatistics from "./components/detectionStatistics/reducedataStatistics.vue";
 
 </script>
 

+ 18 - 22
src/components/atmpollution/airsampleChart.vue

@@ -23,7 +23,7 @@
 <script setup>
 import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
 import * as echarts from 'echarts'
-import axios from 'axios'
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
 // 接收计算方式(重量/体积)
 const props = defineProps({
@@ -37,8 +37,7 @@ const props = defineProps({
 // --------------------------
 // 配置区
 // --------------------------
-const BACKEND_PORT = '8000';
-const API_URL = `http://localhost:${BACKEND_PORT}/api/vector/export/all?table_name=Atmo_sample_data`;
+const API_URL = `/api/vector/export/all?table_name=Atmo_sample_data`; // 使用相对路径
 
 // 重量指标字段
 const WEIGHT_FIELDS = [
@@ -60,6 +59,7 @@ const COLORS = ['#ff4d4f99', '#1890ff', '#ffd700', '#52c41a88', '#722ed199'];
 const chartRef = ref(null);
 const loading = ref(true);
 const error = ref('');
+const errorDetails = ref(''); // 存储错误详情
 const showLog = ref(false); // 默认隐藏日志
 const fullLog = ref('');
 let myChart = null;
@@ -71,9 +71,7 @@ const log = (message) => {
   console.log(`[日志] ${message}`);
 };
 
-/**
- * 修复JSON中的非法值
- */
+
 const fixInvalidJsonValues = (rawData) => {
   if (typeof rawData !== 'string') {
     rawData = JSON.stringify(rawData);
@@ -91,9 +89,7 @@ const fixInvalidJsonValues = (rawData) => {
   return fixedData;
 };
 
-/**
- * 重量转体积计算
- */
+
 function weightToVolume(weight, volume) {
   if (weight === undefined || weight === null) {
     log(`重量值无效: ${weight}`);
@@ -116,9 +112,6 @@ function weightToVolume(weight, volume) {
   return parseFloat((ug / volumeNum).toFixed(2));
 }
 
-/**
- * 提取区县
- */
 function getRegion(location) {
   if (!location || typeof location !== 'string') {
     return '未知区县';
@@ -154,15 +147,15 @@ function getRegion(location) {
   return '未知区县';
 }
 
-/**
- * 数据处理
- */
+
 async function processData() {
   try {
     log('开始数据处理');
-    const response = await axios.get(API_URL, { 
-      timeout: 15000,
-      responseType: 'text'
+    
+    // 使用 api8000 实例发起请求
+    const response = await api8000.get(API_URL, {
+      responseType: 'text', // 确保获取原始文本
+      timeout: 15000
     });
     
     const fixedJson = fixInvalidJsonValues(response.data);
@@ -268,16 +261,18 @@ async function processData() {
 
   } catch (err) {
     error.value = '数据处理失败';
+    errorDetails.value = err.message;
     return null;
   }
 }
 
-/**
- * 初始化图表(带单位显示)
- */
+// /​**
+//  * 初始化图表(带单位显示)
+//  */
 async function initChart() {
   loading.value = true;
   error.value = '';
+  errorDetails.value = '';
   
   try {
     await nextTick();
@@ -365,6 +360,7 @@ async function initChart() {
 
   } catch (err) {
     error.value = '图表加载失败';
+    errorDetails.value = err.message;
   } finally {
     loading.value = false;
   }
@@ -484,4 +480,4 @@ onUnmounted(() => {
   font-family: monospace;
   font-size: 12px;
 }
-</style>
+</style>

+ 19 - 20
src/components/atmpollution/airsampleLine.vue

@@ -114,9 +114,10 @@
 import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue';
 import { LMap, LTileLayer, LMarker, LPopup } from '@vue-leaflet/vue-leaflet'; // 地图组件
 import * as echarts from 'echarts'; // 柱状图
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
 // ========== 接口配置 ==========
-const apiUrl = 'http://localhost:8000/api/vector/export/all?table_name=Atmo_sample_data'; 
+const apiUrl = '/api/vector/export/all?table_name=Atmo_sample_data'; // 使用相对路径
 
 // ========== 响应式数据 ==========
 const props = defineProps({
@@ -136,6 +137,7 @@ const sortKey = ref('');
 const sortOrder = ref('asc'); 
 const mapCenter = ref([23.5, 116.5]); // 地图初始中心(根据实际数据调整)
 const chartInstance = ref(null); // ECharts实例
+const chart = ref(null); // 图表容器引用
 
 // 换算函数(重量→体积)
 function calculateConcentration(heavyMetalWeight, volume) {
@@ -293,8 +295,10 @@ const chartData = computed(() => {
 const initChart = () => {
   nextTick(() => {
     if (chartInstance.value) chartInstance.value.dispose();
-    chartInstance.value = echarts.init($refs.chart);
-    chartInstance.value.setOption(chartData.value);
+    if (chart.value) {
+      chartInstance.value = echarts.init(chart.value);
+      chartInstance.value.setOption(chartData.value);
+    }
   });
 };
 
@@ -312,30 +316,25 @@ const fetchData = async () => {
     error.value = '';
     rawResponse.value = '';
 
-    // 超时控制(5秒)
-    const TIMEOUT = 5000;
-    const controller = new AbortController();
-    const timeoutId = setTimeout(() => controller.abort(), TIMEOUT);
-
-    const response = await fetch(apiUrl, { signal: controller.signal });
-    clearTimeout(timeoutId);
-
-    if (!response.ok) throw new Error(`HTTP 错误:${response.status}`);
-
-    let rawText = await response.text();
-    rawResponse.value = rawText;
+    // 使用 api8000 实例发起请求
+    const response = await api8000.get(apiUrl, {
+      responseType: 'text', // 确保获取原始文本
+      timeout: 5000 // 5秒超时
+    });
+    
+    rawResponse.value = response.data;
 
     // 修复 JSON 语法(替换 NaN/Infinity)
-    rawText = rawText
+    const fixedText = response.data
       .replace(/:\s*NaN/g, ': null')
       .replace(/:\s*Infinity/g, ': null');
 
     let data;
     try {
-      data = JSON.parse(rawText);
+      data = JSON.parse(fixedText);
     } catch (parseErr) {
-      error.value = `数据解析失败:${parseErr.message}\n原始响应:${rawText.slice(0, 200)}...`;
-      console.error(parseErr, rawText);
+      error.value = `数据解析失败:${parseErr.message}\n原始响应:${response.data.slice(0, 200)}...`;
+      console.error(parseErr, response.data);
       loading.value = false;
       return;
     }
@@ -367,7 +366,7 @@ const fetchData = async () => {
     initChart(); // 初始化图表
 
   } catch (err) {
-    if (err.name === 'AbortError') {
+    if (err.code === 'ECONNABORTED') {
       error.value = '请求超时!请检查网络或接口响应速度';
     } else {
       error.value = `数据加载失败:${err.message}`;

+ 18 - 18
src/components/atmpollution/atmcompanyline.vue

@@ -52,10 +52,11 @@
 
 <script setup>
 import { ref, onMounted, computed } from 'vue';
-import { wgs84togcj02 } from 'coordtransform'; // 经纬度转换(按需保留)
+import { wgs84togcj02 } from 'coordtransform';
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
 // ========== 接口配置 ==========
-const apiUrl = 'http://localhost:8000/api/vector/export/all?table_name=atmo_company'; 
+const apiUrl = '/api/vector/export/all?table_name=atmo_company'; // 使用相对路径
 
 // ========== 响应式数据 ==========
 const error = ref('');        // 错误信息
@@ -78,14 +79,13 @@ const fetchData = async () => {
     error.value = '';
     rawResponse.value = '';
 
-    // 1. 发起请求(获取原始文本)
-    const response = await fetch(apiUrl);
-    if (!response.ok) {
-      throw new Error(`HTTP 错误:${response.status}`);
-    }
-
-    // 2. 获取原始响应文本(关键:避免自动解析错误)
-    let rawText = await response.text();
+    // 1. 使用 api8000 实例获取原始文本
+    const response = await api8000.get(apiUrl, {
+      responseType: 'text' // 确保获取原始文本
+    });
+    
+    // 2. 获取原始响应文本
+    let rawText = response.data;
     rawResponse.value = rawText; // 保存原始响应
 
     // 3. 暴力替换 NaN 为 null(核心修复!)
@@ -220,19 +220,19 @@ onMounted(() => {
 
 /* 列宽分配 */
 .data-table th:nth-child(1),
-.data-table td:nth-child(1) { width: 10%; }/**污染源序号 */
+.data-table td:nth-child(1) { width: 10%; }
 .data-table th:nth-child(2),
-.data-table td:nth-child(2) { width: 28%; }/**公司名 */
+.data-table td:nth-child(2) { width: 28%; }
 .data-table th:nth-child(3),
-.data-table td:nth-child(3) { width: 16%; }/**类型 */
+.data-table td:nth-child(3) { width: 16%; }
 .data-table th:nth-child(4),
-.data-table td:nth-child(4) { width: 10%; }/**位置区县 */
+.data-table td:nth-child(4) { width: 10%; }
 .data-table th:nth-child(5),
-.data-table td:nth-child(5) { width: 12%; }/**颗粒物排放数据 */
+.data-table td:nth-child(5) { width: 12%; }
 .data-table th:nth-child(6),
-.data-table td:nth-child(6) { width: 11%; }/**经度 */
+.data-table td:nth-child(6) { width: 11%; }
 .data-table th:nth-child(7),
-.data-table td:nth-child(7) { width: 11%; }/**纬度 */
+.data-table td:nth-child(7) { width: 11%; }
 
 /* 内容换行处理 */
 .data-table td {
@@ -249,4 +249,4 @@ onMounted(() => {
     font-size: 13px; /* 缩小字体 */
   }
 }
-</style>
+</style>

+ 11 - 8
src/components/atmpollution/atmcompanymap.vue

@@ -8,6 +8,7 @@
 import { ref, onMounted } from 'vue';
 import L from 'leaflet';
 import 'leaflet/dist/leaflet.css';
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
 const mapContainer = ref(null);
 
@@ -89,17 +90,19 @@ onMounted(() => {
       alert('乡镇划分图加载错误:' + err.message);
     });
 
-  // 加载大气污染源数据(保持不变)
-  fetch('http://localhost:8000/api/vector/export/all?table_name=atmo_company')
-    .then(res => {
-      if (!res.ok) throw new Error(`接口请求失败:${res.status}`);
-      return res.text();
-    })
-    .then(text => {
+  // 加载大气污染源数据(使用 api8000 实例)
+  api8000.get('/api/vector/export/all?table_name=atmo_company', {
+    responseType: 'text' // 确保获取原始文本
+  })
+    .then(response => {
+      const text = response.data;
+      
+      // 修复 NaN 问题
       const fixedText = text.replace(
         /"particulate_emission":\s*NaN/g, 
         '"particulate_emission": null'
       );
+      
       const geoJSONData = JSON.parse(fixedText);
       
       console.log('✅ 接口数据加载完成,共', geoJSONData.features.length, '条记录');
@@ -246,4 +249,4 @@ onMounted(() => {
   transition: transform 0.2s;
   display: block;
 }
-</style>
+</style>

+ 52 - 53
src/components/atmpollution/atmsamplemap.vue

@@ -8,6 +8,7 @@
 import { ref, onMounted, watch } from 'vue'; 
 import L from 'leaflet';
 import 'leaflet/dist/leaflet.css';
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
 const props = defineProps({
   calculationMethod: {
@@ -176,63 +177,61 @@ onMounted(() => {
         },
       }).addTo(map);
 
-      // 加载大气数据(修复 JSON 非法值 + 换算逻辑)
-      fetch('http://localhost:8000/api/vector/export/all?table_name=Atmo_sample_data')
-        .then(res => {
-          if (!res.ok) throw new Error(`大气数据加载失败:${res.status}`);
-          return res.text(); // 先取文本,清洗 NaN
-        })
-        .then(text => {
+      // 加载大气数据(使用 api8000 实例)
+      api8000.get('/api/vector/export/all?table_name=Atmo_sample_data', {
+        responseType: 'text' // 确保获取原始文本
+      })
+        .then(response => {
+          const text = response.data;
           const validJsonText = text.replace(/NaN/g, 'null');
-          try {
-            return JSON.parse(validJsonText);
-          } catch (err) {
-            console.error('❌ JSON 解析失败(原始数据含非法值):', err);
-            throw new Error('数据格式错误,请检查服务端返回');
-          }
-        })
-        .then(geojsonData => {
-          const atmosphereData = geojsonData.features.map(feature => feature.properties);
-          console.log('✅ 大气数据加载完成,记录数:', atmosphereData.length);
-          markers.value = []; 
           
-          atmosphereData.forEach((item, idx) => {
-            try {
-              // 提取经纬度(兼容 properties 和 geometry)
-              let lat = item.latitude;
-              let lng = item.longitude;
-
-              if (lat === undefined || lng === undefined) {
-                if (item.geometry && item.geometry.type === 'Point' && item.geometry.coordinates.length === 2) {
-                  lng = item.geometry.coordinates[0]; 
-                  lat = item.geometry.coordinates[1];
-                } else {
-                  console.error(`❌ 未找到经纬度字段(第${idx}条)`);
-                  return;
+          try {
+            const geojsonData = JSON.parse(validJsonText);
+            const atmosphereData = geojsonData.features.map(feature => feature.properties);
+            console.log('✅ 大气数据加载完成,记录数:', atmosphereData.length);
+            markers.value = []; 
+            
+            atmosphereData.forEach((item, idx) => {
+              try {
+                // 提取经纬度(兼容 properties 和 geometry)
+                let lat = item.latitude;
+                let lng = item.longitude;
+
+                if (lat === undefined || lng === undefined) {
+                  if (item.geometry && item.geometry.type === 'Point' && item.geometry.coordinates.length === 2) {
+                    lng = item.geometry.coordinates[0]; 
+                    lat = item.geometry.coordinates[1];
+                  } else {
+                    console.error(`❌ 未找到经纬度字段(第${idx}条)`);
+                    return;
+                  }
                 }
-              }
 
-              const cleanLat = String(lat).replace(/[^\d.-]/g, '');
-              const cleanLng = String(lng).replace(/[^\d.-]/g, '');
-              
-              lat = parseFloat(parseFloat(cleanLat).toFixed(6));
-              lng = parseFloat(parseFloat(cleanLng).toFixed(6));
-              
-              const marker = L.circleMarker([lat, lng], {
-                radius: 3.5,
-                color: '#FF3333',
-                fillColor: '#FF3333',
-                fillOpacity: 0.9,
-                weight: 1.5,
-                zIndexOffset: 1000,
-              }).addTo(map);
-
-              marker.bindPopup(generatePopupContent(item, props.calculationMethod));
-              markers.value.push({ marker, item });
-            } catch (err) {
-              console.error(`❌ 处理大气数据失败(第${idx}条):`, err);
-            }
-          });
+                const cleanLat = String(lat).replace(/[^\d.-]/g, '');
+                const cleanLng = String(lng).replace(/[^\d.-]/g, '');
+                
+                lat = parseFloat(parseFloat(cleanLat).toFixed(6));
+                lng = parseFloat(parseFloat(cleanLng).toFixed(6));
+                
+                const marker = L.circleMarker([lat, lng], {
+                  radius: 3.5,
+                  color: '#FF3333',
+                  fillColor: '#FF3333',
+                  fillOpacity: 0.9,
+                  weight: 1.5,
+                  zIndexOffset: 1000,
+                }).addTo(map);
+
+                marker.bindPopup(generatePopupContent(item, props.calculationMethod));
+                markers.value.push({ marker, item });
+              } catch (err) {
+                console.error(`❌ 处理大气数据失败(第${idx}条):`, err);
+              }
+            });
+          } catch (parseErr) {
+            console.error('❌ JSON 解析失败(原始数据含非法值):', parseErr);
+            throw new Error('数据格式错误,请检查服务端返回');
+          }
         })
         .catch(err => {
           console.error('❌ 大气数据加载失败:', err);

+ 13 - 12
src/components/atmpollution/heavyMetalEnterprisechart.vue

@@ -41,10 +41,10 @@
 <script setup>
 import { ref, onMounted, computed, onUnmounted, nextTick } from 'vue';
 import * as echarts from 'echarts';
-import axios from 'axios';
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
 // ========== 核心配置 ==========
-const API_URL = 'http://localhost:8000/api/vector/export/all?table_name=atmo_company'; 
+const API_URL = '/api/vector/export/all?table_name=atmo_company'; // 使用相对路径
 const SG_REGIONS = [
   '浈江区', '武江区', '曲江区', '乐昌市', 
   '南雄市', '始兴县', '仁化县', '翁源县', 
@@ -163,13 +163,13 @@ const processData = (features) => {
         : 0;
     }),
     itemStyle: { 
-  // 当x轴类目是“全市平均”时,柱子显示红色
-  color: (params) => {
-    // params.dataIndex 对应 regions 数组的索引,最后一个是“全市平均”
-    const isTotal = validRegions[params.dataIndex] === '全市平均';
-    return isTotal ? '#ff0000' : '#1890ff'; // 红色可用 #ff0000 或其他红色值
-  }
-},
+      // 当x轴类目是“全市平均”时,柱子显示红色
+      color: (params) => {
+        // params.dataIndex 对应 regions 数组的索引,最后一个是“全市平均”
+        const isTotal = validRegions[params.dataIndex] === '全市平均';
+        return isTotal ? '#ff0000' : '#1890ff'; // 红色可用 #ff0000 或其他红色值
+      }
+    },
     label: { show: true, position: 'top', fontSize: 15 }
   }));
 
@@ -251,11 +251,12 @@ const fetchData = async () => {
     error.value = '';
     console.log('🚀 开始请求数据:', API_URL);
 
-    // 发起请求(延长超时)
-    const response = await axios.get(API_URL, {
+    // 使用 api8000 实例发起请求
+    const response = await api8000.get(API_URL, {
       timeout: 20000, // 20秒超时
       responseType: 'text'
     });
+    
     rawResponse.value = response.data;
     console.log('✅ 数据请求成功,状态码:', response.status);
 
@@ -387,4 +388,4 @@ onUnmounted(() => window.removeEventListener('resize', handleResize));
   color: #1890ff;
   z-index: 10;
 }
-</style>
+</style>

+ 64 - 20
src/components/detectionStatistics/atmcompanyStatics.vue

@@ -45,6 +45,7 @@
 
 <script>
 import * as echarts from 'echarts'
+import { api8000 } from '@/utils/request'
 
 export default {
   data() {
@@ -53,8 +54,9 @@ export default {
       error: null,
       processedData: [],
       currentMethod: 'max',
-      apiEndpoint: 'http://localhost:8000/api/vector/export/all?table_name=atmo_company',
+      apiEndpoint: '/api/vector/export/all?table_name=atmo_company',
       totalPoints: 0,
+      originalFeatures: [] // 添加原始数据备份用于调试
     };
   },
   mounted() {
@@ -70,21 +72,46 @@ export default {
   methods: {
     async fetchData() {
       try {
-        const response = await fetch(this.apiEndpoint);
-        if (!response.ok) {
-          throw new Error(`网络请求失败: ${response.status}`);
+        this.loading = true;
+        this.error = null;
+        
+        const response = await api8000.get(this.apiEndpoint);
+        
+        // 处理NaN问题
+        let data = response.data;
+        
+        // 如果返回的是字符串,尝试解析为JSON
+        if (typeof data === 'string') {
+          try {
+            // 替换NaN为null
+            const cleanedData = data.replace(/\bNaN\b/g, 'null');
+            data = JSON.parse(cleanedData);
+          } catch (parseError) {
+            this.loading = false;
+            this.error = `数据解析失败: ${parseError.message}`;
+            console.error("JSON解析错误:", parseError);
+            return;
+          }
         }
         
-        // 处理含NaN的非法JSON:先转为文本替换NaN为null
-        const rawText = await response.text();
-        const cleanedText = rawText.replace(/\bNaN\b/g, 'null'); // 关键修复
-        const data = JSON.parse(cleanedText);
+        // 检查处理后的数据结构
+        console.log('处理后的API数据:', data);
         
-        if (data && data.features && data.features.length > 0) {
+        // 情况1:返回的是FeatureCollection对象
+        if (data && data.type === "FeatureCollection" && Array.isArray(data.features)) {
+          this.originalFeatures = data.features;
           this.processData(data.features);
-        } else {
+        }
+        // 情况2:返回的是features数组
+        else if (Array.isArray(data)) {
+          this.originalFeatures = data;
+          this.processData(data);
+        }
+        // 其他情况
+        else {
           this.loading = false;
-          this.error = "接口返回空数据";
+          this.error = "接口返回数据格式不正确";
+          console.error("无效的数据结构:", data);
         }
         
       } catch (err) {
@@ -102,16 +129,22 @@ export default {
           const props = feature.properties || {};
           let county = props.county || '';
           
-          // 标准化地区名称
+          // 标准化地区名称 - 增强处理
           county = this.standardizeCountyName(county);
           
           if (county) {
             // 提取并校验排放数据
             const rawEmission = props.particulate_emission;
-            const emissionValue = parseFloat(rawEmission);
+            let emissionValue = parseFloat(rawEmission);
             
-            // 过滤无效值(NaN、非数字等)
-            if (isNaN(emissionValue)) {
+            // 处理可能的字符串值(如"15.836")
+            if (isNaN(emissionValue) && typeof rawEmission === 'string') {
+              emissionValue = parseFloat(rawEmission.replace(/[^0-9.]/g, ''));
+            }
+            
+            // 过滤无效值
+            if (isNaN(emissionValue) || emissionValue === null) {
+              console.warn('无效的排放量值:', rawEmission, '县区:', county);
               return;
             }
             
@@ -134,6 +167,8 @@ export default {
                 countyData.maxEmission = emissionValue;
               }
             }
+          } else {
+            console.warn('缺少县区名称:', props);
           }
         });
         
@@ -148,14 +183,23 @@ export default {
         
         // 计算有效样本总数
         this.totalPoints = this.processedData.reduce(
-          (sum, item) => sum + item.pointCount,
-          0
+          (sum, item) => sum + item.pointCount, 0
         );
 
+        console.log('处理后数据:', this.processedData);
+        
         this.loading = false;
-        this.$nextTick(() => {
-          this.renderChart();
-        });
+        
+        if (this.processedData.length === 0) {
+          this.error = "无有效数据可展示";
+          console.warn('无有效数据原因:', {
+            originalCount: features.length,
+            filteredCount: this.processedData.length,
+            sampleData: features.slice(0, 3)
+          });
+        } else {
+          this.$nextTick(this.renderChart);
+        }
         
       } catch (err) {
         this.loading = false;

+ 6 - 8
src/components/detectionStatistics/atmsampleStatistics.vue

@@ -27,7 +27,7 @@
 <script>
 import * as echarts from 'echarts'
 import VChart from 'vue-echarts'
-import axios from 'axios'
+import { api8000 } from '@/utils/request' // 导入 api8000 实例
 import { ref, onMounted, computed } from 'vue'
 
 export default {
@@ -35,7 +35,7 @@ export default {
   setup() {
     // -------- 核心配置 --------
     // 新接口地址(直接返回箱线图所需统计数据)
-    const apiUrl = ref('http://localhost:8000/api/vector/stats/Atmo_sample_data')
+    const apiUrl = ref('/api/vector/stats/Atmo_sample_data') // 修改为相对路径
     const heavyMetals = [
       { key: 'Cr_particulate', name: '铬 (Cr)', color: '#FF9800' },
       { key: 'As_particulate', name: '砷 (As)', color: '#4CAF50' },
@@ -58,7 +58,6 @@ export default {
 
 
     // -------- 工具函数 --------
-    /** 日志工具(带颜色区分) */
     const log = (message, metalName = '') => {
       console.log(
         `%c[${metalName || '全局'}] %c${message}`,
@@ -67,7 +66,6 @@ export default {
       )
     }
 
-    /** 构建箱线图数据 */
     const buildBoxplotData = (stats) => {
       const xAxisData = heavyMetals.map(m => m.name)
       // 生成箱线图所需格式:[[min, q1, median, q3, max], ...]
@@ -79,7 +77,6 @@ export default {
       return { xAxisData, data }
     }
 
-    /** 初始化图表 */
     const initChart = (xAxisData, data, stats) => {
       statsByIndex.value = stats // 缓存统计数据用于tooltip
       
@@ -137,8 +134,9 @@ export default {
     onMounted(async () => {
       try {
         log('发起新接口请求,获取统计数据...')
-        const response = await axios.get(apiUrl.value)
-        const apiData = response.data.data
+        // 使用 api8000 替代 axios
+        const response = await api8000.get(apiUrl.value)
+        const apiData = response.data
 
         // 从接口数据中提取每个重金属的统计量
         const stats = heavyMetals.map(metal => {
@@ -254,4 +252,4 @@ export default {
   color: #888;
   margin-top: 4px;
 }
-</style>
+</style>

+ 66 - 20
src/components/detectionStatistics/crosscetionStatistics.vue

@@ -1,11 +1,10 @@
 <template>
   <div class="dashboard">
     <div class="chart-container">
-
       <div class="chart-header">
         <div class="title-group">
-             <div class="chart-title">断面采样点各地区镉浓度统计</div>
-             <p class="sample-subtitle">样本来源:{{ totalPoints }}个数据</p>
+          <div class="chart-title">断面采样点各地区镉浓度统计</div>
+          <p class="sample-subtitle">样本来源:{{ totalPoints }}个数据</p>
         </div>
        
         <div class="method-selector">
@@ -45,6 +44,7 @@
 
 <script>
 import * as echarts from 'echarts'
+import { api8000 } from '@/utils/request' // 导入 api8000 实例
 
 export default {
   data() {
@@ -53,9 +53,9 @@ export default {
       error: null,
       processedData: [],
       currentMethod: 'max',
-      apiEndpoint: 'http://localhost:8000/api/vector/export/all?table_name=cross_section',
+      apiEndpoint: '/api/vector/export/all?table_name=cross_section', // 使用相对路径
       totalPoints: 0,
-      chartInstance: null // 新增:存储图表实例,避免内存泄漏
+      chartInstance: null
     };
   },
   mounted() {
@@ -77,18 +77,53 @@ export default {
   methods: {
     async fetchData() {
       try {
-        const response = await fetch(this.apiEndpoint);
-        if (!response.ok) {
+        this.loading = true;
+        this.error = null;
+        
+        // 使用 api8000 实例发送请求
+        const response = await api8000.get(this.apiEndpoint);
+        
+        // 检查响应状态
+        if (response.status !== 200) {
           throw new Error(`网络请求失败: ${response.status}`);
         }
         
-        const data = await response.json();
+        const data = response.data;
+        
+        // 处理可能的字符串响应
+        if (typeof data === 'string') {
+          try {
+            // 替换 NaN 为 null
+            const cleanedData = data.replace(/\bNaN\b/g, 'null');
+            data = JSON.parse(cleanedData);
+          } catch (parseErr) {
+            throw new Error('接口返回的是字符串,但 JSON 解析失败');
+          }
+        }
+        
+        // 处理对象中的 NaN 值
+        if (typeof data === 'object' && data !== null) {
+          const replaceNaN = (obj) => {
+            for (const key in obj) {
+              if (typeof obj[key] === 'object' && obj[key] !== null) {
+                replaceNaN(obj[key]);
+              } else if (typeof obj[key] === 'number' && isNaN(obj[key])) {
+                obj[key] = null;
+              } else if (obj[key] === 'NaN') {
+                obj[key] = null;
+              }
+            }
+          };
+          replaceNaN(data);
+        }
         
-        if (data && data.features && data.features.length > 0) {
+        // 检查数据结构
+        if (data && data.features && Array.isArray(data.features)) {
           this.processData(data.features);
+        } else if (Array.isArray(data)) {
+          this.processData(data);
         } else {
-          this.loading = false;
-          this.error = "接口返回空数据";
+          throw new Error("接口返回数据格式不正确");
         }
         
       } catch (err) {
@@ -109,7 +144,18 @@ export default {
           county = this.standardizeCountyName(county);
           
           if (county) {
-            const cdValue = parseFloat(props.cd_concentration) || 0;
+            // 处理可能的 NaN 值
+            let cdValue = props.cd_concentration;
+            if (cdValue === 'NaN' || isNaN(cdValue)) {
+              cdValue = null;
+            } else {
+              cdValue = parseFloat(cdValue);
+            }
+            
+            // 跳过无效值
+            if (cdValue === null || isNaN(cdValue)) {
+              return;
+            }
             
             if (!countyMap.has(county)) {
               countyMap.set(county, {
@@ -193,8 +239,8 @@ export default {
             const d = params[0].data;
             return `
               <div style="margin-bottom:3px;font-weight:bold;font-size:12px">${d.name}</div>
-              <div style="font-size:11px">最高浓度: ${d.maxCd} mg/L</div>
-              <div style="font-size:11px">平均浓度: ${d.avgCd} mg/L</div>
+              <div style="font-size:11px">最高浓度: ${d.maxCd.toFixed(4)} mg/L</div>
+              <div style="font-size:11px">平均浓度: ${d.avgCd.toFixed(4)} mg/L</div>
               <div style="margin-top:3px;font-size:11px">监测点数量: ${d.pointCount}个</div>
             `;
           }
@@ -212,12 +258,12 @@ export default {
           name: `镉浓度`,
           nameLocation: 'end',
           nameTextStyle: {
-            fontSize: 10 ,// 缩小坐标轴名称字体
+            fontSize: 10,
             padding:-10,
           },
           axisLabel: {
-            formatter: value => value + ' mg/L',
-            fontSize: 10 ,// 缩小坐标轴标签字体
+            formatter: value => value.toFixed(2) + ' mg/L',
+            fontSize: 10,
           },
           splitLine: {
             lineStyle: {
@@ -230,7 +276,7 @@ export default {
           data: data.map(d => d.name),
           axisLabel: {
             formatter: value => value,
-            fontSize: 10 // 缩小坐标轴标签字体
+            fontSize: 10
           }
         },
         series: [{
@@ -240,8 +286,8 @@ export default {
           label: {
             show: true,
             position: 'right',
-            formatter: params => params.value + ' mg/L',
-            fontSize: 9, // 缩小数据标签字体
+            formatter: params => params.value.toFixed(2) + ' mg/L',
+            fontSize: 9,
             rotate:15
           },
           barWidth: '60%',

+ 38 - 41
src/components/detectionStatistics/irrigationstatistics.vue

@@ -1,58 +1,56 @@
 <template>
   <div class="boxplot-container">
-
     <div class="chart-container">
       <div class="header">
-      <div class="chart-title">灌溉水重金属浓度统计箱线图</div>
-      <p>展示各重金属浓度的分布特征(最小值、四分位数、中位数、最大值)</p>
-      <p class="sample-subtitle">样本来源:{{ totalPoints }}个数据</p>
-    </div>
+        <div class="chart-title">灌溉水重金属浓度统计箱线图</div>
+        <p>展示各重金属浓度的分布特征(最小值、四分位数、中位数、最大值)</p>
+        <p class="sample-subtitle">样本来源:{{ totalPoints }}个数据</p>
+      </div>
 
-    <div v-if="isLoading" class="loading-state">
-      <span class="spinner"></span> 数据加载中...
-    </div>
+      <div v-if="isLoading" class="loading-state">
+        <span class="spinner"></span> 数据加载中...
+      </div>
 
-    <div v-else-if="error" class="error-state">
-      ❌ 加载失败:{{ error.message }}
-    </div>
+      <div v-else-if="error" class="error-state">
+        ❌ 加载失败:{{ error.message }}
+      </div>
 
-    <div v-else>
-      <div class="chart-wrapper">
-        <v-chart :option="chartOption" autoresize />
+      <div v-else>
+        <div class="chart-wrapper">
+          <v-chart :option="chartOption" autoresize />
+        </div>
       </div>
     </div>
-    </div>
-    
   </div>
 </template>
 
 <script>
 import * as echarts from 'echarts'
 import VChart from 'vue-echarts'
-import axios from 'axios'
-import { ref, onMounted ,computed} from 'vue'
+import { api8000 } from '@/utils/request' // 导入 api8000 实例
+import { ref, onMounted, computed } from 'vue'
 
 export default {
   components: { VChart },
   setup() {
     // -------- 基本状态 --------
-    const apiUrl = ref('http://localhost:8000/api/vector/stats/water_sampling_data')
+    const apiUrl = ref('/api/vector/stats/water_sampling_data') // 修改为相对路径
     const apiTimestamp = ref(null)
-    const sampleCount = ref(0)
     const statsData = ref({}); 
     const chartOption = ref({})
     const isLoading = ref(true)
     const error = ref(null)
-    const stats = null;
+    
+    // 样本数统计(从预统计数据中获取)
     const totalPoints = computed(() => {
       const firstMetalKey = heavyMetals[0]?.key;
       return statsData.value[firstMetalKey]?.count || 0;
     })
 
-    // 关键:缓存每个品类的统计量(与 x 轴顺序一致)
+    // 缓存每个品类的统计量(与 x 轴顺序一致)
     const statsByIndex = ref([])
 
-    // -------- 配置:金属字段(保持你原样) --------
+    // -------- 配置:金属字段 --------
     const heavyMetals = [
       { key: 'cr_concentration', name: '铬 (Cr)', color: '#FF9800' },
       { key: 'as_concentration', name: '砷 (As)', color: '#4CAF50' },
@@ -60,13 +58,14 @@ export default {
       { key: 'hg_concentration', name: '汞 (Hg)', color: '#2196F3' },
       { key: 'pb_concentration', name: '铅 (Pb)', color: '#F44336' }
     ]
-    // -------- 构建箱线数据(保留你自己的顺序) --------
+
+    // -------- 构建箱线数据 --------
     const buildBoxplotData = () => {
-      const xAxisData = heavyMetals.map(m => m.name); // 保持x轴顺序与配置一致
+      const xAxisData = heavyMetals.map(m => m.name);
 
       // 缓存每个重金属的统计量(用于tooltip)
       statsByIndex.value = heavyMetals.map(metal => {
-       const stat = statsData.value[metal.key] || {}; // 从接口数据中取当前金属的统计
+        const stat = statsData.value[metal.key] || {};
         return {
           key: metal.key,
           name: metal.name,
@@ -79,25 +78,24 @@ export default {
         };
       });
 
-      // 构建ECharts箱线图所需的二维数组格式 [[min, q1, median, q3, max], ...]
+      // 构建ECharts箱线图数据
       const data = statsByIndex.value.map(s => {
         if (s.min === undefined || s.min === null) {
-          return [null, null, null, null, null]; // 无数据时返回空数组
+          return [null, null, null, null, null];
         }
         return [s.min, s.q1, s.median, s.q3, s.max];
       });
 
-       return { xAxisData, data };
+      return { xAxisData, data };
     };
 
     // -------- 初始化图表 --------
     const initChart = () => {
-      const { xAxisData, data } = buildBoxplotData(stats)
+      const { xAxisData, data } = buildBoxplotData();
 
       chartOption.value = {
         tooltip: {
           trigger: 'item',
-          // 关键:不从 params.data 取,直接读我们缓存的原始统计值,彻底避免被内部处理影响
           formatter: (params) => {
             const s = statsByIndex.value[params.dataIndex]
             if (!s || s.min === null) {
@@ -151,22 +149,21 @@ export default {
     // -------- 拉取接口并绘图 --------
     onMounted(async () => {
       try {
-        //log('发起API请求...')
-        const response = await axios.get(apiUrl.value);
-        statsData.value = response.data.data; 
-        apiTimestamp.value = new Date().toLocaleString()
-          initChart()
+        // 使用api8000替代axios
+        const response = await api8000.get(apiUrl.value);
+        statsData.value = response.data; 
+        apiTimestamp.value = new Date().toLocaleString();
+        initChart();
       } catch (err) {
-        error.value = err
-        isLoading.value = false
-        console.error('接口请求失败:', err)
+        error.value = err;
+        isLoading.value = false;
+        console.error('接口请求失败:', err);
       }
     })
 
     return {
       apiUrl,
       apiTimestamp,
-      sampleCount,
       chartOption,
       isLoading,
       error,
@@ -222,7 +219,7 @@ export default {
 .chart-header {
   display: flex;
   justify-content: space-between;
-  align-items: flex-start; /**与顶部对齐 */
+  align-items: flex-start;
   margin-bottom: 15px;
 }
 

+ 19 - 24
src/components/irrpollution/crossSectionSamplelineData.vue

@@ -50,6 +50,7 @@
 <script setup>
 import { ref, reactive, onMounted } from 'vue'
 import { wgs84togcj02 } from 'coordtransform';
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
 // 状态管理
 const error = ref(null)
@@ -62,33 +63,27 @@ const state = reactive({
 // 从接口获取数据并处理
 const fetchData = async () => {
   try {
-    // 接口地址
-    const apiUrl = 'http://localhost:8000/api/vector/export/all?table_name=cross_section'
+    loading.value = true;
+    error.value = null;
     
-    // 发起请求
-    const response = await fetch(apiUrl)
-    if (!response.ok) {
-      throw new Error(`接口请求失败: ${response.status}`)
-    }
-    
-    // 解析GeoJSON数据
-    const geoJsonData = await response.json()
+    // 使用 api8000 实例获取数据
+    const response = await api8000.get('/api/vector/export/all?table_name=cross_section');
     
     // 处理数据
-    state.excelData = geoJsonData.features
+    state.excelData = response.data.features
       .map(feature => {
-        const props = feature.properties
+        const props = feature.properties;
         // 转换经纬度
-        const lng = Number(props.longitude)
-        const lat = Number(props.latitude)
+        const lng = Number(props.longitude);
+        const lat = Number(props.latitude);
         
         if (isNaN(lat) || isNaN(lng)) {
-          console.error('无效经纬度数据:', props)
-          return null
+          console.error('无效经纬度数据:', props);
+          return null;
         }
         
         // WGS84转GCJ02坐标
-        const [gcjLng, gcjLat] = wgs84togcj02(lng, lat)
+        const [gcjLng, gcjLat] = wgs84togcj02(lng, lat);
         
         return {
           id: props.id,
@@ -98,21 +93,21 @@ const fetchData = async () => {
           cdValue: props.cd_concentration !== undefined ? props.cd_concentration : '未知',
           latitude: gcjLat,
           longitude: gcjLng
-        }
+        };
       })
-      .filter(item => item !== null) // 过滤无效数据
+      .filter(item => item !== null); // 过滤无效数据
       
   } catch (err) {
-    error.value = `数据加载失败: ${err.message}`
-    console.error('数据处理错误:', err)
+    error.value = `数据加载失败: ${err.message || '未知错误'}`;
+    console.error('数据处理错误:', err);
   } finally {
-    loading.value = false
+    loading.value = false;
   }
 }
 
 // 生命周期
 onMounted(async () => {
-  await fetchData()
+  await fetchData();
 })
 
 </script>
@@ -213,4 +208,4 @@ onMounted(async () => {
     overflow-x: auto;
   }
 }
-</style>
+</style>

+ 15 - 15
src/components/irrpollution/crossSetionData1.vue

@@ -15,15 +15,16 @@
     <div v-else ref="chartContainer" class="chart-container"></div>
   </div>
 </template>
-<!--按河流划分计算的柱状图-->
+
 <script setup>
 import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'
 import { wgs84togcj02 } from 'coordtransform';
 import * as echarts from 'echarts'
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
 // 状态管理
 const error = ref(null)
-const loading = ref(true)  // 新增加载状态
+const loading = ref(true)
 const chartContainer = ref(null)
 let chart = null
 
@@ -35,20 +36,14 @@ const state = reactive({
 // 从接口获取数据并初始化
 const initData = async () => {
   try {
-    // 接口地址
-    const apiUrl = 'http://localhost:8000/api/vector/export/all?table_name=cross_section'
-    
-    // 发起接口请求
-    const response = await fetch(apiUrl)
-    if (!response.ok) {
-      throw new Error(`接口请求失败:HTTP ${response.status}`)
-    }
+    loading.value = true;
+    error.value = null;
     
-    // 解析GeoJSON数据
-    const geoJsonData = await response.json()
+    // 使用 api8000 实例获取数据
+    const response = await api8000.get('/api/vector/export/all?table_name=cross_section');
     
     // 处理每个Feature的properties
-    state.excelData = geoJsonData.features
+    state.excelData = response.data.features
       .map(feature => {
         const props = feature.properties
         
@@ -80,7 +75,7 @@ const initData = async () => {
     calculateRiverAvg()
     
   } catch (err) {
-    error.value = `数据加载失败:${err.message}`
+    error.value = `数据加载失败:${err.message || '未知错误'}`
     console.error('数据处理错误:', err)
   } finally {
     loading.value = false
@@ -158,7 +153,12 @@ const updateChart = () => {
     xAxis: {
       type: 'category',
       data: rivers,
-      axisLabel: { interval: 0, fontSize: 15 }
+      axisLabel: { 
+        interval: 0, 
+        fontSize: 15,
+        rotate: 30, // 添加旋转以避免标签重叠
+        margin: 15  // 增加间距
+      }
     },
     yAxis: {
       type: 'value',

+ 68 - 16
src/components/irrpollution/crossSetionData2.vue

@@ -1,15 +1,30 @@
 <template>
   <!-- 柱状图容器 -->
   <div class="chart-page">
-    <div ref="chartContainer" class="chart-container"></div>
+    <!-- 加载状态 -->
+    <div v-if="loading" class="loading-indicator">
+      <div class="spinner"></div>
+      <p>数据加载中...</p>
+    </div>
+    
+    <!-- 错误提示 -->
+    <div v-else-if="error" class="error-message">
+      <i class="fa fa-exclamation-circle"></i> {{ error }}
+    </div>
+    
+    <!-- 图表容器 -->
+    <div v-else ref="chartContainer" class="chart-container"></div>
   </div>
 </template>
 
 <script setup>
 import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'
 import * as echarts from 'echarts'
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
 // 状态管理
+const error = ref(null)
+const loading = ref(true) // 添加加载状态
 const chartContainer = ref(null)
 let chart = null
 const state = reactive({
@@ -17,21 +32,17 @@ const state = reactive({
   districtAvgData: [] // 存储按区县分组后的平均数据
 })
 
-// 从接口初始化数据(核心修改:异步获取 + 严格数据解析
+// 从接口初始化数据(使用 api8000 实例
 const initData = async () => {
   try {
-    // 接口地址(注意修正空格:localhost)
-    const apiUrl = 'http://localhost:8000/api/vector/export/all?table_name=cross_section'
-    const response = await fetch(apiUrl)
-    
-    if (!response.ok) {
-      throw new Error(`接口请求失败(状态码:${response.status})`)
-    }
+    loading.value = true;
+    error.value = null;
     
-    const geoJson = await response.json()
+    // 使用 api8000 实例获取数据
+    const response = await api8000.get('/api/vector/export/all?table_name=cross_section');
     
     // 逐行解析Feature,确保每个样本都被处理
-    state.excelData = geoJson.features.map(feature => {
+    state.excelData = response.data.features.map(feature => {
       const props = feature.properties || {}
       
       // 强制转换Cd浓度为数值(处理异常值)
@@ -51,8 +62,10 @@ const initData = async () => {
     
     calculateDistrictAvg() // 计算区县平均值
   } catch (err) {
+    error.value = `数据加载失败: ${err.message || '未知错误'}`;
     console.error('数据加载失败:', err)
-    // 可扩展:全局错误提示
+  } finally {
+    loading.value = false;
   }
 }
 
@@ -128,7 +141,9 @@ const updateChart = () => {
       data: districts,
       axisLabel: {
         interval: 0,
-        fontSize: 15
+        fontSize: 15,
+        rotate: 30, // 添加旋转以避免标签重叠
+        margin: 15 // 增加间距
       }
     },
     yAxis: {
@@ -193,7 +208,7 @@ onBeforeUnmount(() => {
 })
 </script>
 
-<style>
+<style scoped>
 .chart-page {
   width: 100%;
   margin: 0 auto 24px;
@@ -205,8 +220,45 @@ onBeforeUnmount(() => {
 }
 .chart-container {
   width: 100%;
-  height: 500px; 
-  
+  height: 550px; /* 增加高度以容纳旋转的标签 */
+}
+
+/* 加载状态样式 */
+.loading-indicator {
+  text-align: center;
+  padding: 40px 0;
+  color: #6b7280;
 }
 
+.spinner {
+  width: 40px;
+  height: 40px;
+  margin: 0 auto 16px;
+  border: 4px solid #e5e7eb;
+  border-top: 4px solid #3b82f6;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+/* 错误提示样式 */
+.error-message {
+  color: #dc2626;
+  background-color: #fee2e2;
+  padding: 12px 16px;
+  border-radius: 6px;
+  margin-bottom: 16px;
+  display: flex;
+  align-items: center;
+  font-weight: 500;
+}
+
+.error-message i {
+  margin-right: 8px;
+  font-size: 18px;
+}
 </style>

+ 5 - 7
src/components/irrpollution/crosssectionmap.vue

@@ -8,6 +8,7 @@
 import { ref, onMounted } from 'vue';
 import L from 'leaflet';
 import 'leaflet/dist/leaflet.css';
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
 const mapContainer = ref(null);
 
@@ -96,14 +97,11 @@ onMounted(() => {
           }).addTo(map);
 
           // ========================
-          // 从接口加载数据(替换本地rawData
+          // 从接口加载数据(使用 api8000 实例
           // ========================
-          fetch('http://localhost:8000/api/vector/export/all?table_name=cross_section')
-            .then(res => {
-              if (!res.ok) throw new Error(`数据加载失败:HTTP ${res.status}`);
-              return res.json();
-            })
-            .then(geoJSONData => {
+          api8000.get('/api/vector/export/all?table_name=cross_section')
+            .then(response => {
+              const geoJSONData = response.data;
               // 提取GeoJSON的features.properties作为数据项
               const dataItems = geoJSONData.features.map(feature => feature.properties);
               console.log('✅ 接口数据加载完成,要素数:', dataItems.length);

+ 6 - 8
src/components/irrpollution/irrwatermap.vue

@@ -8,6 +8,7 @@
 import { ref, onMounted } from 'vue';
 import L from 'leaflet';
 import 'leaflet/dist/leaflet.css';
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
 const mapContainer = ref(null);
 
@@ -87,14 +88,11 @@ onMounted(() => {
           }).addTo(map);
 
           // ========================
-          // 修复核心:加载采样+检测数据(单接口
+          // 修复核心:加载采样+检测数据(使用 api8000 实例
           // ========================
-          fetch('http://localhost:8000/api/vector/export/all?table_name=water_sampling_data')
-            .then(res => {
-              if (!res.ok) throw new Error(`数据加载失败:HTTP ${res.status}`);
-              return res.json();
-            })
-            .then(geoJSONData => { // geoJSONData 是 FeatureCollection 结构
+          api8000.get('/api/vector/export/all?table_name=water_sampling_data')
+            .then(response => {
+              const geoJSONData = response.data;
               console.log('✅ 采样数据加载完成,要素数:', geoJSONData.features.length);
                 
               let markerCount = 0;
@@ -251,7 +249,7 @@ onMounted(() => {
 ::v-deep .popup-container {
   min-width: 180px;
   max-width: 220px;
-  padding: 10px;/**内边距 */
+  padding: 10px;
   font-family: "Microsoft YaHei", sans-serif;
 }
 

+ 2 - 2
src/components/irrpollution/tencentMapView.vue

@@ -71,7 +71,7 @@ const loadSDK = () => {
   });
 };
 
-const WATER_SAMPLING_API='http://localhost:3000/table/Water_sampling_data';
+const WATER_SAMPLING_API='https://www.soilgd.com:3000/table/Water_sampling_data';
 const fetchWaterSamplingData = async ()=>{
   try{
     const response = await axios.get(WATER_SAMPLING_API);
@@ -397,7 +397,7 @@ const updateMarkers = () => {
   markersLayer.setGeometries(geometries);
 };
 
-const API_BASE_URL = 'http://localhost:3000/table/Water_assay_data'; 
+const API_BASE_URL = 'https://www.soilgd.com:3000/table/Water_assay_data'; 
 
 // 新增Marker点击事件处理
 const handleMarkerClick = async(e) => {

+ 222 - 0
src/components/irrpollution/waterassaydata1.vue

@@ -0,0 +1,222 @@
+<template>
+  <div class="boxplot-container">
+    <div ref="chartRef" style="width: 100%; height: 500px;"></div>
+  </div>
+</template>
+<!--各种重金属的箱图-->
+<script setup lang="ts">
+import * as echarts from 'echarts';
+import { ref, onMounted, onUnmounted } from 'vue';
+import axios from 'axios';
+
+// 明确定义数据类型
+interface HeavyMetalData {
+  sampleId: string;
+  Cr: number | null;
+  As: number | null;
+  Cd: number | null;
+  Hg: number | null;
+  Pb: number | null;
+}
+
+const METALS = ['Cr', 'As', 'Cd', 'Hg', 'Pb'] as const;
+type MetalType = typeof METALS[number];
+
+const METAL_LABELS: Record<MetalType, string> = {
+  Cr: '铬(Cr)',
+  As: '砷(As)',
+  Cd: '镉(Cd)',
+  Hg: '汞(Hg)',
+  Pb: '铅(Pb)'
+};
+
+// 图表变量
+const chartRef = ref<HTMLElement | null>(null);
+const chartInstance = ref<echarts.ECharts | null>(null);
+const metalData = ref<HeavyMetalData[]>([]);
+let resizeHandler: (() => void) | null = null; // 用于存储resize处理函数
+
+// 数据清洗函数
+const cleanData = (rawValue: any): number | null => {
+  if (typeof rawValue === 'string') {
+    const num = parseFloat(rawValue);
+    return isNaN(num) || num < 0 ? null : num;
+  }
+  return typeof rawValue === 'number' && rawValue >= 0 ? rawValue : null;
+};
+
+// 修复后的四分位数计算算法
+const calculateBoxplotStats = (values: number[]): [number, number, number, number, number] | null => {
+  if (values.length < 5) return null; // 至少需要5个数据点才能生成有效的箱线图
+  
+  // 升序排序
+  const sorted = [...values].sort((a, b) => a - b);
+  const n = sorted.length;
+
+  // 正确的分位位置计算
+  const quantile = (p: number) => {
+    const pos = (n + 1) * p;
+    const lowerIndex = Math.max(0, Math.min(n - 1, Math.floor(pos) - 1));
+    const fraction = pos - Math.floor(pos);
+    
+    if (lowerIndex >= n - 1) return sorted[n - 1];
+    return sorted[lowerIndex] + fraction * (sorted[lowerIndex + 1] - sorted[lowerIndex]);
+  };
+
+  return [
+    sorted[0],               // 最小值
+    quantile(0.25),          // Q1
+    quantile(0.5),           // 中位数
+    quantile(0.75),          // Q3
+    sorted[n - 1]            // 最大值
+  ];
+};
+
+// 渲染图表
+const renderBoxplot = () => {
+  if (!chartRef.value || metalData.value.length === 0) return;
+  
+  // 移除旧的resize监听器
+  if (resizeHandler) {
+    window.removeEventListener('resize', resizeHandler);
+  }
+
+  // 分组收集每种金属的有效数值
+  const metalValues = Object.fromEntries(
+    METALS.map(metal => [
+      metal, 
+      metalData.value
+        .map(item => item[metal])
+        .filter((val): val is number => val !== null)
+    ])
+  ) as Record<MetalType, number[]>;
+
+  // 准备箱线图数据
+  const validBoxplotData: ([number, number, number, number, number] | null)[] = 
+    METALS.map(metal => calculateBoxplotStats(metalValues[metal]));
+
+  // ECharts配置
+  const option: echarts.EChartsOption = {
+    backgroundColor: '#FFFFFF',
+    title: {
+      text: '重金属浓度分布箱线图',
+      left: 'center',
+      textStyle: { color: '#333', fontSize: 16 }
+    },
+    tooltip: {
+      trigger: 'item',
+      formatter: (params: any) => {
+        const metalIndex = params.dataIndex;
+        const metal = METALS[metalIndex];
+        const stats = validBoxplotData[metalIndex];
+        
+        // 处理空数据情况(修复图片中的null错误)
+        if (stats === null || stats[0] === null) {
+          return `<span style="color:#ff0000">${METAL_LABELS[metal]}数据不足,无法生成统计值</span>`;
+        }
+        
+        // 类型安全解构(确保所有值都是number类型)
+        const [min, q1, median, q3, max] = stats;
+        
+        return `
+          <b>${METAL_LABELS[metal]}</b><br/>
+          最小值: ${min.toFixed(4)} mg/L<br/>
+          下四分位: ${q1.toFixed(4)} mg/L<br/>
+          中位数: ${median.toFixed(4)} mg/L<br/>
+          上四分位: ${q3.toFixed(4)} mg/L<br/>
+          最大值: ${max.toFixed(4)} mg/L
+        `;
+      }
+    },
+    xAxis: {
+      type: 'category',
+      data: METALS.map(metal => METAL_LABELS[metal]),
+      axisLabel: { color: '#333', interval: 0 }
+    },
+    yAxis: {
+      type: 'value',
+      name: '浓度(mg/L)',
+      nameTextStyle: { color: '#333' },
+      axisLabel: { 
+        color: '#333',
+        formatter: (value: number) => value.toFixed(4)
+      }
+    },
+    series: [{
+      type: 'boxplot',
+      // 过滤无效数据(解决ts 2322错误)
+      data: validBoxplotData.filter(arr => arr !== null) as [number, number, number, number, number][],
+      itemStyle: {
+        color: '#4285F4',
+        borderWidth: 1.5
+      },
+      emphasis: {
+        itemStyle: {
+          borderColor: '#333',
+          borderWidth: 2
+        }
+      }
+    }]
+  };
+
+  // 初始化图表
+  if (chartInstance.value) {
+    chartInstance.value.dispose();
+  }
+  chartInstance.value = echarts.init(chartRef.value);
+  chartInstance.value.setOption(option);
+  
+  // 响应式处理
+  resizeHandler = () => chartInstance.value?.resize();
+  window.addEventListener('resize', resizeHandler);
+};
+
+// 数据加载
+const loadData = async () => {
+  try {
+    const response = await axios.get<any[]>(
+      'https://www.soilgd.com:3000/table/Water_assay_data',
+      { timeout: 5000 }
+    );
+    
+    // 数据转换与过滤
+    metalData.value = response.data
+      .map(item => ({
+        sampleId: String(item.sampleId),
+        Cr: cleanData(item.Cr),
+        As: cleanData(item.As),
+        Cd: cleanData(item.Cd),
+        Hg: cleanData(item.Hg),
+        Pb: cleanData(item.Pb)
+      }))
+      // 修复:允许部分有效数据
+      .filter(item => METALS.some(metal => item[metal] !== null));
+    
+    renderBoxplot();
+  } catch (error) {
+    console.error('数据加载失败:', error);
+    alert('数据加载错误,请查看控制台日志');
+  }
+};
+
+onMounted(() => loadData());
+onUnmounted(() => {
+  // 清理资源
+  if (resizeHandler) {
+    window.removeEventListener('resize', resizeHandler);
+  }
+  chartInstance.value?.dispose();
+});
+</script>
+
+<style scoped>
+.boxplot-container {
+  width: 100%;
+  max-width: 1000px;
+  margin: 20px auto;
+  padding: 20px;
+  background: white;
+  border-radius: 8px;
+  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
+}
+</style>

+ 49 - 9
src/components/irrpollution/waterassaydata2.vue

@@ -9,10 +9,10 @@
 <script setup>
 import { ref, onMounted, onUnmounted } from 'vue';
 import * as echarts from 'echarts';
-import axios from 'axios';
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
-// ========== 接口配置(仅保留新接口) ==========
-const NEW_API = 'http://localhost:8000/api/vector/export/all?table_name=water_sampling_data';
+// ========== 接口配置(使用 api8000 实例) ==========
+const TABLE_NAME = 'water_sampling_data';
 
 // ========== 配置项(调整字段适配新接口) ==========
 // 排除的非重金属字段(适配新接口字段名)
@@ -185,6 +185,7 @@ const initChart = ({ regions, series, totalSamples }) => {
   myChart = echarts.init(chartRef.value);
   const option = {
     title: { 
+      text: '各地区重金属含量平均值',
       left: 'center',
       subtext: `数据来源: ${totalSamples}个有效检测样本`,
       subtextStyle: {
@@ -252,13 +253,47 @@ const initChart = ({ regions, series, totalSamples }) => {
   myChart.setOption(option);
 };
 
-// ========== 生命周期钩子(适配新接口) ==========
+// ========== 生命周期钩子(使用 api8000 实例) ==========
 onMounted(async () => {
   try {
-    // 仅请求新接口
-    const response = await axios.get(NEW_API, { timeout: 10000 });
-    // 从GeoJSON中提取数据(features.properties)
-    const allData = response.data.features.map(feature => feature.properties);
+    loading.value = true;
+    error.value = '';
+    
+    // 使用 api8000 实例获取数据
+    const response = await api8000.get(`/api/vector/export/all?table_name=${TABLE_NAME}`);
+    
+    // 处理可能的字符串响应
+    let data = response.data;
+    if (typeof data === 'string') {
+      try {
+        // 替换 NaN 为 null
+        const cleanedData = data.replace(/\bNaN\b/g, 'null');
+        data = JSON.parse(cleanedData);
+      } catch (parseErr) {
+        throw new Error('接口返回的是字符串,但 JSON 解析失败');
+      }
+    }
+    
+    // 处理对象中的 NaN 值
+    if (typeof data === 'object' && data !== null) {
+      const replaceNaN = (obj) => {
+        for (const key in obj) {
+          if (typeof obj[key] === 'object' && obj[key] !== null) {
+            replaceNaN(obj[key]);
+          } else if (typeof obj[key] === 'number' && isNaN(obj[key])) {
+            obj[key] = null;
+          } else if (obj[key] === 'NaN') {
+            obj[key] = null;
+          }
+        }
+      };
+      replaceNaN(data);
+    }
+    
+    // 接口返回格式判断(GeoJSON或直接数组)
+    const allData = data.features 
+      ? data.features.map(f => f.properties) 
+      : data;
     
     // 处理数据并初始化图表
     initChart(processData(allData));
@@ -273,7 +308,10 @@ onMounted(async () => {
 // 响应式布局(保持不变)
 const resizeHandler = () => myChart && myChart.resize();
 onMounted(() => window.addEventListener('resize', resizeHandler));
-onUnmounted(() => window.removeEventListener('resize', resizeHandler));
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeHandler);
+  if (myChart) myChart.dispose();
+});
 </script>
 
 <style scoped>
@@ -290,6 +328,7 @@ onUnmounted(() => window.removeEventListener('resize', resizeHandler));
   min-height: 400px;
   background-color: white;
   border-radius: 8px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
 }
 .status {
   position: absolute;
@@ -300,6 +339,7 @@ onUnmounted(() => window.removeEventListener('resize', resizeHandler));
   background: rgba(255,255,255,0.8);
   border-radius: 4px;
   font-size: 16px;
+  z-index: 10;
 }
 .error { 
   color: #ff4d4f;

+ 197 - 0
src/components/irrpollution/waterassaydata3.vue

@@ -0,0 +1,197 @@
+<template>
+  <div class="heavy-metal-radar">
+    <h2 class="chart-title">重金属指标雷达图分析</h2>
+    <canvas ref="chartRef" class="chart-box"></canvas>
+    <div v-if="loading" class="status">数据加载中...</div>
+    <div v-else-if="error" class="status error">{{ error }}</div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted } from 'vue';
+import Chart from 'chart.js/auto';
+import axios from 'axios';
+
+// ========== 接口配置(和柱状图对齐) ==========
+const ASSAY_API = 'https://www.soilgd.com:3000/table/Water_assay_data'; // 复用柱状图的接口
+
+// ========== 配置项(模仿柱状图) ==========
+const EXCLUDE_FIELDS = [
+  'water_assay_ID', 'sample_code', 'assayer_ID', 'assay_time', 
+  'assay_instrument_model', 'water_sample_ID', 'pH'
+];
+const COLORS = ['#165DFF', '#36CFC9', '#722ED1']; // 雷达图三色
+
+// ========== 响应式数据 ==========
+const chartRef = ref(null);
+const loading = ref(true);
+const error = ref('');
+let radarChart = null;
+
+// ========== 数据处理:提取重金属指标 + 统计计算 ==========
+const processRadarData = (assayData) => {
+  // 1. 提取重金属字段(排除指定字段,且为数值类型)
+  const metals = Object.keys(assayData[0] || {})
+    .filter(key => !EXCLUDE_FIELDS.includes(key) && !isNaN(parseFloat(assayData[0][key])));
+
+  // 2. 计算每个重金属的统计值(均值、中位数、标准差)
+  const stats = metals.map(metal => {
+    const values = assayData.map(item => parseFloat(item[metal])).filter(v => !isNaN(v));
+    return {
+      mean: calculateMean(values),
+      median: calculateMedian(values),
+      std: calculateStdDev(values)
+    };
+  });
+
+  return { metals, stats };
+};
+
+// ========== 统计工具函数 ==========
+const calculateMean = (values) => {
+  if (values.length === 0) return 0;
+  return values.reduce((sum, val) => sum + val, 0) / values.length;
+};
+
+const calculateMedian = (values) => {
+  if (values.length === 0) return 0;
+  const sorted = [...values].sort((a, b) => a - b);
+  const mid = Math.floor(sorted.length / 2);
+  return sorted.length % 2 === 0 
+    ? (sorted[mid - 1] + sorted[mid]) / 2 
+    : sorted[mid];
+};
+
+const calculateStdDev = (values) => {
+  if (values.length <= 1) return 0;
+  const mean = calculateMean(values);
+  const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
+  return Math.sqrt(variance);
+};
+
+// ========== 初始化雷达图(Chart.js) ==========
+const initRadarChart = ({ metals, stats }) => {
+  if (!chartRef.value) return;
+  if (radarChart) radarChart.destroy();
+
+  const ctx = chartRef.value.getContext('2d');
+  radarChart = new Chart(ctx, {
+    type: 'radar',
+    data: {
+      labels: metals,
+      datasets: [
+        {
+          label: '均值',
+          data: stats.map(s => s.mean.toFixed(2)),
+          borderColor: COLORS[0],
+          backgroundColor: 'rgba(22, 93, 255, 0.1)',
+          pointRadius: 4,
+          borderWidth: 2
+        },
+        {
+          label: '中位数',
+          data: stats.map(s => s.median.toFixed(2)),
+          borderColor: COLORS[1],
+          backgroundColor: 'rgba(54, 207, 201, 0.1)',
+          pointRadius: 4,
+          borderWidth: 2
+        },
+        {
+          label: '标准差',
+          data: stats.map(s => s.std.toFixed(2)),
+          borderColor: COLORS[2],
+          backgroundColor: 'rgba(114, 46, 209, 0.1)',
+          pointRadius: 4,
+          borderWidth: 2
+        }
+      ]
+    },
+    options: {
+      responsive: true,
+      maintainAspectRatio: false,
+      scales: {
+        r: {
+          beginAtZero: true,
+          ticks: { display: false },
+          pointLabels: { font: { size: 12, weight: 'bold' } },
+          grid: { color: 'rgba(0,0,0,0.05)' },
+          angleLines: { color: 'rgba(0,0,0,0.1)' }
+        }
+      },
+      plugins: {
+        legend: { position: 'bottom' },
+        tooltip: {
+          callbacks: {
+            label: (ctx) => `${ctx.dataset.label}: ${ctx.raw} mg/L`
+          }
+        }
+      }
+    }
+  });
+};
+
+// ========== 生命周期钩子(和柱状图对齐) ==========
+onMounted(async () => {
+  try {
+    // 【关键】和柱状图一样,axios 请求 **不携带凭证**(withCredentials: false,默认就是false)
+    const assayRes = await axios.get(ASSAY_API, { timeout: 10000, withCredentials:false });
+    const processed = processRadarData(assayRes.data);
+    
+    if (processed.metals.length === 0) {
+      throw new Error('未检测到有效重金属指标');
+    }
+    
+    initRadarChart(processed);
+  } catch (err) {
+    error.value = '数据加载失败: ' + (err.message || '未知错误');
+    console.error('接口错误:', err);
+  } finally {
+    loading.value = false;
+  }
+});
+
+// 响应式resize(模仿柱状图)
+const resizeHandler = () => radarChart && radarChart.resize();
+onMounted(() => window.addEventListener('resize', resizeHandler));
+onUnmounted(() => window.removeEventListener('resize', resizeHandler));
+</script>
+
+<style scoped>
+.heavy-metal-radar {
+  width: 100%;
+  max-width: 800px;
+  margin: 20px auto;
+  position: relative;
+  padding-top: 0;
+  background-color: white;
+  border-radius: 8px;
+}
+.chart-box {
+  width: 100%;
+  min-height: 350px;
+  max-height: 600px;
+  height: auto;
+  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
+}
+.chart-title {
+  text-align: center;   /* 水平居中 */
+  font-size: 18px;      /* 字体大小 */
+  font-weight: 600;     /* 加粗 */
+  color: #333;          /* 字体颜色 */
+  margin: 10px 0;     /* 底部间距,避免和图表贴紧 */
+}
+.status {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  padding: 15px;
+  background: rgba(255,255,255,0.8);
+  border-radius: 4px;
+  z-index: 10;
+}
+.error { 
+  color: #ff4d4f;
+  font-weight: bold;
+}
+</style>

+ 340 - 0
src/components/irrpollution/waterassaydata4.vue

@@ -0,0 +1,340 @@
+<template>
+  <div class="region-average-chart">
+    <!-- 重金属选择器 -->
+    <div class="metal-selector" v-if="!loading && !error && metals.length > 0">
+      <label for="metal-select">选择重金属:</label>
+      <select id="metal-select" v-model="selectedMetal">
+        <option v-for="metal in metals" :key="metal" :value="metal">{{ metal }}</option>
+      </select>
+    </div>
+    
+    <!-- 图表容器 -->
+    <div ref="chartRef" class="chart-box"></div>
+    
+    <!-- 状态信息 -->
+    <div v-if="loading" class="status">数据加载中...</div>
+    <div v-else-if="error" class="status error">{{ error }}</div>
+    <div v-else-if="metals.length === 0" class="status">没有有效的重金属数据</div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted, watch } from 'vue';
+import * as echarts from 'echarts';
+import axios from 'axios';
+
+// ========== 接口配置 ==========
+const SAMPLING_API = 'https://www.soilgd.com:3000/table/Water_sampling_data';
+const ASSAY_API = 'https://www.soilgd.com:3000/table/Water_assay_data';
+
+// ========== 配置项 ==========
+const EXCLUDE_FIELDS = [
+  'water_assay_ID', 'sample_code', 'assayer_ID', 'assay_time', 
+  'assay_instrument_model', 'water_sample_ID', 'pH'
+];
+const COLORS = [
+  '#FF6B6B', '#4ECDC4', '#FFD166', '#6A4C93', '#1982C4',
+  '#FF9F1C', '#2EC4B6', '#E71D36', '#3A86FF', '#FF006E'
+];
+
+// 韶关市下属行政区划
+const SG_REGIONS = [
+  '浈江区', '武江区', '曲江区', '乐昌市', 
+  '南雄市', '始兴县', '仁化县', '翁源县', 
+  '新丰县', '乳源瑶族自治县'
+];
+
+// ========== 响应式数据 ==========
+const chartRef = ref(null);
+const loading = ref(true);
+const error = ref('');
+// 修复 selectedMetal 未定义问题:提前声明变量
+const selectedMetal = ref(''); 
+const metals = ref([]);
+const chartData = ref(null);
+let myChart = null;
+
+// ========== 地区提取函数 ==========
+const extractRegion = (location) => {
+  if (!location || typeof location !== 'string') return null;
+
+  // 1. 精确匹配官方区县名称
+  const officialMatch = SG_REGIONS.find(region => 
+    location.includes(region)
+  );
+  if (officialMatch) return officialMatch;
+
+  // 2. 处理嵌套格式(如"韶关市-浈江区")
+  const nestedMatch = location.match(/(韶关市)([^市]+?[区市县])/);
+  if (nestedMatch && nestedMatch[2]) {
+    const region = nestedMatch[2].replace("韶关市", "").trim();
+    // 验证是否为合法区县
+    const validRegion = SG_REGIONS.find(r => r.includes(region));
+    if (validRegion) return validRegion;
+  }
+
+  // 3. 特殊格式处理(如"韶关市浈江区")
+  const shortMatch = location.match(/韶关市([区市县][^市]{2,5})/);
+  if (shortMatch && shortMatch[1]) return shortMatch[1];
+
+  // 4. 修正常见拼写错误
+  if (location.includes('乐昌')) return '乐昌市';
+  if (location.includes('乳源')) return '乳源瑶族自治县';
+
+  console.warn(`⚠️ 未识别地区: ${location}`);
+  return '未知区县';
+};
+
+// ========== 数据处理流程 ==========
+const processMergedData = (samplingData, assayData) => {
+  // 1. 构建采样点ID到区县的映射
+  const regionMap = new Map();
+  const uniqueSampleIds = new Set();
+  
+  samplingData.forEach(item => {
+    const region = extractRegion(item.sampling_location || '');
+    if (region && region !== '未知区县') {
+      regionMap.set(item.water_sample_ID, region);
+    }
+  });
+
+  // 2. 关联重金属数据与区县
+  const mergedData = assayData.map(item => ({
+    ...item,
+    region: regionMap.get(item.water_sample_ID) || '未知区县'
+  }));
+
+  // 3. 识别重金属字段
+  const detectedMetals = Object.keys(mergedData[0] || {})
+    .filter(key => !EXCLUDE_FIELDS.includes(key) && !isNaN(parseFloat(mergedData[0][key])));
+  
+  metals.value = detectedMetals;
+  if (detectedMetals.length > 0 && !selectedMetal.value) {
+    selectedMetal.value = detectedMetals[0];
+  }
+
+  // 4. 按区县分组统计
+  const regionGroups = {};
+  
+  mergedData.forEach(item => {
+    const region = item.region;
+    const sampleId = item.water_sample_ID;
+    
+    if (sampleId) uniqueSampleIds.add(sampleId);
+    
+    // 初始化区县数据
+    if (!regionGroups[region]) {
+      regionGroups[region] = {};
+      detectedMetals.forEach(metal => {
+        regionGroups[region][metal] = { sum: 0, count: 0 };
+      });
+    }
+
+    // 统计重金属数据
+    detectedMetals.forEach(metal => {
+      const val = parseFloat(item[metal]);
+      if (!isNaN(val)) {
+        regionGroups[region][metal].sum += val;
+        regionGroups[region][metal].count++;
+      }
+    });
+  });
+
+  // 5. 构建扇形图数据
+  const pieSeriesData = detectedMetals.map(metal => {
+    // 该重金属在各区县的平均浓度总和
+    let totalAverage = 0;
+    
+    // 收集各区县该重金属的平均值
+    const regionAverages = SG_REGIONS.map(region => {
+      if (!regionGroups[region]) return null;
+      
+      const group = regionGroups[region][metal];
+      const avg = group.count ? group.sum / group.count : 0;
+      totalAverage += avg;
+      
+      return { 
+        name: region,
+        value: avg
+      };
+    }).filter(Boolean);
+
+    // 计算占比
+    const seriesData = regionAverages.map(item => ({
+      name: item.name,
+      value: totalAverage > 0 ? (item.value / totalAverage) * 100 : 0,
+      rawValue: item.value // 保留原始浓度值用于显示
+    }));
+
+    return {
+      metal,
+      seriesData
+    };
+  });
+
+  return {
+    regions: SG_REGIONS,
+    pieSeriesData,
+    totalSamples: uniqueSampleIds.size
+  };
+};
+
+// ========== 初始化/更新图表 ==========
+const initChart = () => {
+  if (!chartRef.value || !selectedMetal.value || !chartData.value) return;
+  
+  if (myChart) myChart.dispose();
+  myChart = echarts.init(chartRef.value);
+
+  // 获取当前重金属的数据
+  const currentMetalData = chartData.value.pieSeriesData.find(
+    item => item.metal === selectedMetal.value
+  );
+
+  if (!currentMetalData) return;
+
+  const option = {
+    title: { 
+      text: `韶关市${selectedMetal.value}平均浓度区域占比`, 
+      left: 'center',
+      subtext: `数据来源: ${chartData.value.totalSamples}个有效检测样本`
+    },
+    tooltip: {
+      trigger: 'item',
+      formatter: function(params) {
+        return `${params.name}<br/>
+                ${selectedMetal.value}: ${params.data.rawValue.toFixed(4)} mg/L<br/>
+                占比: ${params.percent}%`;
+      }
+    },
+    legend: {
+      orient: 'vertical',
+      right: 10,
+      top: 'center',
+      data: currentMetalData.seriesData.map(item => item.name)
+    },
+    series: [
+      {
+        name: selectedMetal.value,
+        type: 'pie',
+        radius: ['35%', '65%'],
+        center: ['45%', '50%'],
+        avoidLabelOverlap: false,
+        itemStyle: {
+          borderRadius: 10,
+          borderColor: '#fff',
+          borderWidth: 2
+        },
+        label: {
+          show: false,
+          position: 'center'
+        },
+        emphasis: {
+          label: {
+            show: true,
+            fontSize: '16',
+            fontWeight: 'bold',
+            formatter: '{b}\n{c}%'
+          }
+        },
+        labelLine: {
+          show: false
+        },
+        data: currentMetalData.seriesData
+      }
+    ],
+    color: COLORS
+  };
+
+  myChart.setOption(option);
+};
+
+// ========== 生命周期钩子 ==========
+onMounted(async () => {
+  try {
+    // 修复请求超时问题:将超时时间延长至10秒
+    const [samplingRes, assayRes] = await Promise.all([
+      axios.get(SAMPLING_API, { timeout: 10000 }), // 10秒超时
+      axios.get(ASSAY_API, { timeout: 10000 })
+    ]);
+    
+    chartData.value = processMergedData(samplingRes.data, assayRes.data);
+    initChart();
+  } catch (err) {
+    // 处理超时错误
+    if (err.code === 'ECONNABORTED') {
+      error.value = '请求超时:服务器响应时间超过10秒';
+    } else {
+      error.value = '数据加载失败: ' + (err.message || '未知错误');
+    }
+    console.error('接口错误:', err);
+  } finally {
+    loading.value = false;
+  }
+});
+
+// 监听重金属选择变化
+watch(selectedMetal, (newVal) => {
+  if (newVal && myChart && chartData.value) {
+    initChart();
+  }
+});
+
+// 响应式布局
+const resizeHandler = () => myChart && myChart.resize();
+onMounted(() => window.addEventListener('resize', resizeHandler));
+onUnmounted(() => window.removeEventListener('resize', resizeHandler));
+</script>
+
+<style scoped>
+.region-average-chart {
+  width: 100%;
+  max-width: 1200px;
+  margin: 20px auto;
+  position: relative;
+}
+.chart-box {
+  width: 100%;
+  height: 600px;
+  min-height: 400px;
+  background-color: white;
+  border-radius: 8px;
+  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
+}
+.status {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  padding: 15px;
+  background: rgba(255,255,255,0.8);
+  border-radius: 4px;
+  text-align: center;
+}
+.error { 
+  color: #ff4d4f;
+  font-weight: bold;
+}
+.metal-selector {
+  margin-bottom: 15px;
+  text-align: center;
+  padding: 10px;
+}
+.metal-selector label {
+  margin-right: 10px;
+  font-weight: bold;
+}
+.metal-selector select {
+  padding: 8px 15px;
+  border-radius: 4px;
+  border: 1px solid #ddd;
+  background-color: #f8f8f8;
+  font-size: 14px;
+  min-width: 150px;
+  cursor: pointer;
+  transition: border 0.3s;
+}
+.metal-selector select:hover {
+  border-color: #1890ff;
+}
+</style>

+ 103 - 25
src/components/irrpollution/waterdataline.vue

@@ -3,7 +3,6 @@
     <div class="container mx-auto px-4 py-8">
     <div class="bg-white rounded-xl shadow-lg overflow-hidden">
       
-      
       <!-- 加载状态 -->
       <div v-if="loading" class="py-20 flex justify-center items-center">
         <div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
@@ -40,9 +39,9 @@
                 class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors"
                 @click="sortData(col.key)"
               >
-                <div class="flex items-center justify-between">
-                  {{ col.label }}
-                  <span v-if="sortKey === col.key" class="ml-1 text-gray-400">
+                <div class="flex items-center justify-between whitespace-nowrap overflow-hidden">
+                  <span class="truncate" :title="col.label">{{ col.label }}</span>
+                  <span v-if="sortKey === col.key" class="ml-1 text-gray-400 flex-shrink-0">
                     {{ sortOrder === 'asc' ? '↑' : '↓' }}
                   </span>
                 </div>
@@ -59,7 +58,13 @@
               >
                 <div class="flex items-center">
                   <div class="text-gray-900 font-medium">
-                    {{ item[col.key] !== null ? item[col.key] : '-' }}
+                    <!-- 格式化金属含量字段为小数点后六位 -->
+                    <template v-if="concentrationFields.includes(col.key) && item[col.key] !== null">
+                      {{ formatConcentration(item[col.key]) }}
+                    </template>
+                    <template v-else>
+                      {{ item[col.key] !== null ? item[col.key] : '-' }}
+                    </template>
                   </div>
                 </div>
               </td>
@@ -90,16 +95,15 @@
     </div>
   </div>
   </div>
-  
 </template>
 
 <script setup>
 import { ref, computed, onMounted } from 'vue';
-import axios from 'axios';
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
-// 适配接口的字段映射(关键修改点)
+// 适配接口的字段映射
 const displayColumns = ref([
-  { key: 'sample_number', label: '水样ID' }, // 原water_sample_ID对应接口的sample_number
+  { key: 'sample_number', label: '水样ID' },
   { key:'sampling_location',label:'地理位置'},
   { key: 'ph_value', label: 'PH值' },
   { key: 'cr_concentration', label: '铬含量(ug/L)' },
@@ -109,6 +113,15 @@ const displayColumns = ref([
   { key: 'pb_concentration', label: '铅含量(ug/L)' },
 ]);
 
+// 需要格式化为小数点后六位的字段
+const concentrationFields = [
+  'cr_concentration',
+  'as_concentration',
+  'cd_concentration',
+  'hg_concentration',
+  'pb_concentration'
+];
+
 // 状态管理
 const waterData = ref([]);
 const loading = ref(true);
@@ -116,44 +129,100 @@ const error = ref(null);
 const sortKey = ref('');
 const sortOrder = ref('asc');
 
-// 接口请求(关键修改点:URL和数据解析)
+// 格式化金属含量为小数点后六位
+const formatConcentration = (value) => {
+  const num = Number(value);
+  return isNaN(num) ? value : num.toFixed(6);
+};
+
+// 接口请求
 const fetchData = async () => {
   try {
     loading.value = true;
     error.value = null;
-    const response = await axios.get(
-      'http://localhost:8000/api/vector/export/all?table_name=water_sampling_data'
-    );
-    // 解析GeoJSON数据:提取features中的properties作为数据项
-    waterData.value = response.data.features.map(feature => feature.properties);
+    
+    // 使用 api8000 实例获取数据
+    const response = await api8000.get('/api/vector/export/all?table_name=water_sampling_data');
+    
+    // 处理可能的字符串响应
+    let data = response.data;
+    if (typeof data === 'string') {
+      try {
+        // 替换 NaN 为 null
+        const cleanedData = data.replace(/\bNaN\b/g, 'null');
+        data = JSON.parse(cleanedData);
+      } catch (parseErr) {
+        throw new Error('接口返回的是字符串,但 JSON 解析失败');
+      }
+    }
+    
+    // 处理对象中的 NaN 值
+    if (typeof data === 'object' && data !== null) {
+      const replaceNaN = (obj) => {
+        for (const key in obj) {
+          if (typeof obj[key] === 'object' && obj[key] !== null) {
+            replaceNaN(obj[key]);
+          } else if (typeof obj[key] === 'number' && isNaN(obj[key])) {
+            obj[key] = null;
+          } else if (obj[key] === 'NaN') {
+            obj[key] = null;
+          }
+        }
+      };
+      replaceNaN(data);
+    }
+    
+    // 接口返回格式判断(GeoJSON或直接数组)
+    const rawData = data.features 
+      ? data.features.map(f => f.properties) 
+      : data;
+    
+    waterData.value = rawData;
   } catch (err) {
     error.value = err.message || '无法连接到服务器,请检查接口是否可用';
+    console.error('数据加载失败:', err);
   } finally {
     loading.value = false;
   }
 };
 
-// 过滤全空行(逻辑保留,自动适配新字段)
+// 过滤全空行
 const filteredData = computed(() => {
   return waterData.value.filter(item => {
     return displayColumns.value.some(col => item[col.key] !== null && item[col.key] !== '-');
   });
 });
 
-// 排序功能(逻辑保留,自动适配新字段)
+// 排序功能
 const sortedData = computed(() => {
   if (!sortKey.value) return filteredData.value;
   
   return [...filteredData.value].sort((a, b) => {
     const valA = a[sortKey.value];
     const valB = b[sortKey.value];
-    if (valA < valB) return sortOrder.value === 'asc' ? -1 : 1;
-    if (valA > valB) return sortOrder.value === 'asc' ? 1 : -1;
+    
+    // 处理 null 值
+    if (valA === null && valB === null) return 0;
+    if (valA === null) return sortOrder.value === 'asc' ? 1 : -1;
+    if (valB === null) return sortOrder.value === 'asc' ? -1 : 1;
+    
+    // 数值比较
+    if (typeof valA === 'number' && typeof valB === 'number') {
+      return sortOrder.value === 'asc' ? valA - valB : valB - valA;
+    }
+    
+    // 字符串比较
+    if (typeof valA === 'string' && typeof valB === 'string') {
+      return sortOrder.value === 'asc' 
+        ? valA.localeCompare(valB) 
+        : valB.localeCompare(valA);
+    }
+    
     return 0;
   });
 });
 
-// 切换排序(逻辑保留)
+// 切换排序
 const sortData = (key) => {
   if (sortKey.value === key) {
     sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc';
@@ -163,7 +232,7 @@ const sortData = (key) => {
   }
 };
 
-// 组件挂载(逻辑保留)
+// 组件挂载
 onMounted(() => {
   fetchData();
 });
@@ -213,13 +282,13 @@ onMounted(() => {
 /* 表格 */
 table { 
   width: 100%; 
-  border-collapse: collapse; /* 合并边框线 */
+  border-collapse: collapse;
   background-color: white;
 }
 th, td {
-  border: 1px solid #d1d5db; /* 灰色边框 */
-  text-align: center; /* 内容居中 */
-  padding: 12px 8px; /* 内边距优化 */
+  border: 1px solid #d1d5db;
+  text-align: center;
+  padding: 12px 8px;
   font-size: 14px;
   background-color: white;
 }
@@ -227,6 +296,15 @@ th, td {
 .py-4 { padding: 1rem 0; }
 .hover\:bg-gray-50:hover { background-color: #f9fafb; }
 
+/* 表头样式优化 - 确保不换行 */
+th .truncate {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  max-width: 150px; /* 可根据实际调整 */
+  display: inline-block;
+}
+
 /* 响应式 */
 @media (max-width: 640px) {
   .container { padding: 32px 8px; }

+ 9 - 9
src/components/layout/AppAside.vue

@@ -41,7 +41,7 @@
 import { reactive, computed, inject, toRefs, watch } from 'vue';
 import { useRouter } from 'vue-router';
 import { ElMessage } from 'element-plus';
-import { menuItems } from './menuItems'; // 建议你将 menuItems 独立维护在该文件中
+import { menuItems } from './menuItems';
 
 const props = defineProps({
   activeTab: {
@@ -124,18 +124,19 @@ watch(
   min-height: 100%;
 }
 
+/* 统一菜单项和子菜单标题的内边距 */
 :deep(.el-menu-item),
 :deep(.el-sub-menu__title) {
-  margin-left: 0 !important;
-  margin-right: 0 !important;
+  margin: 0 !important;
   width: 100%;
   box-sizing: border-box;
-  padding-left: 40px !important;
   padding-right: 20px !important;
 }
 
+/* 子菜单项样式调整 */
 :deep(.el-sub-menu .el-menu-item) {
   background-color: rgba(252, 234, 183, 0.3) !important;
+  padding-left: 40px !important; /* 确保子菜单项与主菜单标题对齐 */
 }
 
 :deep(.el-sub-menu .el-menu-item:not(:last-child)) {
@@ -168,17 +169,16 @@ watch(
   box-shadow: 0 2px 8px rgba(16, 146, 216, 0.25);
 }
 
-/* 子菜单标题样式 + 图标右移 */
+/* 子菜单标题样式 */
 :deep(.el-sub-menu__title) {
   display: flex;
   align-items: center;
-  padding-left: 20px !important;
 }
 
-/* 下拉图标右移 */
+/* 下拉图标右移调整 */
 :deep(.el-sub-menu__icon-arrow) {
   margin-left: auto;
-  margin-right: -160px;
+  margin-right: 0; /* 移除负边距 */
   transition: transform 0.3s ease;
 }
-</style>
+</style>

+ 1 - 9
src/components/layout/AppHeader.vue

@@ -29,7 +29,7 @@
 
 <script setup lang="ts">
 import { reactive } from "vue";
-import { ElMessageBox, ElMessage } from "element-plus";
+import { ElMessage } from "element-plus";
 import { useTokenStore } from "@/stores/mytoken";
 import { logout } from "@/API/users";
 import router from "@/router";
@@ -37,7 +37,6 @@ import router from "@/router";
 // 获取 tokenStore 实例
 const tokenStore = useTokenStore();
 
-
 // 日志记录函数
 function logAction(action: string) {
   console.log(`[AppHeader] ${action}`);
@@ -52,11 +51,6 @@ function showError(message: string) {
 const handleLogout = async () => {
   try {
     logAction("Logout initiated");
-    await ElMessageBox.confirm("确定要退出登录吗?", "提示", {
-      confirmButtonText: "确定",
-      cancelButtonText: "取消",
-      type: "warning",
-    });
     await logout(); // 调用退出接口
     tokenStore.clearToken(); // 清除本地存储的 token
     ElMessage.success("退出成功");
@@ -66,8 +60,6 @@ const handleLogout = async () => {
     logAction("Logout failed");
     if (error instanceof Error) {
       showError(`退出失败:${error.message}`);
-    } else {
-      ElMessage.info("已取消退出");
     }
   }
 };

+ 71 - 27
src/components/layout/AppLayout.vue

@@ -17,13 +17,13 @@
         <img src="@/assets/logo.png" alt="Logo" class="logo" />
         <div class="title-and-user">
           <span class="project-name" :class="{ 'light-text': isSpecialBg }">
-            区域土壤重金属污染风险评估
+            区域土壤重金属污染风险评估系统
           </span>
 
           <!-- 用户信息 -->
           <div class="user-info-row" v-if="!isSelectCity">
             <span class="welcome-text" :class="{ 'light-text': isSpecialBg }">
-              欢迎 {{ userInfo.type === "admin" ? "管理员" : "用户" }} 登录成功
+              欢迎 {{ userInfo.name }} 登录成功
             </span>
             <el-dropdown>
               <span class="el-dropdown-link">
@@ -92,21 +92,35 @@
         </div>
       </el-main>
     </el-container>
+    
+    <!-- 全局消息提示组件 -->
+    <div v-if="showGlobalMessage" class="global-message">
+      <div class="message-content" :class="messageType">
+        {{ globalMessage }}
+      </div>
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, computed, watch, defineAsyncComponent } from "vue";
+import { ref, reactive, computed, watch, defineAsyncComponent, onMounted } from "vue";
 import { useRouter, useRoute } from "vue-router";
 import { useTokenStore } from "@/stores/mytoken";
-import { ElMessageBox, ElMessage } from "element-plus";
+import { ElMessageBox, ElMessage } from "element-plus"; // 确保导入ElMessage
 import { logout } from "@/API/users";
+import { useI18n } from "vue-i18n";
 
+const { t } = useI18n();
 const router = useRouter();
 const route = useRoute();
 const tokenStore = useTokenStore();
 const currentBgImage = ref("");
 
+// ============ 新增状态 ============
+const showGlobalMessage = ref(false);
+const globalMessage = ref("");
+const messageType = ref("success"); // 消息类型: success/error
+
 // 特殊背景路由映射
 const bgRouteMap: Record<string, string> = {
   "/samplingMethodDevice1": "irrigation.jpg",
@@ -128,7 +142,9 @@ const bgRouteMap: Record<string, string> = {
   "/samplingDesc4": "surface-runoff.jpg",
   "/surfaceRunoffInputFlux": "surface-runoff.jpg",
   "/totalInputFlux": "background.jpg",
+  "/totalInputFluxDesc": "background.jpg",
   "/totalOutputFlux": "background.jpg",
+  "/totalOutputFluxDesc": "background.jpg",
   "/netFlux": "background.jpg",
   "/currentYearConcentration": "background.jpg",
   "/TotalCadmiumPrediction": "background.jpg",
@@ -161,6 +177,7 @@ const bgRouteMap: Record<string, string> = {
   "/VegetableRiskModel": "background.jpg",
   "/UserManagement": "background.jpg",
   "/UserRegistration": "background.jpg",
+  "/SoilacidificationStatistics": "background.jpg",
 };
 
 // 当前是否特殊背景
@@ -273,6 +290,9 @@ const tabs = computed(() => {
           "/heavyMetalEnterprise",
           "/airSampleData",
           "/airInputFlux",
+          "/totalInputFlux",
+          "/totalInputFluxDesc"
+
         ],
       },
       {
@@ -288,6 +308,8 @@ const tabs = computed(() => {
           "/subsurfaceLeakageInputFlux",
           "/samplingDesc4",
           "/surfaceRunoffInputFlux",
+           "/totalOutputFlux",
+          "/totalOutputFluxDesc"
         ],
       },
       /*{
@@ -301,21 +323,18 @@ const tabs = computed(() => {
         label: "土壤污染物含量预测",
         icon: "el-icon-c-scale-to-original",
         routes: [
-          "/totalInputFlux",
-          "/TotalCadmiumPrediction",
-          "/totalOutputFlux",
           "/netFlux",
           "/currentYearConcentration",
           "/EffectiveCadmiumPrediction",
           "/CropCadmiumPrediction",
         ],
       },
-      {
-        name: "cropRiskAssessment",
-        label: "作物风险评估",
-        icon: "el-icon-warning",
-        routes: ["/cropRiskAssessment"],
-      },
+      // {
+      //   name: "cropRiskAssessment",
+      //   label: "作物风险评估",
+      //   icon: "el-icon-warning",
+      //   routes: ["/cropRiskAssessment"],
+      // },
       {
         name: "farmlandQualityAssessment",
         label: "耕地质量评估",
@@ -328,16 +347,16 @@ const tabs = computed(() => {
         icon: "el-icon-magic-stick",
         routes: ["/Calculation", "/AcidNeutralizationModel"],
       },
-      {
-        name: "scenarioSimulation",
-        label: "情景模拟",
-        icon: "el-icon-s-operation",
-        routes: [
-          "/TraditionalFarmingRisk",
-          "/HeavyMetalCadmiumControl",
-          "/SoilAcidificationControl",
-        ],
-      },
+      // {
+      //   name: "scenarioSimulation",
+      //   label: "情景模拟",
+      //   icon: "el-icon-s-operation",
+      //   routes: [
+      //     "/TraditionalFarmingRisk",
+      //     "/HeavyMetalCadmiumControl",
+      //     "/SoilAcidificationControl",
+      //   ],
+      // },
       {
         name: "dataStatistics",
         label: "数据统计",
@@ -346,6 +365,7 @@ const tabs = computed(() => {
           "/DetectionStatistics",
           "/FarmlandPollutionStatistics",
           "/LandClutivatesStatistics",
+          "/SoilacidificationStatistics"
         ],
       },
     ];
@@ -398,6 +418,18 @@ const showAside = computed(
     !isFullScreen.value && !["cropRiskAssessment"].includes(activeName.value)
 );
 
+// ============ 显示全局消息 ============
+const showMessage = (message: string, type: "success" | "error" = "success") => {
+  globalMessage.value = message;
+  messageType.value = type;
+  showGlobalMessage.value = true;
+  
+  // 3秒后自动隐藏消息
+  setTimeout(() => {
+    showGlobalMessage.value = false;
+  }, 3000);
+};
+
 // 登出逻辑
 const handleLogout = async () => {
   try {
@@ -408,10 +440,22 @@ const handleLogout = async () => {
     });
     await logout();
     tokenStore.clearToken();
-    ElMessage.success("退出成功");
-    router.push("/login");
-  } catch {
-    ElMessage.info("已取消退出");
+    
+    // 使用ElMessage而不是全局消息提示
+    ElMessage.success("退出登录成功");
+    
+    // 延迟跳转,让用户看到提示消息
+    setTimeout(() => {
+      router.push("/login");
+    }, 1000);
+  } catch (error) {
+    // 区分用户取消操作和真正的错误
+    if (error === 'cancel' || error?.toString().includes('cancel')) {
+      console.log("用户取消退出登录");
+    } else {
+      ElMessage.error("退出失败,请重试");
+      console.error("退出登录错误:", error);
+    }
   }
 };
 

+ 56 - 28
src/components/layout/menuItems.ts

@@ -144,10 +144,24 @@ export const menuItems: MenuItem[] = [
     ]
   },
   {
-    index: '/totalInputFlux',
+    index: 'totalInputFlux',
     label: '输入总通量',
-    icon: Watermelon,
-    tab: 'HmOutFlux'
+    icon: WindPower,
+    tab: 'HmOutFlux',
+    children: [
+      {
+        index: '/totalInputFluxDesc',
+        label: '输入总通量说明',
+        icon: Watermelon,
+        tab: 'HmOutFlux',
+      },
+      {
+        index: '/totalInputFlux',
+        label: '输入总通量结果',
+        icon: List,
+        tab: 'HmOutFlux',
+      },
+    ]
   },
   {
     index: 'grainRemoval',
@@ -230,10 +244,24 @@ export const menuItems: MenuItem[] = [
     ]
   },
   {
-    index: '/totalOutputFlux',
+    index: 'totalOutputFlux',
     label: '输出总通量',
     icon: WindPower,
     tab: 'hmInFlux',
+    children: [
+      {
+        index: '/totalOutputFluxDesc',
+        label: '输出总通量说明',
+        icon: Watermelon,
+        tab: 'hmInFlux',
+      },
+      {
+        index: '/totalOutputFlux',
+        label: '输出总通量结果',
+        icon: List,
+        tab: 'hmInFlux',
+      },
+    ]
   },
   {
     index: '/mapView',
@@ -265,12 +293,12 @@ export const menuItems: MenuItem[] = [
     icon: PieChart,
     tab: 'cadmiumPrediction'
   },
-  {
-    index: '/cropRiskAssessment',
-    label: 'cropRiskAssessment.Title',//<!--i18n:cropRiskAssessment.Title-->水稻镉污染风险
-    icon: Compass,
-    tab: 'cropRiskAssessment'
-  },
+  // {
+  //   index: '/cropRiskAssessment',
+  //   label: 'cropRiskAssessment.Title',//<!--i18n:cropRiskAssessment.Title-->水稻镉污染风险
+  //   icon: Compass,
+  //   tab: 'cropRiskAssessment'
+  // },
   {
     index: '/farmlandQualityAssessment',
     label: 'farmlandQualityAssessment.Title',//<!--i18n:farmlandQualityAssessment.Title-->韶关
@@ -317,24 +345,24 @@ export const menuItems: MenuItem[] = [
       }
     ]
   },
-  {
-    index: '/TraditionalFarmingRisk',
-    label: 'TraditionalFarmingRisk.Title',//<!--i18n:TraditionalFarmingRisk.Title-->传统耕种习惯风险趋势
-    icon: MenuIcon,
-    tab: 'scenarioSimulation'
-  },
-  {
-    index: '/HeavyMetalCadmiumControl',
-    label: 'HeavyMetalCadmiumControl.Title',//<!--i18n:HeavyMetalCadmiumControl.Title-->重金属镉污染治理
-    icon: MenuIcon,
-    tab: 'scenarioSimulation'
-  },
-  {
-    index: '/SoilAcidificationControl',
-    label: 'SoilAcidificationControl.Title',//<!--i18n:SoilAcidificationControl.Title-->土壤酸化治理
-    icon: MenuIcon,
-    tab: 'scenarioSimulation'
-  },
+  // {
+  //   index: '/TraditionalFarmingRisk',
+  //   label: 'TraditionalFarmingRisk.Title',//<!--i18n:TraditionalFarmingRisk.Title-->传统耕种习惯风险趋势
+  //   icon: MenuIcon,
+  //   tab: 'scenarioSimulation'
+  // },
+  // {
+  //   index: '/HeavyMetalCadmiumControl',
+  //   label: 'HeavyMetalCadmiumControl.Title',//<!--i18n:HeavyMetalCadmiumControl.Title-->重金属镉污染治理
+  //   icon: MenuIcon,
+  //   tab: 'scenarioSimulation'
+  // },
+  // {
+  //   index: '/SoilAcidificationControl',
+  //   label: 'SoilAcidificationControl.Title',//<!--i18n:SoilAcidificationControl.Title-->土壤酸化治理
+  //   icon: MenuIcon,
+  //   tab: 'scenarioSimulation'
+  // },
   {
     index: '/DetectionStatistics',
     label: 'DetectionStatistics.Title',//<!--i18n:DetectionStatistics.Title-->检测信息统计

+ 16 - 10
src/components/soilStatictics/reducedataStatistics.vue

@@ -25,18 +25,19 @@
 <script>
 import { ref, onMounted, onUnmounted, nextTick } from 'vue';
 import * as echarts from 'echarts';
+import { api5000 } from '@/utils/request'; // 导入 api5000 实例
 
 export default {
   name: 'PhTrendChart',
   setup() {
     // 接口数据 - 不同时间点的数据集
     const interfaces = [
-      { time: '20241229_201018', url: 'http://localhost:5000/api/table-data?table_name=dataset_5' },
-      { time: '20250104_171827', url: 'http://localhost:5000/api/table-data?table_name=dataset_35' },
-      { time: '20250104_171959', url: 'http://localhost:5000/api/table-data?table_name=dataset_36' },
-      { time: '20250104_214026', url: 'http://localhost:5000/api/table-data?table_name=dataset_37' },
-      { time: '20250308_161945', url: 'http://localhost:5000/api/table-data?table_name=dataset_65' },
-      { time: '20250308_163248', url: 'http://localhost:5000/api/table-data?table_name=dataset_66' },
+      { time: '20241229_201018', url: '/api/table-data?table_name=dataset_5' },
+      { time: '20250104_171827', url: '/api/table-data?table_name=dataset_35' },
+      { time: '20250104_171959', url: '/api/table-data?table_name=dataset_36' },
+      { time: '20250104_214026', url: '/api/table-data?table_name=dataset_37' },
+      { time: '20250308_161945', url: '/api/table-data?table_name=dataset_65' },
+      { time: '20250308_163248', url: '/api/table-data?table_name=dataset_66' },
     ];
     
     const chartRef = ref(null);
@@ -78,10 +79,15 @@ export default {
         
         // 按时间顺序获取每个表的数据
         for (const intf of interfaces) {
-          const response = await fetch(intf.url);
-          if (!response.ok) throw new Error(`网络响应错误: ${response.status}`);
+          // 使用 api5000 替代 fetch
+          const response = await api5000.get(intf.url);
           
-          const responseData = await response.json();
+          // 检查响应状态
+          if (response.status !== 200) {
+            throw new Error(`网络响应错误: ${response.status}`);
+          }
+          
+          const responseData = response.data;
           const dataArray = responseData.data || [];
           
           for (const id of newSelectedIds) {
@@ -93,7 +99,7 @@ export default {
               };
             }
             
-            // 查找匹配的项目 ,使用小写id
+            // 查找匹配的项目,使用小写id
             const targetItem = dataArray.find(item => item.id === id);
             
             if (targetItem) {

+ 16 - 29
src/components/soilStatictics/refluxcedataStatictics.vue

@@ -8,7 +8,7 @@
 <script setup>
 import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
 import * as echarts from 'echarts';
-import axios from 'axios';
+import { api5000 } from '@/utils/request'; // 导入 api5000 实例
 
 // 图表相关
 const chartRef = ref(null);
@@ -19,35 +19,33 @@ const chartData = ref([]);
 const isLoading = ref(false);
 const errorMessage = ref('');
 
-// 接口地址(请替换为实际接口地址
-const API_URL = 'http://localhost:5000/api/table-data?table_name=dataset_60'; // 替换为实际接口
+// 接口地址(使用相对路径
+const API_URL = '/api/table-data?table_name=dataset_60'; // 相对路径
 
 // 获取数据函数
 const fetchData = async () => {
-  //console.log('开始获取数据...');
   // 重置状态
   errorMessage.value = '';
   isLoading.value = true;
   
   try {
-    // 发起请求
-    const response = await axios.get(API_URL);
-    //console.log('API响应:', response);
+    // 使用 api5000 替代 axios
+    const response = await api5000.get(API_URL);
     
     // 检查响应数据是否符合预期格式
     if (response.data && response.data.success) {
-  if (Array.isArray(response.data.data) && response.data.data.length > 0) {
-    if (response.data.data[0].id !== undefined && response.data.data[0].Delta_pH !== undefined) {
-      chartData.value = response.data.data;
+      if (Array.isArray(response.data.data) && response.data.data.length > 0) {
+        if (response.data.data[0].id !== undefined && response.data.data[0].Delta_pH !== undefined) {
+          chartData.value = response.data.data;
+        } else {
+          errorMessage.value = '数据字段缺失:缺少 id 或 Delta_pH';
+        }
+      } else {
+        errorMessage.value = '未获取到有效数据,请稍后重试';
+      }
     } else {
-      errorMessage.value = '数据字段缺失:缺少 id 或 Delta_pH';
+      errorMessage.value = 'API返回失败状态';
     }
-  } else {
-    errorMessage.value = '未获取到有效数据,请稍后重试';
-  }
-} else {
-  errorMessage.value = 'API返回失败状态';
-}
   } catch (error) {
     console.error('数据获取失败:', error);
     // 根据错误类型显示不同信息
@@ -65,7 +63,6 @@ const fetchData = async () => {
 
 // 初始化图表
 const initChart = () => {
-  //console.log('初始化图表,数据长度:', chartData.value.length);
   // 确保DOM已挂载且有数据
   if (!chartRef.value) {
     console.error('图表容器未找到');
@@ -80,16 +77,10 @@ const initChart = () => {
   // 销毁已有实例
   if (myChart) {
     myChart.dispose();
-    //console.log('已销毁旧图表实例');
   }
   
   // 初始化图表
   myChart = echarts.init(chartRef.value);
-  //console.log('ECharts实例已创建');
-
-   myChart.on('rendered', () => {
-    //console.log('图表已渲染');
-  });
   
   // 提取数据
   const xAxisData = chartData.value.map(item => item.id);
@@ -100,9 +91,6 @@ const initChart = () => {
       color: item.Delta_pH >= 0 ? '#0F52BA' : '#F44336'
     }
   }));
-
-  //console.log('X轴数据:', xAxisData);
-  //console.log('系列数据:', seriesData);
   
   // 图表配置
   const option = {
@@ -164,7 +152,6 @@ watch(chartData, () => {
   nextTick(()=>{
     initChart();  
   })
-
 });
 
 // 窗口大小变化时重绘图表
@@ -211,4 +198,4 @@ onUnmounted(() => {
 .title {
   text-align: center;
 }
-</style>
+</style>

+ 11 - 21
src/components/soilcdStatistics/cropcdStatictics.vue

@@ -46,30 +46,23 @@
 </template>
 
 <script setup>
-import { ref, onMounted, watch, nextTick } from 'vue';
+import { ref, onMounted, nextTick } from 'vue';
 import * as echarts from 'echarts';
-import axios from 'axios';
-
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
 // 图表实例引用
 const cdBarChart = ref(null);
 const nutrientBoxChart = ref(null);
 const extraBoxChart = ref(null);
 
-
 // 图表实例变量
 let chartInstanceCd = null;
 let chartInstanceNutrient = null;
 let chartInstanceExtra = null;
 
-
 // 响应式状态
-const showDialog = ref(false);
-const dialogTitle = ref("");
-
 const isLoading = ref(true);
 const error = ref(null);
-const lastUpdate = ref("");
 const stats = ref({
   cd002Avg: 0,
   cd02Avg: 0,
@@ -123,14 +116,12 @@ const fieldConfig = {
   ]
 };
 
-
 // 数据请求(作物态Cd接口)
 const fetchData = async () => {
   try {
-    const apiUrl = 'http://localhost:8000/api/vector/stats/CropCd_input_data';
-    const response = await axios.get(apiUrl);
-    
-    return response.data.data;
+    const apiUrl = '/api/vector/stats/CropCd_input_data'; // 相对路径
+    const response = await api8000.get(apiUrl); // 使用 api8000
+    return response.data;
   } catch (err) {
     throw new Error('数据加载失败: ' + err.message);
   }
@@ -243,19 +234,18 @@ const initExtraChart = () => {
       });
 };
 
-
 // 格式化Tooltip
-const formatTooltip = (stat , unit ='') => {
+const formatTooltip = (stat, unit = '') => {
   if (!stat || !stat.min) {
     return `<div style="font-weight:bold;color:#f56c6c">${stat?.name || '未知'}</div><div>无有效数据</div>`;
   }
   return `<div style="font-weight:bold">${stat.name}</div>
     <div style="margin-top:8px">
-      <div>最小值:<span style="color:#5a5;">${stat.min.toFixed(4)}</span></div>
-      <div>下四分位:<span style="color:#d87a80;">${stat.q1.toFixed(4)}</span></div>
-      <div>中位数:<span style="color:#f56c6c;font-weight:bold;">${stat.median.toFixed(4)}</span></div>
-      <div>上四分位:<span style="color:#d87a80;">${stat.q3.toFixed(4)}</span></div>
-      <div>最大值:<span style="color:#5a5;">${stat.max.toFixed(4)}</span></div>
+      <div>最小值:<span style="color:#5a5;">${stat.min.toFixed(4)} ${unit}</span></div>
+      <div>下四分位:<span style="color:#d87a80;">${stat.q1.toFixed(4)} ${unit}</span></div>
+      <div>中位数:<span style="color:#f56c6c;font-weight:bold;">${stat.median.toFixed(4)} ${unit}</span></div>
+      <div>上四分位:<span style="color:#d87a80;">${stat.q3.toFixed(4)} ${unit}</span></div>
+      <div>最大值:<span style="color:#5a5;">${stat.max.toFixed(4)} ${unit}</span></div>
     </div>`;
 };
 

+ 54 - 22
src/components/soilcdStatistics/effcdStatistics.vue

@@ -44,9 +44,9 @@
 </template>
 
 <script setup>
-import { ref, onMounted, watch, nextTick } from 'vue';
+import { ref, onMounted, nextTick } from 'vue';
 import * as echarts from 'echarts';
-import axios from 'axios';
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
 // 图表实例引用
 const cdBarChart = ref(null);
@@ -57,8 +57,6 @@ const extraBoxChart = ref(null);
 let chartInstanceCd = null;
 let chartInstanceNutrient = null;
 let chartInstanceExtra = null;
-const chartInstancePopup =null;
-
 
 // 响应式状态
 const isLoading = ref(true);
@@ -102,14 +100,12 @@ const fieldConfig = {
   ]
 };
 
-
 // 数据请求
 const fetchData = async () => {
   try {
-    // 实际项目中替换为真实API
-     const res = await axios.get("http://localhost:8000/api/vector/stats/EffCd_input_data");
-     return res.data.data;
-
+    // 使用 api8000 替代 axios
+    const res = await api8000.get("/api/vector/stats/EffCd_input_data");
+    return res.data;
   } catch (err) {
     throw new Error('数据加载失败: ' + err.message);
   }
@@ -119,7 +115,16 @@ const fetchData = async () => {
 const calculateFieldStats = (statsData, fieldKey, fieldName, fieldConfigItem) => {
   const fieldStats = statsData[fieldKey]; // 从接口数据中取当前字段的统计结果
   if (!fieldStats) {
-    return { key: fieldKey, name: fieldName, min: null, q1: null, median: null, q3: null, max: null, mean: null };
+    return { 
+      key: fieldKey, 
+      name: fieldName, 
+      min: null, 
+      q1: null, 
+      median: null, 
+      q3: null, 
+      max: null, 
+      mean: null 
+    };
   }
 
   // 提取原始统计值
@@ -196,7 +201,7 @@ const initPollutionChart = () => {
   
   // 提取x轴标签和数据
   const xAxisData = fieldConfig.pollution.map(f => f.name);
-  const barData = pollutionStats.value.map(stat =>stat.mean);
+  const barData = pollutionStats.value.map(stat => stat.mean);
   
   chartInstanceCd.setOption({
     title: { text: '主要指标含量对比', left: 'center', textStyle: { fontSize: 14 } },
@@ -205,11 +210,25 @@ const initPollutionChart = () => {
       formatter: (params) => `${params[0].name}<br/>平均值: ${params[0].value.toFixed(4)} mg/kg`
     },
     grid: { top: 40, right: 15, bottom: 30, left: 40 },
-    xAxis: { type: "category", data: xAxisData, axisLabel: { fontSize: 12 ,rotate:30 } },
-    yAxis: { type: "value", name: '含量 (mg/kg)', nameTextStyle: { fontSize: 12  }, axisLabel: { fontSize: 11 ,rotate:30} },
+    xAxis: { 
+      type: "category", 
+      data: xAxisData, 
+      axisLabel: { 
+        fontSize: 12,
+        rotate: 30,
+        interval: 0, // 强制显示所有标签
+        formatter: (value) => value.length > 8 ? value.substring(0, 8) + '...' : value
+      } 
+    },
+    yAxis: { 
+      type: "value", 
+      name: '含量 (mg/kg)', 
+      nameTextStyle: { fontSize: 12  }, 
+      axisLabel: { fontSize: 11 } 
+    },
     series: [{
       name: '平均值', type: "bar",
-      itemStyle: {color: '#5470c6' },
+      itemStyle: {color: (params) => fieldConfig.pollution[params.dataIndex].color },
       data: barData
     }]
   });
@@ -230,11 +249,25 @@ const initNutrientChart = () => {
       formatter: (params) => formatTooltip(nutrientStats.value[params.dataIndex])
     },
     grid: { top: 40, right: 15, bottom: 40, left: 40 },
-    xAxis: { type: "category", data: xAxisData, axisLabel: { fontSize: 11, rotate: 30 } },
-    yAxis: { type: "value", name: '含量(mg/kg)', nameTextStyle: { fontSize: 12 }, axisLabel: { fontSize: 11 , rotate: 30 } },
+    xAxis: { 
+      type: "category", 
+      data: xAxisData, 
+      axisLabel: { 
+        fontSize: 11, 
+        rotate: 30,
+        interval: 0, // 强制显示所有标签
+        formatter: (value) => value.length > 8 ? value.substring(0, 8) + '...' : value
+      } 
+    },
+    yAxis: { 
+      type: "value", 
+      name: '含量(mg/kg)', 
+      nameTextStyle: { fontSize: 12 }, 
+      axisLabel: { fontSize: 11 } 
+    },
     series: [{
       name: '养分元素', type: "boxplot",
-      itemStyle: { color: '#fac858', borderColor: '#ee6666' },
+      itemStyle: { color: (params) => fieldConfig.nutrient[params.dataIndex].color },
       data: boxData
     }]
   });
@@ -259,14 +292,13 @@ const initExtraChart = () => {
           yAxis: { type: "value", name: '%', nameTextStyle: { fontSize: 12 }, axisLabel: { fontSize: 11 } },
           series: [{
             name: '理化性质', type: "boxplot",
-            itemStyle: { color: '#73c0de', borderColor: '#5470c6' },
+            itemStyle: { color: (params) => fieldConfig.extra[params.dataIndex].color },
             data: boxData
           }]
         });
   });
 };
 
-
 // 格式化Tooltip(复用缓存的统计数据)
 const formatTooltip = (stat) => {
   if (!stat || !stat.min) {
@@ -310,7 +342,7 @@ onMounted(() => {
   
   // 窗口 resize 处理
   const handleResize = () => {
-    [chartInstanceCd, chartInstanceNutrient, chartInstanceExtra, chartInstancePopup]
+    [chartInstanceCd, chartInstanceNutrient, chartInstanceExtra]
       .forEach(inst => inst && inst.resize());
   };
   window.addEventListener('resize', handleResize);
@@ -318,7 +350,7 @@ onMounted(() => {
   // 组件卸载清理
   return () => {
     window.removeEventListener('resize', handleResize);
-    [chartInstanceCd, chartInstanceNutrient, chartInstanceExtra, chartInstancePopup]
+    [chartInstanceCd, chartInstanceNutrient, chartInstanceExtra]
       .forEach(inst => inst && inst.dispose());
   };
 });
@@ -405,4 +437,4 @@ onMounted(() => {
   border-radius: 50%;
   margin-right: 5px;
 }
-</style>
+</style>

+ 40 - 62
src/components/soilcdStatistics/fluxcdStatictics.vue

@@ -15,57 +15,53 @@
     </div>
     
     <!-- 1. 初始Cd 单独箱线图 -->
-<section class="mb-6 chart-container">
-  <h3 class="section-title text-base font-semibold">初始Cd(Initial_Cd)分布箱线图</h3>
-  <div ref="initialCdChart" style="width: 100%; height: 400px;"></div>
-  <!-- 容器内的加载遮罩 -->
-  <div v-if="isLoading" class="absolute inset-0 bg-white bg-opacity-80 flex items-center justify-center">
-    <div class="spinner"></div>
-  </div>
-  <!-- 错误提示(保留重试按钮) -->
-  <div v-if="error && !chartInstanceInitial" class="bg-yellow-50 border border-yellow-200 p-4 rounded mt-4">
-    <p class="text-yellow-700">图表初始化失败: {{ error.message }}</p>
-    <button class="mt-2 px-3 py-1 bg-yellow-500 text-white rounded" @click="initInitialCdChart">
-      重新尝试初始化
-    </button>
-  </div>
-</section>
+    <section class="mb-6 chart-container">
+      <h3 class="section-title text-base font-semibold">初始Cd(Initial_Cd)分布箱线图</h3>
+      <div ref="initialCdChart" style="width: 100%; height: 400px;"></div>
+      <div v-if="isLoading" class="absolute inset-0 bg-white bg-opacity-80 flex items-center justify-center">
+        <div class="spinner"></div>
+      </div>
+      <div v-if="error && !chartInstanceInitial" class="bg-yellow-50 border border-yellow-200 p-4 rounded mt-4">
+        <p class="text-yellow-700">图表初始化失败: {{ error.message }}</p>
+        <button class="mt-2 px-3 py-1 bg-yellow-500 text-white rounded" @click="initInitialCdChart">
+          重新尝试初始化
+        </button>
+      </div>
+    </section>
 
-<!-- 2. 其他指标 合并箱线图 -->
-<section class="mb-6 chart-container">
-  <div class="flex flex-wrap justify-between items-center mb-4">
-    <h3 class="section-title text-base font-semibold">其他通量Cd指标分布箱线图</h3>
-  </div>
-  <div ref="otherIndicatorsChart" style="width: 100%; height: 400px;"></div>
-  <!-- 容器内的加载遮罩 -->
-  <div v-if="isLoading" class="absolute inset-0 bg-white bg-opacity-80 flex items-center justify-center">
-    <div class="spinner"></div>
-  </div>
-  <!-- 错误提示(保留重试按钮) -->
-  <div v-if="error && !chartInstanceOther" class="bg-yellow-50 border border-yellow-200 p-4 rounded mt-4">
-    <p class="text-yellow-700">图表初始化失败: {{ error.message }}</p>
-    <button class="mt-2 px-3 py-1 bg-yellow-500 text-white rounded" @click="initOtherIndicatorsChart">
-      重新尝试初始化
-    </button>
-  </div>
-</section>
+    <!-- 2. 其他指标 合并箱线图 -->
+    <section class="mb-6 chart-container">
+      <div class="flex flex-wrap justify-between items-center mb-4">
+        <h3 class="section-title text-base font-semibold">其他通量Cd指标分布箱线图</h3>
+      </div>
+      <div ref="otherIndicatorsChart" style="width: 100%; height: 400px;"></div>
+      <!-- 容器内的加载遮罩 -->
+      <div v-if="isLoading" class="absolute inset-0 bg-white bg-opacity-80 flex items-center justify-center">
+        <div class="spinner"></div>
+      </div>
+      <!-- 错误提示(保留重试按钮) -->
+      <div v-if="error && !chartInstanceOther" class="bg-yellow-50 border border-yellow-200 p-4 rounded mt-4">
+        <p class="text-yellow-700">图表初始化失败: {{ error.message }}</p>
+        <button class="mt-2 px-3 py-1 bg-yellow-500 text-white rounded" @click="initOtherIndicatorsChart">
+          重新尝试初始化
+        </button>
+      </div>
+    </section>
   </div>
 </template>
 
 <script setup>
 import { ref, onMounted, nextTick } from 'vue';
 import * as echarts from 'echarts';
-import axios from 'axios';
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
 // 图表容器 & 实例
 const initialCdChart = ref(null);   // 初始Cd图表
 const otherIndicatorsChart = ref(null); // 其他指标图表
 const chartInstanceInitial = ref(null);   // 初始Cd实例
 const chartInstanceOther = ref(null);     // 其他指标实例
-const chartInstancePopup = ref(null);     // 弹窗实例
 
 // 响应式状态
-
 const isLoading = ref(true);
 const error = ref(null);
 const stats = ref({ samples: 0 });
@@ -92,8 +88,8 @@ const fieldConfig = {
 // 数据请求
 const fetchData = async () => {
   try {
-    const apiUrl = 'http://localhost:8000/api/vector/stats/FluxCd_input_data';
-    const response = await axios.get(apiUrl);
+    const apiUrl = '/api/vector/stats/FluxCd_input_data'; // 相对路径
+    const response = await api8000.get(apiUrl); // 使用 api8000
     const rawData = response.data.features 
       ? response.data.features.map(f => f.properties) 
       : response.data.data;
@@ -103,22 +99,6 @@ const fetchData = async () => {
   }
 };
 
-// 分位数计算(QUARTILE.INC)
-const calculatePercentile = (sortedArray, percentile) => {
-  const n = sortedArray.length;
-  if (n === 0) return null;
-  if (percentile <= 0) return sortedArray[0];
-  if (percentile >= 100) return sortedArray[n - 1];
-  
-  const index = (n - 1) * (percentile / 100);
-  const lowerIndex = Math.floor(index);
-  const upperIndex = lowerIndex + 1;
-  const fraction = index - lowerIndex;
-  
-  if (upperIndex >= n) return sortedArray[lowerIndex];
-  return sortedArray[lowerIndex] + fraction * (sortedArray[upperIndex] - sortedArray[lowerIndex]);
-};
-
 // 计算单个字段的统计量
 const calculateFieldStats = (statsData, fieldKey, fieldName) => {
   // 从接口数据中获取当前字段的统计结果
@@ -201,7 +181,6 @@ const initInitialCdChart = () => {
     const boxData = buildBoxplotData(initialCdStats.value);
     
     chartInstanceInitial.value.setOption({
-      // 保持原配置不变...
       title: { text: '初始Cd分布箱线图', left: 'center', textStyle: { fontSize: 14 } },
       tooltip: {
         trigger: "item",
@@ -236,7 +215,6 @@ const initInitialCdChart = () => {
   }
 };
 
-
 // 初始化【其他指标】合并图表
 const initOtherIndicatorsChart = () => {
   // 容器存在性检查
@@ -266,7 +244,6 @@ const initOtherIndicatorsChart = () => {
     const boxData = buildBoxplotData(otherIndicatorsStats.value);
     
     chartInstanceOther.value.setOption({
-      // 保持原配置不变...
       title: { text: '其他通量Cd指标分布对比', left: 'center', textStyle: { fontSize: 14 } },
       tooltip: {
         trigger: "item",
@@ -276,7 +253,12 @@ const initOtherIndicatorsChart = () => {
       xAxis: { 
         type: "category", 
         data: xAxisData,
-        axisLabel: { fontSize: 11, rotate: 45 }
+        axisLabel: { 
+          fontSize: 11, 
+          rotate: 45,
+          interval: 0, // 强制显示所有标签
+          formatter: (value) => value.length > 8 ? value.substring(0, 8) + '...' : value
+        }
       },
       yAxis: { 
         type: "value", 
@@ -300,8 +282,6 @@ const initOtherIndicatorsChart = () => {
   }
 };
 
-
-
 // Tooltip格式化(通用逻辑)
 const formatTooltip = (stat) => {
   if (!stat || !stat.min) {
@@ -380,7 +360,6 @@ onMounted(() => {
   const handleResize = () => {
     if (chartInstanceInitial.value) chartInstanceInitial.value.resize();
     if (chartInstanceOther.value) chartInstanceOther.value.resize();
-    if (chartInstancePopup.value) chartInstancePopup.value.resize();
   };
   window.addEventListener('resize', handleResize);
   
@@ -388,7 +367,6 @@ onMounted(() => {
     window.removeEventListener('resize', handleResize);
     if (chartInstanceInitial.value) chartInstanceInitial.value.dispose();
     if (chartInstanceOther.value) chartInstanceOther.value.dispose();
-    if (chartInstancePopup.value) chartInstancePopup.value.dispose();
   };
 });
 </script>

+ 2 - 1
src/locales/en.json

@@ -29,7 +29,8 @@
     "registerButton": "Register",
     "passwordMismatch": "Passwords do not match",
     "successMessage": "Registration successful, please log in",
-    "errorMessage": "Registration failed"
+    "errorMessage": "Registration failed",
+    "registerFailed": "Registration failed"
   },
   "validation": {
     "usernameRequired": "Please enter your username",

+ 15 - 15
src/locales/zh.json

@@ -33,7 +33,7 @@
     "registerButton": "注册",
     "passwordMismatch": "两次输入的密码不一致",
     "successMessage": "注册成功,请登录",
-    "errorMessage": "注册失败"
+    "registerFailed": "注册失败"
   },
   "validation": {
     "usernameRequired": "请输入账号",
@@ -47,7 +47,7 @@
   },
   "irrigationwater": {
     "Title": "灌溉水",
-    "irrigationwaterMethodsTitle": "采样方法和装置",
+    "irrigationwaterMethodsTitle": "灌溉水采样方法和装置",
     "irrigationwaterMethods": {
       "title1": "1.采样容器与过程",
       "content1": "采样容器均为500mL的白色聚乙烯瓶,采样体积均为500mL,采样过程在不同天气条件下进行,主要天气状况包括多云、阴天和小雨,采样点周边环境主要为河流,只有少数样品采集于水渠或瀑布区域。",
@@ -77,7 +77,7 @@
   },
   "atmosDeposition": {
     "Title": "大气干湿沉降",
-    "AtmosDepositionSamplingDescTitle": "采样说明",
+    "AtmosDepositionSamplingDescTitle": "​大气干湿沉降调查说明​",
     "heavyMetalEnterpriseTitle": "涉重企业",
     "heavyMetalEnterprise": {
       "MapTitle": "涉重企业地图展示",
@@ -96,11 +96,11 @@
     "airInputFluxTitle": "大气输入通量"
   },
   "agriInput": {
-    "Title": "农产品投入",
-    "farmInputSamplingDescTitle": "采样说明",
-    "prodInputFluxTitle": "农品输入通量"
+    "Title": "农业投入品",
+    "farmInputSamplingDescTitle": "农业投入品采样说明",
+    "prodInputFluxTitle": "农业投入品输入通量"
   },
-  "Title": "区域土壤重金属污染风险评估",
+  "Title": "区域土壤重金属污染风险评估系统",
   "Menu": {
     "dataManagement": "数据管理",
     "infoManagement": "信息管理",
@@ -135,23 +135,23 @@
   },
   "grainRemoval": {
     "Title": "籽粒移除",
-    "samplingDesc1": "采样说明",
+    "samplingDesc1": "籽粒移除采样说明",
     "grainRemovalInputFlux": "籽粒移除输出通量"
   },
   "strawRemoval": {
     "Title": "秸秆移除",
-    "samplingDesc2": "采样说明",
+    "samplingDesc2": "秸秆移除说明",
     "strawRemovalInputFlux": "秸秆移除输出通量"
   },
   "subsurfaceLeakage": {
     "Title": "地下渗漏",
-    "samplingDesc3": "采样说明",
-    "subsurfaceLeakageInputFlux": "地下渗漏输通量"
+    "samplingDesc3": "地下渗漏说明",
+    "subsurfaceLeakageInputFlux": "地下渗漏输通量"
   },
   "surfaceRunoff": {
     "Title": "地表径流",
-    "samplingDesc4": "采样说明",
-    "surfaceRunoffInputFlux": "地表径流输通量"
+    "samplingDesc4": "地表径流说明",
+    "surfaceRunoffInputFlux": "地表径流输通量"
   },
   "mapView": {
     "Title": "地图展示"
@@ -163,10 +163,10 @@
     "Title": "输出总通量"
   },
   "netFlux": {
-    "Title": "净通量"
+    "Title": "土壤镉净通量"
   },
   "currentYearConcentration": {
-    "Title": "当年浓度"
+    "Title": "土壤镉当年浓度"
   },
   "TotalCadmiumPrediction": {
     "Title": "土壤镉的总含量预测"

+ 50 - 36
src/router/index.ts

@@ -166,6 +166,20 @@ const routes = [
           import("@/views/User/HmOutFlux/atmosDeposition/airInputFlux.vue"),
         meta: { title: "大气输入通量" },
       },
+      {
+        path: "totalInputFluxDesc",
+        name: "totalInputFluxDesc",
+        component: () =>
+          import("@/views/User/HmOutFlux/totalInputFluxDesc.vue"),
+        meta: { title: "输入总通量说明" },
+      },
+      {
+        path: "totalOutputFluxDesc",
+        name: "totalOutputFluxDesc",
+        component: () =>
+          import("@/views/User/hmInFlux/totalOutputFluxDesc.vue"),
+        meta: { title: "输出总通量说明" },
+      },
       // {
       //   path: "hmInFlux",
       //   name: "hmInFlux",
@@ -192,7 +206,7 @@ const routes = [
           import(
             "@/views/User/hmInFlux/grainRemoval/grainRemovalInputFlux.vue"
           ),
-        meta: { title: "籽粒移除输通量" },
+        meta: { title: "籽粒移除输通量" },
       },
       // {
       //   path: "strawRemoval",
@@ -214,7 +228,7 @@ const routes = [
           import(
             "@/views/User/hmInFlux/strawRemoval/strawRemovalInputFlux.vue"
           ),
-        meta: { title: "秸秆移除输通量" },
+        meta: { title: "秸秆移除输通量" },
       },
       // {
       //   path: "subsurfaceLeakage",
@@ -236,7 +250,7 @@ const routes = [
           import(
             "@/views/User/hmInFlux/subsurfaceLeakage/subsurfaceLeakageInputFlux.vue"
           ),
-        meta: { title: "地下渗漏输通量" },
+        meta: { title: "地下渗漏输通量" },
       },
       // {
       //   path: "surfaceRunoff",
@@ -258,7 +272,7 @@ const routes = [
           import(
             "@/views/User/hmInFlux/surfaceRunoff/surfaceRunoffInputFlux.vue"
           ),
-        meta: { title: "地表径流输通量" },
+        meta: { title: "地表径流输通量" },
       },
       {
         path: "Calculation",
@@ -349,13 +363,13 @@ const routes = [
           import("@/views/User/cadmiumPrediction/CropCadmiumPrediction.vue"), // 修复路径
         meta: { title: "土壤镉作物态含量预测" },
       },
-      {
-        path: "cropRiskAssessment",
-        name: "cropRiskAssessment",
-        component: () =>
-          import("@/views/User/cropRiskAssessment/cropRiskAssessment.vue"), // 修复路径
-        meta: { title: "水稻镉污染风险" },
-      },
+      // {
+      //   path: "cropRiskAssessment",
+      //   name: "cropRiskAssessment",
+      //   component: () =>
+      //     import("@/views/User/cropRiskAssessment/cropRiskAssessment.vue"), // 修复路径
+      //   meta: { title: "水稻镉污染风险" },
+      // },
 
       {
         path: "farmlandQualityAssessment",
@@ -385,31 +399,31 @@ const routes = [
           ), // 修复路径
         meta: { title: "土壤降酸预测" },
       },
-      {
-        path: "TraditionalFarmingRisk",
-        name: "TraditionalFarmingRisk",
-        component: () =>
-          import("@/views/User/scenarioSimulation/TraditionalFarmingRisk.vue"), // 修复路径
-        meta: { title: "传统耕种习惯风险趋势" },
-      },
-      {
-        path: "HeavyMetalCadmiumControl",
-        name: "HeavyMetalCadmiumControl",
-        component: () =>
-          import(
-            "@/views/User/scenarioSimulation/HeavyMetalCadmiumControl.vue"
-          ), // 修复路径
-        meta: { title: "重金属镉污染治理" },
-      },
-      {
-        path: "SoilAcidificationControl",
-        name: "SoilAcidificationControl",
-        component: () =>
-          import(
-            "@/views/User/scenarioSimulation/SoilAcidificationControl.vue"
-          ), // 修复路径
-        meta: { title: "土壤酸化治理" },
-      },
+      // {
+      //   path: "TraditionalFarmingRisk",
+      //   name: "TraditionalFarmingRisk",
+      //   component: () =>
+      //     import("@/views/User/scenarioSimulation/TraditionalFarmingRisk.vue"), // 修复路径
+      //   meta: { title: "传统耕种习惯风险趋势" },
+      // },
+      // {
+      //   path: "HeavyMetalCadmiumControl",
+      //   name: "HeavyMetalCadmiumControl",
+      //   component: () =>
+      //     import(
+      //       "@/views/User/scenarioSimulation/HeavyMetalCadmiumControl.vue"
+      //     ), // 修复路径
+      //   meta: { title: "重金属镉污染治理" },
+      // },
+      // {
+      //   path: "SoilAcidificationControl",
+      //   name: "SoilAcidificationControl",
+      //   component: () =>
+      //     import(
+      //       "@/views/User/scenarioSimulation/SoilAcidificationControl.vue"
+      //     ), // 修复路径
+      //   meta: { title: "土壤酸化治理" },
+      // },
       {
         path: "DetectionStatistics",
         name: "DetectionStatistics",

+ 1 - 0
src/stores/mytoken.ts

@@ -4,6 +4,7 @@ export interface UserInfo {
   userId: number;
   name: string;
   loginType: "user" | "admin";
+  
 }
 
 interface TokenState {

+ 140 - 41
src/utils/request.ts

@@ -1,48 +1,147 @@
-// @/utils/request.ts
-import axios, { type AxiosRequestConfig, type AxiosResponse, isAxiosError } from "axios";
-import router from '@/router';
-import { useTokenStore } from '@/stores/mytoken';
-
-const requestAdmin = axios.create({
-    baseURL: import.meta.env.VITE_API_URL,
-    timeout: 10000,
-    headers: {
-        'Content-Type': 'application/json',
-    },
+// src/utils/request.ts
+import axios from 'axios';
+import type { 
+  AxiosInstance, 
+  AxiosRequestConfig, 
+  AxiosResponse, 
+  AxiosError,
+  InternalAxiosRequestConfig,
+  AxiosRequestHeaders
+} from 'axios';
+
+// 检测当前环境
+const isDevelopment = import.meta.env.MODE === 'development';
+
+// 定义接口
+interface CustomAxiosInstance extends AxiosInstance {
+  (config: AxiosRequestConfig): Promise<any>;
+}
+
+// 创建API客户端
+export const api5000: CustomAxiosInstance = axios.create({
+  baseURL: isDevelopment 
+    ? 'http://localhost:5000' 
+    : 'https://www.soilgd.com:5000',
+  timeout: 100000,
+  withCredentials: false  // 关键修改:除非需要cookie,否则设为false
 });
 
-requestAdmin.interceptors.request.use(
-    (config) => {
-        console.log('Starting Request', config);
-        return config;
-    },
-    (error: unknown) => {
-        console.error('Request error:', error);
-        return Promise.reject(error);
-    }
-);
-
-requestAdmin.interceptors.response.use(
-    (response: AxiosResponse) => response,
-    async (error: unknown) => {
-        if (isAxiosError(error)) {
-            console.error('Response error:', error.message);
-            if (error.response && error.response.status === 401) {
-                const tokenStore = useTokenStore();
-                tokenStore.clearToken();
-                router.push('/login');
-            }
-        } else {
-            console.error('Non-Axios error:', error);
+export const api8000: CustomAxiosInstance = axios.create({
+  baseURL: isDevelopment 
+    ? 'http://localhost:8000' 
+    : 'https://www.soilgd.com:8000',
+  timeout: 100000,
+  withCredentials: true
+});
+
+export const apiMain: CustomAxiosInstance = axios.create({
+  baseURL: isDevelopment 
+    ? 'http://localhost' 
+    : 'https://www.soilgd.com',
+  timeout: 100000,
+  withCredentials: true
+});
+
+
+//  检查是否为GeoJSON响应
+function isGeoJSONResponse(response: AxiosResponse): boolean {
+  const contentType = response.headers['content-type'] || '';
+  return (
+    contentType.includes('application/geo+json') ||
+    (contentType.includes('application/json') && 
+     (response.data?.type === 'FeatureCollection' || 
+      response.data?.type === 'Feature'))
+  );
+}
+
+const setupInterceptors = (instance: CustomAxiosInstance) => {
+  // 请求拦截器
+  instance.interceptors.request.use(
+    (config: InternalAxiosRequestConfig) => {
+      const token = localStorage.getItem('token');
+      if (token) {
+        // 使用新的headers API
+        if (!config.headers) {
+          config.headers = new axios.AxiosHeaders();
         }
-        return Promise.reject(error);
+        config.headers.set('Authorization', `Bearer ${token}`);
+      }
+      
+      // 为GeoJSON请求设置Accept头
+      if (config.url?.match(/\/geojson|\/vector/i)) {
+        if (!config.headers) {
+          config.headers = new axios.AxiosHeaders();
+        }
+        config.headers.set('Accept', 'application/geo+json, application/json');
+        
+        // 为GeoJSON请求设置更长的超时时间
+        if (!config.timeout || config.timeout < 180000) {
+          config.timeout = 180000; // 3分钟
+        }
+      }
+      
+      return config;
+    },
+    (error: AxiosError) => Promise.reject(error)
+  );
+  
+  // 响应拦截器
+  instance.interceptors.response.use(
+    (response: AxiosResponse) => {
+      // 根据响应类型和内容类型决定如何处理
+      const contentType = response.headers['content-type'] || '';
+      const isBlob = response.config.responseType === 'blob';
+      const isImage = contentType.includes('image/');
+      const isJSON = contentType.includes('application/json');
+      
+      console.log('响应处理:', {
+        url: response.config.url,
+        responseType: response.config.responseType,
+        contentType,
+        isBlob,
+        isImage,
+        isJSON
+      });
+      
+      // 1. 处理二进制响应(图片/文件下载)
+      if (isBlob || isImage) {
+        return response;
+      }
+      
+      // 2. 处理GeoJSON响应(新增部分)
+      if (isGeoJSONResponse(response)) {
+        console.log('检测到GeoJSON响应,返回完整响应对象');
+        return response;
+      }
+      
+      // 3. 处理普通JSON响应
+      if (isJSON) {
+        return response;
+      }
+      
+      // 4. 其他类型响应
+      return response;
+    },
+    (error: AxiosError) => {
+      if (error.response?.status === 401) {
+        localStorage.removeItem('token');
+        window.location.href = '/login';
+      }
+      return Promise.reject(error);
     }
-);
+  );
 
-console.log('Base URL:', requestAdmin.defaults.baseURL);
+  return instance;
+};
 
-// ✅ 改为默认导出
-export default requestAdmin; // 👈 修改这里
+// 设置拦截器
+setupInterceptors(api5000);
+setupInterceptors(api8000);
+setupInterceptors(apiMain);
 
-// ❌ 移除或注释掉原来的命名导出
-// export { requestAdmin };
+// 导出所有API客户端
+export default {
+  api5000,
+  api8000,
+  apiMain
+};

+ 14 - 24
src/views/User/HmOutFlux/agriInput/farmInputSamplingDesc.vue

@@ -12,8 +12,7 @@
           <div class="card-icon">🚜</div>
           <div class="card-content">
             <p>
-              实地走访调查项目实施区域作物种植面积、氮肥、磷肥、钾肥、复合肥、商品有机肥、
-              农药和农家肥等年均施用量。
+              对项目实施区域进行实地走访,调查作物种植面积,并统计化肥(氮、磷、钾、复合肥)、有机肥(商品有机肥、农家肥)及农药等农资的年均施用量。
             </p>
             <p>
               <span class="highlight">后期方案:</span>利用无人机光谱识别技术调查项目区内的种植结构,
@@ -80,7 +79,7 @@
     <div class="process-section">
       <div class="section-header">
         <div class="section-number">3</div>
-        <h2>采集农业投入品样品重金属含量测试</h2>
+        <h2>​农业投入品重金属含量的测定与评估​</h2>
       </div>
       
       <div class="section-content">
@@ -89,12 +88,7 @@
             <div class="card-icon">🔬</div>
             <div class="card-content">
               <p>
-                在完成农业投入品重金属输入通量计算公式的构建和优化后,最终的结果输出将包括
-                各类农业投入品的重金属输入通量数据。
-              </p>
-              <p>
-                这些数据能够量化每种农业投入品在农田中所引入的重金属污染,并为后续的政策制定
-                和管理提供依据。
+                对不同种类的农业投入品进行测试,从而分析比较其重金属含量差异。
               </p>
             </div>
           </div>
@@ -103,8 +97,7 @@
           <div class="testing-gallery">
             <div class="results-card">
               <div class="results-header">
-                <h3>各农业投入品重金属含量测试结果</h3>
-                <p>不同种类农业投入品中重金属含量对比分析</p>
+                <h3>各农业投入品重金属含量测试报告样例</h3>
               </div>
               <div class="image-container-proportional">
                 <el-image 
@@ -113,13 +106,12 @@
                   class="results-image-proportional">
                 </el-image>
               </div>
-              <p class="image-caption">图3-1 各农业投入品测试结果</p>
+              <p class="image-caption">图3-1 各农业投入品重金属含量测试报告样例</p>
             </div>
             
             <div class="results-card">
               <div class="results-header">
-                <h3>农业投入品镉含量专项分析</h3>
-                <p>各类农业投入品中镉元素含量分布情况</p>
+                <h3>各类农业投入品中镉元素含量分布情况</h3>
               </div>
               <div class="image-container-proportional">
                 <el-image 
@@ -150,9 +142,6 @@
               通过农业投入品的施用数据与重金属检测结果,建立重金属输入通量计算公式,
               精确计算不同农业投入品在农田中所引入的重金属污染通量。
             </p>
-            <p>
-              该阶段包括公式的初步构建、验证与校准,以及公式的持续优化。
-            </p>
           </div>
         </div>
         
@@ -160,8 +149,8 @@
           <div class="formula-card">
             <div class="formula-image">
               <el-image :src="image8" alt="农业投入品用量计算方法" class="sampling-image"></el-image>
-              <p class="image-caption">图4-1 农业投入品用量计算方法</p>
             </div>
+            <p class="image-caption">图4-1 农业投入品用量计算方法</p>
             <div class="formula-text">
               <p>式中:</p>
               <p>N<sub>i</sub>为农业投入品i的单位面积年施用量,单位:kg/ha/a;</p>
@@ -173,8 +162,9 @@
           <div class="formula-card">
             <div class="formula-image">
               <el-image :src="image9" alt="农业投入品重金属输入通量计算方法" class="sampling-image"></el-image>
-              <p class="image-caption">图4-2 农业投入品重金属输入通量计算方法</p>
             </div>
+                          <p class="image-caption">图4-2 农业投入品重金属输入通量计算方法</p>
+
             <div class="formula-text">
               <p>式中:</p>
               <p>F<sub>f</sub>为单位面积农业投入品途径的重金属输入通量,g/ha/a;</p>
@@ -193,10 +183,10 @@ export default {
   data() {
     return {
       image1: '/农业投入品使用情况.png',
-      image2: '/农业化肥采集.png',
-      image3: '/农药采集.png',
-      image4: '/农膜采集.png',
-      image5: '/有机肥采集.png',
+      image2: '/农业化肥采集(1).png',
+      image3: '/农药采集(1).png',
+      image4: '/农膜采集(1).png',
+      image5: '/有机肥采集(1).png',
       image6: '/各农业投入品测试结果.png',
       image7: '/农业投入品镉含量.png',
       image8: '/农业投入品用量计算方法.png',
@@ -486,7 +476,7 @@ h2 {
   flex-direction: column;
   border-radius: 15px;
   overflow: hidden;
-  background: rgba(250, 255, 245, 0.7);
+  background: rgba(255, 255, 255, 0.7);
   box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1);
   border-top: 3px solid #5cb85c;
 }

+ 761 - 532
src/views/User/HmOutFlux/agriInput/prodInputFlux.vue

@@ -1,128 +1,274 @@
 <template>
-  <div class="fertilizer-input-form">
-    <!-- 输入表单部分 -->
-    <el-card v-if="showInputForm" shadow="always" class="form-card">
-      <div class="card-content">
-        <div class="input-section">
-          <el-form label-width="250px" label-position="top">
-            <div class="form-section">
-              <div class="input-group">
-                <el-form-item label="氮肥镉含量平均值 (mg/kg)" class="form-item">
-                  <el-input v-model="formData.f3_nitrogen_cd_content" placeholder="0.12"></el-input>
-                </el-form-item>
-                <el-form-item label="氮肥单位面积使用量 (t/ha/a)" class="form-item">
-                  <el-input v-model="formData.nf_nitrogen_usage" placeholder="0.25"></el-input>
-                </el-form-item>
+  <div class="agricultural-input-management">
+    <!-- 计算页面 -->
+    <div v-if="showInputForm" class="page-container">
+      <el-card class="gradient-card">
+        <div class="calculation-content">
+          <h2 class="page-title">农业投入品输入通量计算</h2>
+          <div class="scrollable-content">
+            <el-form label-position="left">
+              <div class="input-section">
+                <!-- 第一行:氮肥的两个输入栏 -->
+                <div class="input-row">
+                  <div class="input-item">
+                    <div class="input-title">氮肥镉含量平均值 (mg/kg)</div>
+                    <el-input
+                      v-model="formData.f3_nitrogen_cd_content"
+                      placeholder="0.12"
+                      size="large"
+                      class="fixed-width-input"
+                    />
+                  </div>
+                  <div class="input-item">
+                    <div class="input-title">氮肥单位面积使用量 (t/ha/a)</div>
+                    <el-input
+                      v-model="formData.nf_nitrogen_usage"
+                      placeholder="0.25"
+                      size="large"
+                      class="fixed-width-input"
+                    />
+                  </div>
+                </div>
+                
+                <!-- 第二行:磷肥的两个输入栏 -->
+                <div class="input-row">
+                  <div class="input-item">
+                    <div class="input-title">磷肥镉含量平均值 (mg/kg)</div>
+                    <el-input
+                      v-model="formData.f4_phosphorus_cd_content"
+                      placeholder="0.85"
+                      size="large"
+                      class="fixed-width-input"
+                    />
+                  </div>
+                  <div class="input-item">
+                    <div class="input-title">磷肥单位面积使用量 (t/ha/a)</div>
+                    <el-input
+                      v-model="formData.pf_phosphorus_usage"
+                      placeholder="0.15"
+                      size="large"
+                      class="fixed-width-input"
+                    />
+                  </div>
+                </div>
+                
+                <!-- 第三行:钾肥的两个输入栏 -->
+                <div class="input-row">
+                  <div class="input-item">
+                    <div class="input-title">钾肥镉含量平均值 (mg/kg)</div>
+                    <el-input
+                      v-model="formData.f5_potassium_cd_content"
+                      placeholder="0.05"
+                      size="large"
+                      class="fixed-width-input"
+                    />
+                  </div>
+                  <div class="input-item">
+                    <div class="input-title">钾肥单位面积使用量 (t/ha/a)</div>
+                    <el-input
+                      v-model="formData.kf_potassium_usage"
+                      placeholder="0.12"
+                      size="large"
+                      class="fixed-width-input"
+                    />
+                  </div>
+                </div>
+                
+                <!-- 第四行:复合肥的两个输入栏 -->
+                <div class="input-row">
+                  <div class="input-item">
+                    <div class="input-title">复合肥镉含量平均值 (mg/kg)</div>
+                    <el-input
+                      v-model="formData.f6_compound_cd_content"
+                      placeholder="0.45"
+                      size="large"
+                      class="fixed-width-input"
+                    />
+                  </div>
+                  <div class="input-item">
+                    <div class="input-title">复合肥单位面积使用量 (t/ha/a)</div>
+                    <el-input
+                      v-model="formData.cf_compound_usage"
+                      placeholder="0.30"
+                      size="large"
+                      class="fixed-width-input"
+                    />
+                  </div>
+                </div>
+                
+                <!-- 第五行:有机肥的两个输入栏 -->
+                <div class="input-row">
+                  <div class="input-item">
+                    <div class="input-title">有机肥镉含量平均值 (mg/kg)</div>
+                    <el-input
+                      v-model="formData.f7_organic_cd_content"
+                      placeholder="0.22"
+                      size="large"
+                      class="fixed-width-input"
+                    />
+                  </div>
+                  <div class="input-item">
+                    <div class="input-title">有机肥单位面积使用量 (t/ha/a)</div>
+                    <el-input
+                      v-model="formData.of_organic_usage"
+                      placeholder="2.50"
+                      size="large"
+                      class="fixed-width-input"
+                    />
+                  </div>
+                </div>
+                
+                <!-- 第六行:农药的两个输入栏 -->
+                <div class="input-row">
+                  <div class="input-item">
+                    <div class="input-title">农药镉含量 (mg/kg)</div>
+                    <el-input
+                      v-model="formData.f8_pesticide_cd_content"
+                      placeholder="0.08"
+                      size="large"
+                      class="fixed-width-input"
+                    />
+                  </div>
+                  <div class="input-item">
+                    <div class="input-title">农药单位面积使用量 (t/ha/a)</div>
+                    <el-input
+                      v-model="formData.p_pesticide_usage"
+                      placeholder="0.02"
+                      size="large"
+                      class="fixed-width-input"
+                    />
+                  </div>
+                </div>
+                
+                <!-- 第七行:农家肥的两个输入栏 -->
+                <div class="input-row">
+                  <div class="input-item">
+                    <div class="input-title">农家肥镉含量 (mg/kg)</div>
+                    <el-input
+                      v-model="formData.f9_farmyard_cd_content"
+                      placeholder="0.15"
+                      size="large"
+                      class="fixed-width-input"
+                    />
+                  </div>
+                  <div class="input-item">
+                    <div class="input-title">农家肥单位面积使用量 (t/ha/a)</div>
+                    <el-input
+                      v-model="formData.ff_farmyard_usage"
+                      placeholder="1.80"
+                      size="large"
+                      class="fixed-width-input"
+                    />
+                  </div>
+                </div>
+                
+                <!-- 第八行:农膜的两个输入栏 -->
+                <div class="input-row">
+                  <div class="input-item">
+                    <div class="input-title">农膜镉含量 (mg/kg)</div>
+                    <el-input
+                      v-model="formData.f10_film_cd_content"
+                      placeholder="0.03"
+                      size="large"
+                      class="fixed-width-input"
+                    />
+                  </div>
+                  <div class="input-item">
+                    <div class="input-title">农膜单位面积使用量 (t/ha/a)</div>
+                    <el-input
+                      v-model="formData.af_film_usage"
+                      placeholder="0.05"
+                      size="large"
+                      class="fixed-width-input"
+                    />
+                  </div>
+                </div>
               </div>
-              <div class="input-group">
-                <el-form-item label="磷肥镉含量平均值 (mg/kg)" class="form-item">
-                  <el-input v-model="formData.f4_phosphorus_cd_content" placeholder="0.85"></el-input>
-                </el-form-item>
-                <el-form-item label="磷肥单位面积使用量 (t/ha/a)" class="form-item">
-                  <el-input v-model="formData.pf_phosphorus_usage" placeholder="0.15"></el-input>
-                </el-form-item>
-              </div>
-              <div class="input-group">
-                <el-form-item label="钾肥镉含量平均值 (mg/kg)" class="form-item">
-                  <el-input v-model="formData.f5_potassium_cd_content" placeholder="0.05"></el-input>
-                </el-form-item>
-                <el-form-item label="钾肥单位面积使用量 (t/ha/a)" class="form-item">
-                  <el-input v-model="formData.kf_potassium_usage" placeholder="0.12"></el-input>
-                </el-form-item>
-              </div>
-              <div class="input-group">
-                <el-form-item label="复合肥镉含量平均值 (mg/kg)" class="form-item">
-                  <el-input v-model="formData.f6_compound_cd_content" placeholder="0.45"></el-input>
-                </el-form-item>
-                <el-form-item label="复合肥单位面积使用量 (t/ha/a)" class="form-item">
-                  <el-input v-model="formData.cf_compound_usage" placeholder="0.30"></el-input>
-                </el-form-item>
-              </div>
-              <div class="input-group">
-                <el-form-item label="有机肥镉含量平均值 (mg/kg)" class="form-item">
-                  <el-input v-model="formData.f7_organic_cd_content" placeholder="0.22"></el-input>
-                </el-form-item>
-                <el-form-item label="有机肥单位面积使用量 (t/ha/a)" class="form-item">
-                  <el-input v-model="formData.of_organic_usage" placeholder="2.50"></el-input>
-                </el-form-item>
-              </div>
-              <div class="input-group">
-                <el-form-item label="农药镉含量 (mg/kg)" class="form-item">
-                  <el-input v-model="formData.f8_pesticide_cd_content" placeholder="0.08"></el-input>
-                </el-form-item>
-                <el-form-item label="农药单位面积使用量 (t/ha/a)" class="form-item">
-                  <el-input v-model="formData.p_pesticide_usage" placeholder="0.02"></el-input>
-                </el-form-item>
-              </div>
-              <div class="input-group">
-                <el-form-item label="农家肥镉含量 (mg/kg)" class="form-item">
-                  <el-input v-model="formData.f9_farmyard_cd_content" placeholder="0.15"></el-input>
-                </el-form-item>
-                <el-form-item label="农家肥单位面积使用量 (t/ha/a)" class="form-item">
-                  <el-input v-model="formData.ff_farmyard_usage" placeholder="1.80"></el-input>
-                </el-form-item>
-              </div>
-              <div class="input-group">
-                <el-form-item label="农膜镉含量 (mg/kg)" class="form-item">
-                  <el-input v-model="formData.f10_film_cd_content" placeholder="0.03"></el-input>
-                </el-form-item>
-                <el-form-item label="农膜(存留)单位面积使用量 (t/ha/a)" class="form-item">
-                  <el-input v-model="formData.af_film_usage" placeholder="0.05"></el-input>
-                </el-form-item>
-              </div>
-            </div>
-          </el-form>
-        </div>
-        
-        <div class="button-section">
-          <div class="button-bg"></div>
-          <div class="bottom-overlay"></div>
-          <el-button 
-            class="calculate-btn" 
-            @click="calculateAndVisualize"
-            :loading="loading"
-          >
-            <span class="btn-text">计算并生成可视化</span>
-          </el-button>
+            </el-form>
+          </div>
+
+          <div class="button-container">
+            <el-button
+              class="calculate-btn"
+              @click="calculateAndVisualize"
+              :loading="loading"
+              size="large"
+            >
+              计算农业投入品输入通量
+            </el-button>
+          </div>
         </div>
+      </el-card>
+    </div>
+
+    <!-- 结果页面 -->
+    <div v-if="!showInputForm" class="page-container">
+      <div class="toolbar">
+        <el-button class="custom-button back-button" @click="showInputForm = true">
+          返回计算
+        </el-button>
       </div>
-    </el-card>
-    
-    <!-- 结果页面部分 -->
-    <div v-if="!showInputForm" class="results-page">
-      <!-- 返回按钮 -->
-      <el-button 
-        type="primary" 
-        class="back-button"
-        @click="showInputForm = true"
-      >
-        返回计算
-      </el-button>
-      <!-- 结果页面标题 -->
-      <h2 class="results-title">农业投入Cd通量计算结果</h2>
       
-      <!-- 自定义数据计算结果卡片 -->
-      <el-card class="result-card" v-if="customResult.success">
-        <h3>农业投入Cd通量计算结果</h3>
-        <p>总通量: {{ customResult.data.total_cd_flux }} g/ha/a</p>
-        <el-table :data="customResultDetails" border>
-          <el-table-column prop="type" label="投入类型"></el-table-column>
-          <el-table-column prop="flux" label="Cd通量(g/ha/a)"></el-table-column>
-        </el-table>
-        <div class="chart-container">
-            <div ref="customPieChart" style="width: 100%; height: 400px;"></div>
+      <el-card class="results-card">
+        <div class="results-content">
+          <!-- 地图区域 -->
+          <div class="map-section">
+            <h3>农业投入品Cd通量分布图</h3>
+            <div v-if="loadingMap" class="loading-container">
+              <el-icon class="loading-icon"><Loading /></el-icon>
+              <span>地图加载中...</span>
+            </div>
+            <div class="image-container">
+              <img v-if="mapImageUrl" :src=mapImageUrl class="result-image"></img>
+              <div v-if="!mapImageUrl && !loadingMap" class="no-data">
+                <el-icon><Picture /></el-icon>
+                <p>暂无地图数据</p>
+              </div>
+            </div>
           </div>
           
-        <!-- 添加地图展示区域 -->
-        <div class="map-container">
-          <h3>空间分布图</h3>
-          <div class="map-wrapper">
-            <div v-if="mapImageUrl" class="map-image-container">
-              <img :src=mapImageUrl alt="农业投入品输入通量分布图" class="map-image">
+          <!-- 统计图表区域 -->
+          <div class="stats-area">
+            <h3>农业投入品Cd通量统计信息</h3>
+            <div class="model-info">
+              <el-tag type="info">农业投入品Cd通量模型</el-tag>
+              <span class="update-time">
+                最后更新: {{ updateTime ? new Date(updateTime).toLocaleString() : '未知' }}
+              </span>
             </div>
-            <div v-else class="map-placeholder">
-              <p>地图生成中...</p>
-              <el-progress :percentage="mapProgress" :status="mapStatus"></el-progress>
+            
+            <div v-if="loadingStats" class="loading-container">
+              <el-icon class="loading-icon"><Loading /></el-icon>
+              <span>统计数据加载中...</span>
+            </div>
+            
+            <div v-if="!loadingStats && customResult.data" class="stats-container">
+              <div class="total-flux">
+                总通量: {{ customResult.data.total_cd_flux }} g/ha/a
+              </div>
+              
+              <el-table 
+                :data="customResultDetails" 
+                style="width: 100%; margin-bottom: 15px;"
+                border
+                stripe
+              >
+                <el-table-column prop="type" label="投入类型" align="center" min-width="120" />
+                <el-table-column prop="flux" label="Cd通量(g/ha/a)" align="center" :formatter="formatNumber" min-width="120" />
+              </el-table>
+            </div>
+            
+            <div v-if="!loadingStats && !customResult.data" class="no-data">
+              <el-icon><DataAnalysis /></el-icon>
+              <p>暂无统计数据</p>
+            </div>
+          </div>
+          
+          <!-- 饼图区域 -->
+          <div class="chart-section">
+            <h3>农业投入品Cd通量占比</h3>
+            <div class="image-container">
+              <div ref="pieChart" style="width: 100%; height: 350px;"></div>
             </div>
           </div>
         </div>
@@ -132,96 +278,131 @@
 </template>
 
 <script>
-import axios from 'axios';
+import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
+import { 
+  ElInput, 
+  ElButton, 
+  ElMessage, 
+  ElCard, 
+  ElTable, 
+  ElTableColumn,
+  ElTag,
+  ElIcon,
+  ElForm
+} from 'element-plus';
+import { 
+  Loading, 
+  Picture, 
+  DataAnalysis 
+} from '@element-plus/icons-vue';
 import * as echarts from 'echarts';
-import { ElNotification } from 'element-plus';
+import { api8000 } from '@/utils/request';
 
 export default {
-  data() {
-    return {
-      showInputForm: true,
-      formData: {
-        f3_nitrogen_cd_content: "0.12",
-        f4_phosphorus_cd_content: "0.85",
-        f5_potassium_cd_content: "0.05",
-        f6_compound_cd_content: "0.45",
-        f7_organic_cd_content: "0.22",
-        f8_pesticide_cd_content: "0.08",
-        f9_farmyard_cd_content: "0.15",
-        f10_film_cd_content: "0.03",
-        nf_nitrogen_usage: "0.25",
-        pf_phosphorus_usage: "0.15",
-        kf_potassium_usage: "0.12",
-        cf_compound_usage: "0.30",
-        of_organic_usage: "2.50",
-        p_pesticide_usage: "0.02",
-        ff_farmyard_usage: "1.80",
-        af_film_usage: "0.05",
-        description: "自定义数据计算结果"
-      },
-      loading: false,
-      customResult: {},
-      customPieChart: null,
-      mapImageUrl: null,
-      mapProgress: 0,
-      mapStatus: 'success',
-      mapInterval: null
-    };
+  components: {
+    ElInput,
+    ElButton,
+    ElCard,
+    ElTable,
+    ElTableColumn,
+    ElTag,
+    ElIcon,
+    ElForm,
+    Loading,
+    Picture,
+    DataAnalysis
   },
-  computed: {
-    customResultDetails() {
-      if (!this.customResult.data || !this.customResult.data.details) return [];
-      return Object.entries(this.customResult.data.details).map(([type, flux]) => ({
-        type: this.getTypeName(type),
-        flux: flux
-      }));
-    },
-    customPieData() {
-      if (!this.customResult.data || !this.customResult.data.details) return [];
-      return Object.entries(this.customResult.data.details).map(([type, value]) => ({
-        name: this.getTypeName(type),
-        value: value
-      }));
-    }
-  },
-  methods: {
-    async calculateAndVisualize() {
+  setup() {
+    // 计算页面数据
+    const showInputForm = ref(true);
+    const formData = ref({
+      f3_nitrogen_cd_content: "0.12",
+      f4_phosphorus_cd_content: "0.85",
+      f5_potassium_cd_content: "0.05",
+      f6_compound_cd_content: "0.45",
+      f7_organic_cd_content: "0.22",
+      f8_pesticide_cd_content: "0.08",
+      f9_farmyard_cd_content: "0.15",
+      f10_film_cd_content: "0.03",
+      nf_nitrogen_usage: "0.25",
+      pf_phosphorus_usage: "0.15",
+      kf_potassium_usage: "0.12",
+      cf_compound_usage: "0.30",
+      of_organic_usage: "2.50",
+      p_pesticide_usage: "0.02",
+      ff_farmyard_usage: "1.80",
+      af_film_usage: "0.05"
+    });
+    const loading = ref(false);
+    
+    // 结果页面数据
+    const mapImageUrl = ref('');
+    const customResult = ref({});
+    const updateTime = ref(new Date().toISOString());
+    const loadingMap = ref(false);
+    const loadingStats = ref(false);
+    const pieChart = ref(null);
+    
+    // 格式化数字显示(保留4位小数)
+    const formatNumber = (row, column, cellValue) => {
+      if (typeof cellValue === 'number') {
+        return cellValue.toFixed(4);
+      }
+      return cellValue;
+    };
+    
+    // 获取类型名称
+    const getTypeName = (type) => {
+      const typeNames = {
+        'nitrogen_fertilizer': '氮肥',
+        'phosphorus_fertilizer': '磷肥',
+        'potassium_fertilizer': '钾肥',
+        'compound_fertilizer': '复合肥',
+        'organic_fertilizer': '有机肥',
+        'pesticide': '农药',
+        'farmyard_manure': '农家肥',
+        'agricultural_film': '农膜'
+      };
+      return typeNames[type] || type;
+    };
+    
+    // 计算详情数据
+    const customResultDetails = ref([]);
+    
+    // 计算并可视化
+    const calculateAndVisualize = async () => {
       try {
-        this.loading = true;
-        this.mapImageUrl = null;
-        this.mapProgress = 0;
-        this.mapStatus = 'success';
+        loading.value = true;
+        loadingMap.value = true;
+        loadingStats.value = true;
+        mapImageUrl.value = '';
         
         // 准备请求数据
         const requestBody = {
-          ...this.formData,
+          ...formData.value,
           // 将字符串值转换为数字
-          f3_nitrogen_cd_content: parseFloat(this.formData.f3_nitrogen_cd_content),
-          f4_phosphorus_cd_content: parseFloat(this.formData.f4_phosphorus_cd_content),
-          f5_potassium_cd_content: parseFloat(this.formData.f5_potassium_cd_content),
-          f6_compound_cd_content: parseFloat(this.formData.f6_compound_cd_content),
-          f7_organic_cd_content: parseFloat(this.formData.f7_organic_cd_content),
-          f8_pesticide_cd_content: parseFloat(this.formData.f8_pesticide_cd_content),
-          f9_farmyard_cd_content: parseFloat(this.formData.f9_farmyard_cd_content),
-          f10_film_cd_content: parseFloat(this.formData.f10_film_cd_content),
-          nf_nitrogen_usage: parseFloat(this.formData.nf_nitrogen_usage),
-          pf_phosphorus_usage: parseFloat(this.formData.pf_phosphorus_usage),
-          kf_potassium_usage: parseFloat(this.formData.kf_potassium_usage),
-          cf_compound_usage: parseFloat(this.formData.cf_compound_usage),
-          of_organic_usage: parseFloat(this.formData.of_organic_usage),
-          p_pesticide_usage: parseFloat(this.formData.p_pesticide_usage),
-          ff_farmyard_usage: parseFloat(this.formData.ff_farmyard_usage),
-          af_film_usage: parseFloat(this.formData.af_film_usage),
-          description: this.formData.description
+          f3_nitrogen_cd_content: parseFloat(formData.value.f3_nitrogen_cd_content),
+          f4_phosphorus_cd_content: parseFloat(formData.value.f4_phosphorus_cd_content),
+          f5_potassium_cd_content: parseFloat(formData.value.f5_potassium_cd_content),
+          f6_compound_cd_content: parseFloat(formData.value.f6_compound_cd_content),
+          f7_organic_cd_content: parseFloat(formData.value.f7_organic_cd_content),
+          f8_pesticide_cd_content: parseFloat(formData.value.f8_pesticide_cd_content),
+          f9_farmyard_cd_content: parseFloat(formData.value.f9_farmyard_cd_content),
+          f10_film_cd_content: parseFloat(formData.value.f10_film_cd_content),
+          nf_nitrogen_usage: parseFloat(formData.value.nf_nitrogen_usage),
+          pf_phosphorus_usage: parseFloat(formData.value.pf_phosphorus_usage),
+          kf_potassium_usage: parseFloat(formData.value.kf_potassium_usage),
+          cf_compound_usage: parseFloat(formData.value.cf_compound_usage),
+          of_organic_usage: parseFloat(formData.value.of_organic_usage),
+          p_pesticide_usage: parseFloat(formData.value.p_pesticide_usage),
+          ff_farmyard_usage: parseFloat(formData.value.ff_farmyard_usage),
+          af_film_usage: parseFloat(formData.value.af_film_usage)
         };
 
-        // 启动进度条模拟
-        this.startProgressSimulation();
-
-        // 调用计算并可视化接口(返回FileResponse)
+        // 调用计算并可视化接口
         const [mapResponse, calcResponse] = await Promise.all([
-          axios.post(
-            'http://localhost:8000/api/agricultural-input/calculate-and-visualize-file',
+          api8000.post(
+            '/api/agricultural-input/calculate-and-visualize-file',
             requestBody,
             {
               params: {
@@ -232,443 +413,491 @@ export default {
                 enable_interpolation: false,
                 cleanup_intermediate: true
               },
-              responseType: 'blob' // 重要:指定响应类型为blob
+              responseType: 'blob'
             }
           ),
-          // 同时调用计算接口获取结果数据
-          axios.post(
-            'http://localhost:8000/api/agricultural-input/calculate-with-custom-data',
+          api8000.post(
+            '/api/agricultural-input/calculate-with-custom-data',
             requestBody
           )
         ]);
-
+        
         // 处理地图响应
         const blob = new Blob([mapResponse.data], { type: 'image/jpeg' });
-        this.mapImageUrl = URL.createObjectURL(blob);
-
+        mapImageUrl.value = URL.createObjectURL(blob);
+        
         // 处理计算结果
         if (calcResponse.data.success) {
-          this.customResult = {
+          customResult.value = {
             success: true,
             data: calcResponse.data.data
           };
-
-          // 显示结果页面
-          this.showInputForm = false;
-
+          
+          // 更新详情数据
+          customResultDetails.value = Object.entries(customResult.value.data.details).map(([type, flux]) => ({
+            type: getTypeName(type),
+            flux: flux
+          }));
+          
+          showInputForm.value = false;
+          updateTime.value = new Date().toISOString();
+          
           // 初始化图表
-          this.$nextTick(() => {
-            this.initCharts();
-          });
+          initPieChart();
+          
+          ElMessage.success('计算完成,结果已展示');
         } else {
           throw new Error(calcResponse.data.message);
         }
         
       } catch (error) {
         console.error('API调用错误:', error);
-        this.mapStatus = 'exception';
-        ElNotification.error({
-          title: '计算失败',
-          message: error.message || '请检查网络连接',
-          duration: 5000
-        });
+        ElMessage.error('计算失败: ' + (error.message || '请检查网络连接'));
       } finally {
-        this.stopProgressSimulation();
-        this.loading = false;
+        loading.value = false;
+        loadingMap.value = false;
+        loadingStats.value = false;
       }
-    },
-
-    getTypeName(type) {
-      const typeNames = {
-        'nitrogen_fertilizer': '氮肥',
-        'phosphorus_fertilizer': '磷肥',
-        'potassium_fertilizer': '钾肥',
-        'compound_fertilizer': '复合肥',
-        'organic_fertilizer': '有机肥',
-        'pesticide': '农药',
-        'farmyard_manure': '农家肥',
-        'agricultural_film': '农膜'
-      };
-      return typeNames[type] || type;
-    },
-
-    initCharts() {
-      if (this.customPieChart) {
-        this.customPieChart.dispose();
-      }
-      
-      this.customPieChart = echarts.init(this.$refs.customPieChart);
-      this.customPieChart.setOption(this.getPieChartOption('各项投入通量占比', this.customPieData));
+    };
+    
+    // 初始化饼图
+    const initPieChart = () => {
+      if (!customResult.value.data || !customResult.value.data.details) return;
       
-      window.addEventListener('resize', this.onResize);
-    },
-
-    getPieChartOption(title, data) {
-      return {
-        title: {
-          text: title,
-          left: 'center',
-          textStyle: {
-            fontSize: 16,
-            fontWeight: 'bold'
-          }
-        },
-        tooltip: {
-          trigger: 'item',
-          formatter: '{a} <br/>{b}: {c} g/ha/a ({d}%)'
-        },
-        legend: {
-          orient: 'vertical',
-          right: 10,
-          top: 'center',
-          data: data.map(item => item.name)
-        },
-        series: [
-          {
-            name: title,
-            type: 'pie',
-            radius: ['40%', '70%'],
-            center: ['40%', '50%'],
-            avoidLabelOverlap: false,
-            itemStyle: {
-              borderRadius: 10,
-              borderColor: '#fff',
-              borderWidth: 2
-            },
-            label: {
-              show: false,
-              position: 'center'
-            },
-            emphasis: {
+      nextTick(() => {
+        const chartContainer = document.querySelector('.chart-section .image-container');
+        if (!chartContainer) {
+          console.error('图表容器未找到');
+          return;
+        }
+        
+        // 处理图表数据
+        const chartData = Object.entries(customResult.value.data.details)
+          .filter(([_, value]) => value > 0)
+          .map(([type, value]) => ({
+            name: getTypeName(type),
+            value: parseFloat(value.toFixed(4))
+          }));
+        
+        // 销毁旧图表实例
+        if (pieChart.value) {
+          pieChart.value.dispose();
+        }
+        
+        // 初始化新图表
+        pieChart.value = echarts.init(chartContainer);
+        
+        // 设置图表选项
+        const option = {
+          tooltip: {
+            trigger: 'item',
+            formatter: '{a} <br/>{b}: {c} g/ha/a ({d}%)'
+          },
+          legend: {
+            orient: 'vertical',
+            right: 10,
+            top: 'center',
+            data: chartData.map(item => item.name)
+          },
+          series: [
+            {
+              name: '农业投入品Cd通量占比',
+              type: 'pie',
+              radius: ['40%', '70%'],
+              center: ['40%', '50%'],
+              avoidLabelOverlap: false,
+              itemStyle: {
+                borderRadius: 10,
+                borderColor: '#fff',
+                borderWidth: 2
+              },
               label: {
-                show: true,
-                fontSize: '16',
-                fontWeight: 'bold',
-                formatter: '{b}\n{c} g/ha/a\n{d}%'
-              }
-            },
-            labelLine: {
-              show: false
-            },
-            data: data
-          }
-        ]
-      };
-    },
-
-    onResize() {
-      if (this.customPieChart) {
-        this.customPieChart.resize();
+                show: false,
+                position: 'center'
+              },
+              emphasis: {
+                label: {
+                  show: true,
+                  fontSize: '16',
+                  fontWeight: 'bold',
+                  formatter: '{b}\n{c} g/ha/a\n({d}%)'
+                }
+              },
+              labelLine: {
+                show: false
+              },
+              data: chartData
+            }
+          ],
+          responsive: true,
+          animation: true,
+          animationDuration: 1000,
+          animationEasing: 'cubicOut'
+        };
+        
+        // 设置图表选项
+        pieChart.value.setOption(option);
+        
+        // 添加窗口大小变化监听
+        window.addEventListener('resize', function() {
+          pieChart.value.resize();
+        });
+      });
+    };
+    
+    // 响应式调整
+    const handleResize = () => {
+      if (pieChart.value) {
+        pieChart.value.resize();
       }
-    },
-
-    startProgressSimulation() {
-      this.mapInterval = setInterval(() => {
-        if (this.mapProgress < 90) {
-          this.mapProgress += 10;
-        } else if (this.mapProgress < 99) {
-          this.mapProgress += 1;
-        }
-      }, 500);
-    },
-
-    stopProgressSimulation() {
-      if (this.mapInterval) {
-        clearInterval(this.mapInterval);
-        this.mapInterval = null;
+    };
+    
+    onMounted(() => {
+      window.addEventListener('resize', handleResize);
+    });
+    
+    onBeforeUnmount(() => {
+      window.removeEventListener('resize', handleResize);
+      if (pieChart.value) {
+        pieChart.value.dispose();
       }
-      this.mapProgress = 100;
-    }
-  },
-  beforeUnmount() {
-    window.removeEventListener('resize', this.onResize);
-    if (this.customPieChart) {
-      this.customPieChart.dispose();
-    }
-    if (this.mapInterval) {
-      clearInterval(this.mapInterval);
-    }
-    // 释放Blob URL内存
-    if (this.mapImageUrl) {
-      URL.revokeObjectURL(this.mapImageUrl);
-    }
+      if (mapImageUrl.value) {
+        URL.revokeObjectURL(mapImageUrl.value);
+      }
+    });
+    
+    return {
+      showInputForm,
+      formData,
+      loading,
+      mapImageUrl,
+      customResult,
+      updateTime,
+      loadingMap,
+      loadingStats,
+      customResultDetails,
+      formatNumber,
+      calculateAndVisualize
+    };
   }
 };
 </script>
 
 <style scoped>
-/* 样式部分保持不变,与之前相同 */
-.fertilizer-input-form {
-  padding: 20px;
+/* 整体布局优化 - 紧凑版 */
+.agricultural-input-management {
+  padding: 15px;
+  background: linear-gradient(
+    135deg, 
+    rgba(230, 247, 255, 0.7) 0%, 
+    rgba(240, 248, 255, 0.7) 100%
+  );
+  box-sizing: border-box;
+}
+
+.page-container {
+  width: 100%;
+  height: 100%;
   display: flex;
   flex-direction: column;
-  align-items: center;
-  background-color: rgba(255, 255, 255, 0.8);
 }
 
-.form-card {
-  width: 90%;
-  max-width: 1200px;
-  margin: 0 auto;
-  background: linear-gradient(135deg, #FAFDFF, #FFFAA2);
-  border: 1px solid #e6e6e6;
-  border-radius: 12px;
-  overflow: hidden;
-  box-shadow: 0 8px 24px rgba极端的(0, 0, 0, 0.1);
-}
-
-.card-content {
+.gradient-card, .results-card {
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 6px;
+  padding: 15px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(5px);
+  flex: 1;
   display: flex;
-  min-height: 600px;
-}
-
-.input-section {
-  width: 60%;
-  padding: 30px;
-  border-right: 1px dashed #c0c4cc;
+  flex-direction: column;
 }
 
-.button-section {
-  width: 40%;
+/* 计算内容区域 */
+.calculation-content {
   display: flex;
-  align-items: center;
-  justify-content: center;
-  padding: 极端的30px;
-  position: relative;
-  overflow: hidden;
+  flex-direction: column;
+  height: 100%;
 }
 
-.button-bg {
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background-size: cover;
-  z-index: 0;
+.page-title {
+  text-align: center;
+  margin-bottom: 5px;
+  color: #333;
+  font-size: 20px;
+  font-weight: 600;
 }
 
-.bottom-overlay {
-  position: absolute;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  height: 40%;
-  background: linear-gradient(to top, rgba(255, 255, 255, 0.5), transparent);
-  z-index: 1;
+.scrollable-content {
+  flex: 1;
+  overflow-y: auto;
+  padding: 8px;
+  margin-bottom: 10px;
 }
 
-.form-section {
-  display: flex;
-  flex-direction: column;
-  height: 100%;
+/* 输入区域样式 - 标题在左侧 */
+.input-section {
+  width: 100%;
+  padding: 8px 0;
 }
 
-.input-group {
+.input-row {
   display: flex;
-  justify-content: space-between;
-  margin-bottom: 20px;
-  gap: 20px;
+  flex-wrap: wrap;
+  margin-bottom: 12px;
+  gap: 12px;
 }
 
-.form-item {
+.input-item {
   flex: 1;
-  margin-bottom: 0;
+  min-width: calc(50% - 6px);
   display: flex;
-  flex-direction: column;
-  align-items: flex-start;
+  align-items: center;
+  gap: 10px;
 }
 
-.el-form-item__label {
+.input-title {
   font-size: 16px;
+  color: #666;
   text-align: left;
-  margin-bottom: 8px;
-  padding: 0 !important;
-  font-weight: 600;
-  color: #333;
+  font-weight: 500;
+  width:220px;
+  white-space: nowrap;
 }
 
-.el-input {
-  width: 100%;
+/* 固定输入栏宽度 */
+.fixed-width-input {
+  width: 300px;
+  flex-shrink: 0; /* 防止输入框被压缩 */
 }
 
-:deep(.el-input) .el-input__inner {
-  width: 100% !important;
-  padding: 12px 0;
-  border: none;
-  border-radius: 0;
-  background: transparent;
-  border-bottom: 1px solid #dcdfe6;
-  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05);
-  transition: all 0.3s ease;
-}
-
-:deep(.el-input) .el-input__inner:focus {
-  border-bottom: 2px solid #409EFF;
-  box-shadow: 0 2px 0 rgba(64, 158, 255, 0.2);
-  background: rgba(64, 158, 255, 0.03);
-}
-
-:deep(.el-input) .el-input__inner::placeholder {
-  color: #a0a0a0;
-  font-style: italic;
+/* 按钮容器 */
+.button-container {
+  padding: 10px 0;
+  display: flex;
+  justify-content: center;
+  margin-top: auto;
 }
 
 .calculate-btn {
-  width: 100%;
-  max-width: 300px;
-  height: 200px;
-  border: none;
-  border-radius: 25px !important;
-  font-size: 24px;
+  width: 280px;
+  background-color: #47C3B9 !important;
+  color: white !important;
   font-weight: bold;
-  transition: all 0.4s ease;
-  position: relative;
-  z-index: 2;
-  background: linear-gradient(to right, #8DF9F0, #26B046);
-  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15),
-              0 4px 10px rgba(38, 176, 70, 0.3) inset;
-}
-
-.calculate-btn:hover {
-  transform: scale(1.03);
-  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2),
-              0 4px 12px rgba(38, 176, 70, 0.4) inset;
-  background: linear-gradient(to right, #7de8df, #20a03d);
-}
-
-.calculate-btn:active {
-  transform: scale(0.98);
-  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15),
-              0 2px 8px rgba(38, 176, 70, 极端的0.4) inset;
-}
-
-.btn-text {
-  position: relative;
-  color: white;
-  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
-  font-size: 26px;
-  letter-spacing: 1px;
-  z-index: 1;
+  height: 40px;
+  border-radius: 6px;
+  font-size: 15px;
+}
+
+.results-card {
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 8px;
   padding: 20px;
-  text-align: center;
-  line-height: 1.4;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(5px);
+  height: 100%;
+  box-sizing: border-box;
 }
 
-/* 结果页面样式 */
-.results-page {
-  width: 90%;
-  max-width: 1200px;
-  padding: 30px;
-  background: linear-gradient(135deg, #FAFDFF, #FFFAA2);
-  border-radius: 12px;
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
-  position: relative;
+.results-content {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+.map-section, .stats-area, .chart-section {
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 6px;
+  padding: 15px;
+  margin-bottom: 15px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
 }
 
-.results-title {
-  text-align: center;
+h3 {
+  margin-bottom: 12px;
   color: #333;
-  margin-bottom: 30px;
-  font-size: 28px;
+  font-size: 16px;
+  font-weight: 600;
 }
 
-.result-card {
-  width: 100%;
-  margin: 0 auto;
-  padding: 20px;
-  background: white;
-  border-radius: 12px;
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+.model-info {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 12px;
 }
 
-.result-card h3 {
-  text-align: center;
-  margin-bottom: 20px;
-  color: #333;
+.update-time {
+  color: #666;
+  font-size: 13px;
 }
 
-.result-card p {
-  margin: 10px 0;
-  font-size: 16px;
+.total-flux {
   text-align: center;
+  font-size: 16px;
   font-weight: bold;
-  color: #26B046;
-}
-
-.chart-container {
-  margin-top: 20px;
-  border: 1px solid #eee;
-  border-radius: 8px;
-  padding: 10px;
-  background: #f9f9f9;
+  margin-bottom: 15px;
 }
 
-.map-container {
-  margin-top: 30px;
-  padding: 20px;
+/* 图片容器限定大小 */
+.image-container {
+  width: 100%;
+  height: 500px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
   background-color: #f9f9f9;
-  border-radius: 8px;
-  border: 1px solid #eee;
+  border-radius: 6px;
+  overflow: hidden;
 }
 
-.map-container h3 {
-  text-align: center;
-  margin-bottom: 15px;
-  color: #333;
+.result-image {
+  max-width: 100%;
+  max-height: 100%;
+  object-fit: contain;
 }
 
-.map-wrapper {
-  position: relative;
-  min-height: 400px;
+.loading-container {
   display: flex;
+  flex-direction: column;
   align-items: center;
   justify-content: center;
-  background-color: white;
-  border-radius: 6px;
-  overflow: hidden;
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  height: 250px;
+  color: #47C3B9;
 }
 
-.map-image-container {
-  width: 100%;
+.no-data {
   display: flex;
+  flex-direction: column;
+  align-items: center;
   justify-content: center;
+  height: 250px;
+  color: #999;
+  font-size: 14px;
 }
 
-.map-image {
-  max-width: 100%;
-  max-height: 500px;
-  object-fit: contain;
+.no-data .el-icon {
+  font-size: 40px;
+  margin-bottom: 8px;
 }
 
-.map-placeholder {
-  text-align: center;
-  padding: 20px;
-  width: 80%;
+.loading-icon {
+  font-size: 32px;
+  margin-bottom: 8px;
+  animation: rotate 2s linear infinite;
 }
 
-.map-placeholder p {
-  margin-bottom: 15px;
-  font-size: 16px;
-  color: #666;
+@keyframes rotate {
+  from { transform: rotate(0deg); }
+  to { transform: rotate(360deg); }
 }
 
-.back-button {
-  position: absolute;
-  top: 20px;
-  left: 20px;
-  width: 120px;
-  font-size: 16px;
-  padding: 10px;
-  background: linear-gradient(to right, #8DF9F0, #26B046);
-  color: white;
-  border: none;
-  border-radius: 20px;
-  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+/* 响应式设计 */
+@media (max-width: 992px) {
+  .input-row {
+    gap: 10px;
+  }
+  
+  .input-item {
+    min-width: 100%;
+  }
+  
+  .page-title {
+    font-size: 18px;
+    margin-bottom: 15px;
+  }
+  
+  .fixed-width-input {
+    width: 100%;
+  }
 }
 
-.back-button:hover {
-  background: linear-gradient(to right, #7de8df, #20a03d);
+@media (max-width: 768px) {
+  .toolbar {
+    flex-direction: column;
+    align-items: stretch;
+    gap: 8px;
+  }
+  
+  .input-title {
+    min-width: 150px;
+    font-size: 13px;
+  }
+  
+  .image-container {
+    height: 280px;
+  }
+  
+  :deep(.el-input__inner) {
+    height: 34px;
+    line-height: 34px;
+    font-size: 13px;
+  }
+}
+
+@media (max-width: 480px) {
+  .agricultural-input-management {
+    padding: 10px;
+  }
+  
+  .gradient-card, .results-card {
+    padding: 12px;
+  }
+  
+  .page-title {
+    font-size: 16px;
+    margin-bottom: 12px;
+  }
+  
+  .input-item {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 6px;
+  }
+  
+  .input-title {
+    min-width: 100%;
+    font-size: 12px;
+  }
+  
+  .el-input {
+    width: 100%;
+  }
+  
+  .calculate-btn {
+    width: 100%;
+    max-width: 280px;
+    height: 38px;
+    font-size: 14px;
+  }
+  
+  .image-container {
+    height: 220px;
+  }
+  
+  .total-flux {
+    font-size: 15px;
+  }
+}
+.custom-button {
+  background-color: #47C3B9 !important;
+  color: #DCFFFA !important;
+  border: none;
+  border-radius: 155px;
+  padding: 10px 20px;
+  font-weight: bold;
+  display: flex;
+  align-items: center;
+}
+/* 工具栏样式 */
+.toolbar {
+  display: flex;
+  justify-content: flex-start;
+  gap: 15px;
+  margin-bottom: 20px;
+  padding: 15px;
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(5px);
 }
 </style>

+ 7 - 36
src/views/User/HmOutFlux/atmosDeposition/AtmosDepositionSamplingDesc.vue

@@ -4,7 +4,7 @@
     <div class="content-section">
       <div class="section-header">
         <div class="step-number">1</div>
-        <h2>建立大气污染源清单</h2>
+        <h2>​大气污染源清单构建与数据分析</h2>
       </div>
       
       <div class="process-steps">
@@ -12,7 +12,7 @@
           <div class="step-icon">📊</div>
           <div class="step-content">
             <h3>污染源统计</h3>
-            <p>根据韶关市环保部门或政府官方网站公开资料及全国排污许可证管理信息平台,对韶关具有排污许可证的企业进行数据统计与调查分析</p>
+            <p>根据环保部门或政府官方网站公开资料及全国排污许可证管理信息平台,对具有排污许可证的企业进行数据统计与调查分析</p>
           </div>
         </div>
         
@@ -59,7 +59,7 @@
     <div class="content-section">
       <div class="section-header">
         <div class="step-number">2</div>
-        <h2>现场调研</h2>
+        <h2>重金属排放源现场调查与评估</h2>
       </div>
       
       <div class="text-content">
@@ -73,39 +73,11 @@
         <p class="image-caption">图2 干湿沉降收集装置</p>
       </div>
     </div>
-    
-    <!-- 第三部分:样品分析 -->
-    <div class="content-section">
-      <div class="section-header">
-        <div class="step-number">3</div>
-        <h2>样品分析</h2>
-      </div>
-      
-      <div class="analysis-methods">
-        <div class="method-card">
-          <h3>重金属检测技术</h3>
-          <ul>
-            <li>原子吸收光谱法(AAS)</li>
-            <li>电感耦合等离子体质谱法(ICP-MS)</li>
-            <li>X射线荧光光谱法(XRF)</li>
-          </ul>
-        </div>
-        
-        <div class="method-card">
-          <h3>数据处理与分析</h3>
-          <ul>
-            <li>质量控制方法</li>
-            <li>统计分析技术</li>
-            <li>数据可视化工具</li>
-          </ul>
-        </div>
-      </div>
-    </div>
       <!-- 新增第四部分:采样视频模块 -->
     <div class="content-section">
       <div class="section-header">
-        <div class="step-number">4</div>
-        <h2>采样过程视频演示</h2>
+        <div class="step-number">3</div>
+        <h2>大气干湿沉降过程视频演示</h2>
       </div>
       
       <div class="video-section">
@@ -114,7 +86,6 @@
             <source src='@/assets/videos/干湿沉降.mp4' type="video/mp4">
             您的浏览器不支持HTML5视频播放。
           </video>
-          <p class="video-caption">视频 大气污染物采样过程演示</p>
         </div>
       </div>
     </div>
@@ -125,8 +96,8 @@
 export default {
   data() {
     return {
-      image1: '/大气干湿沉降示意图.png',
-      image2: '/干湿沉降收集装置.png',
+      image1: '/大气干湿沉降示意图(1).png',
+      image2: '/干湿沉降收集装置(1).png',
     };
   }
 };

+ 66 - 57
src/views/User/HmOutFlux/atmosDeposition/airInputFlux.vue

@@ -1,102 +1,111 @@
 <template>
-  <div class="atmosphere-flux-container">
-    <div class="map-title">大气通量分布图</div>
-    <div class="map-image-container">
-      <img 
-        src="/大气通量.jpg" 
-        alt="大气通量分布图" 
-        class="map-image"
-      >
+  <div class="agricultural-input-management">
+    <div class="page-container">
+      <el-card class="results-card">
+        <div class="results-content">
+          <!-- 地图区域 -->
+          <div class="map-section">
+            <h3>大气Cd通量分布图</h3>
+            <div class="image-container">
+               <img 
+                  src="/大气通量(1).jpg" 
+                  alt="大气通量分布图" 
+                  class="result-image"
+                >
+            </div>
+          </div>
+        </div>
+      </el-card>
     </div>
   </div>
 </template>
 
 <script>
+import { ElButton, ElCard } from 'element-plus';
+
 export default {
   name: 'AtmosphereFluxMap',
+  components: {
+    ElButton,
+    ElCard
+  }
 };
 </script>
 
 <style scoped>
-.atmosphere-flux-container {
+
+.page-container {
   width: 100%;
-  max-width: 800px;
-  margin: 0 auto;
-  background: white;
-  border-radius: 12px;
-  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15);
-  overflow: hidden;
-  font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
+  height: 100%;
 }
-
-.map-title {
-  background: linear-gradient(to right, #1a5fad, #2c8fd1);
-  color: white;
-  text-align: center;
-  padding: 18px 0;
-  font-size: 1.8rem;
-  font-weight: 600;
-  letter-spacing: 1px;
-  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
+/* 结果卡片样式 */
+.results-card {
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 8px;
+  padding: 20px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(5px);
+  height: 100%;
+  box-sizing: border-box;
 }
 
-.map-content {
+.results-content {
+  height: 100%;
   display: flex;
   flex-direction: column;
+}
+
+.map-section {
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 8px;
   padding: 20px;
-  background: #f8fafc;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+h3 {
+  margin-bottom: 15px;
+  color: #333;
+  font-size: 18px;
+  font-weight: 600;
 }
 
-.map-image-container {
-  flex: 1;
+/* 图片容器限定大小 */
+.image-container {
+  width: 100%;
+  height: 500px;
   display: flex;
   justify-content: center;
   align-items: center;
-  padding: 10px;
-  background: white;
-  border-radius: 12px;
-  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
+  background-color: #f9f9f9;
+  border-radius: 8px;
+  overflow: hidden;
 }
 
-.map-image {
+.result-image {
   max-width: 100%;
   max-height: 100%;
   object-fit: contain;
-  border-radius: 8px;
-  box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
   transition: transform 0.3s ease;
-  height: 600px;
-  width: 100%;
 }
 
-.map-image:hover {
+.result-image:hover {
   transform: scale(1.02);
 }
 
-
+/* 响应式设计 */
 @media (max-width: 768px) {
-  .map-title {
-    font-size: 1.5rem;
-    padding: 15px 0;
-  }
-  
-  .map-image-container {
-    padding: 15px;
-    min-height: 350px;
-  }
-  
-  .map-footer {
-    font-size: 0.85rem;
+  .image-container {
+    height: 400px;
   }
 }
 
 @media (max-width: 480px) {
-  .map-title {
-    font-size: 1.3rem;
+  .agricultural-input-management {
+    padding: 10px;
   }
   
-  .map-image-container {
-    min-height: 280px;
+  .image-container {
+    height: 300px;
   }
 }
 </style>

File diff suppressed because it is too large
+ 502 - 348
src/views/User/HmOutFlux/irrigationWater/irriWaterInputFlux.vue


+ 6 - 6
src/views/User/HmOutFlux/irrigationWater/samplingMethodDevice1.vue

@@ -102,12 +102,12 @@
 export default {
   data() {
     return {
-      image1: '/图1.png',
-      image2: '/图片2.png',
-      image3: '/图片3.png',
-      fieldImage1: '/图片4.jpg',
-      fieldImage2: '/图片5.jpg',
-      fieldImage3: '/图片6.jpg'
+      image1: '/图1(1).png',
+      image2: '/图片2(1).png',
+      image3: '/图片3(1).png',
+      fieldImage1: '/图片4(1).jpg',
+      fieldImage2: '/图片5(1).jpg',
+      fieldImage3: '/图片6(1).jpg'
     };
   }
 };

+ 367 - 0
src/views/User/HmOutFlux/totalInputFluxDesc.vue

@@ -0,0 +1,367 @@
+<template>
+  <div class="total-flux-intro">
+    <!-- 第一部分:总通量概念介绍 -->
+    <div class="content-section">
+      <div class="section-header">
+        <div class="section-number">1</div>
+        <h2>重金属输入总通量概念</h2>
+      </div>
+      
+      <div class="concept-card">
+        <div class="concept-icon">📊</div>
+        <div class="concept-content">
+          <p>
+            <span class="highlight">重金属输入总通量</span>是指通过灌溉水、农业投入品和大气沉降三种主要途径进入农田生态系统的重金属总量,通常以克/公顷/年(g/ha/a)为单位表示。该指标综合反映了农田系统从外部环境输入的重金属污染负荷,是评估农田重金属污染风险的重要依据。
+          </p>
+        </div>
+      </div>
+      
+      <div class="formula-container">
+        <div class="formula-card">
+          <h3>输入总通量计算公式</h3>
+          <div class="formula-content">
+            <p class="formula">F<sub>总</sub> = F<sub>灌溉水</sub> + F<sub>农业投入品</sub> + F<sub>大气沉降</sub></p>
+            <div class="formula-explain">
+              <p>式中:</p>
+              <p>F<sub>总</sub> —— 重金属输入总通量 (g/ha/a)</p>
+              <p>F<sub>灌溉水</sub> —— 灌溉水途径输入通量 (g/ha/a)</p>
+              <p>F<sub>农业投入品</sub> —— 肥料农药等输入通量 (g/ha/a)</p>
+              <p>F<sub>大气沉降</sub> —— 干湿沉降输入通量 (g/ha/a)</p>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+  }
+};
+</script>
+
+<style scoped>
+.total-flux-intro {
+  padding: 30px;
+  background: linear-gradient(135deg, rgba(250, 255, 245, 0.9) 0%, rgba(240, 248, 255, 0.9) 100%);
+  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+}
+
+.content-section {
+  margin-bottom: 50px;
+  padding: 30px;
+  border-radius: 15px;
+  background: rgba(255, 255, 255, 0.92);
+  box-shadow: 0 8px 25px rgba(0, 60, 120, 0.08);
+  transition: all 0.4s ease;
+}
+
+.content-section:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 12px 35px rgba(0, 60, 120, 0.15);
+}
+
+.section-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 25px;
+  padding-bottom: 15px;
+  border-bottom: 2px solid rgba(58, 160, 207, 0.25);
+}
+
+.section-number {
+  width: 50px;
+  height: 50px;
+  background: linear-gradient(135deg, #4a9ef7, #3a9fd3);
+  color: white;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 1.8rem;
+  font-weight: bold;
+  margin-right: 20px;
+  box-shadow: 0 5px 12px rgba(74, 158, 247, 0.25);
+}
+
+h2 {
+  color: #1a365d;
+  font-size: 1.9rem;
+  margin: 0;
+  font-weight: 650;
+}
+
+.concept-card {
+  display: flex;
+  align-items: center;
+  padding: 25px;
+  background: rgba(235, 245, 255, 0.6);
+  border-radius: 15px;
+  margin-bottom: 30px;
+  border-left: 4px solid #3a9fd3;
+}
+
+.concept-icon {
+  font-size: 2.5rem;
+  margin-right: 25px;
+  color: #3a9fd3;
+}
+
+.concept-content p {
+  font-size: 1.15rem;
+  line-height: 1.8;
+  color: #2d3748;
+  margin: 0 0 15px 0;
+}
+
+.highlight {
+  font-weight: 600;
+  color: #1a6fb3;
+}
+
+.formula-container {
+  display: flex;
+  justify-content: center;
+}
+
+.formula-card {
+  width: 100%;
+  max-width: 700px;
+  padding: 25px;
+  background: rgba(245, 252, 255, 0.8);
+  border-radius: 15px;
+  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
+  border-top: 3px solid #3a9fd3;
+}
+
+.formula-card h3 {
+  color: #1a365d;
+  font-size: 1.5rem;
+  margin-top: 0;
+  margin-bottom: 20px;
+  text-align: center;
+}
+
+.formula-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.formula {
+  font-size: 1.8rem;
+  font-weight: bold;
+  color: #2c3e50;
+  margin-bottom: 20px;
+  text-align: center;
+  padding: 15px;
+  background: white;
+  border-radius: 10px;
+  width: 100%;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.formula-explain {
+  width: 100%;
+}
+
+.formula-explain p {
+  font-size: 1.1rem;
+  line-height: 1.8;
+  color: #2d3748;
+  margin: 5px 0;
+}
+
+.formula-explain p:first-child {
+  font-weight: 600;
+  margin-bottom: 10px;
+}
+
+.contribution-container {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 25px;
+  margin-bottom: 30px;
+}
+
+.contribution-card {
+  display: flex;
+  padding: 20px;
+  background: rgba(245, 252, 255, 0.7);
+  border-radius: 15px;
+  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
+  transition: all 0.3s ease;
+}
+
+.contribution-card:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
+}
+
+.contribution-icon {
+  font-size: 2rem;
+  margin-right: 20px;
+  color: #3a9fd3;
+}
+
+.contribution-content h3 {
+  color: #1a365d;
+  font-size: 1.3rem;
+  margin-top: 0;
+  margin-bottom: 12px;
+}
+
+.contribution-content p {
+  font-size: 1.05rem;
+  line-height: 1.6;
+  color: #2d3748;
+  margin: 0 0 10px 0;
+}
+
+.image-container {
+  max-width: 700px;
+  margin: 0 auto;
+  border-radius: 15px;
+  overflow: hidden;
+  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+}
+
+.flux-image {
+  width: 100%;
+  display: block;
+}
+
+.image-caption {
+  text-align: center;
+  font-size: 16px;
+  color: #2d3748;
+  padding: 15px;
+  background: rgba(248, 250, 252, 0.8);
+  margin: 0;
+  border-top: 1px dashed #cbd5e0;
+}
+
+.application-container {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 25px;
+}
+
+.application-card {
+  display: flex;
+  padding: 20px;
+  background: rgba(245, 252, 255, 0.7);
+  border-radius: 15px;
+  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
+  transition: all 0.3s ease;
+  border-left: 4px solid #5cb85c;
+}
+
+.application-card:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
+}
+
+.application-icon {
+  font-size: 2rem;
+  margin-right: 20px;
+  color: #5cb85c;
+}
+
+.application-content h3 {
+  color: #1a365d;
+  font-size: 1.3rem;
+  margin-top: 0;
+  margin-bottom: 12px;
+}
+
+.application-content p {
+  font-size: 1.05rem;
+  line-height: 1.6;
+  color: #2d3748;
+  margin: 0;
+}
+
+/* 响应式设计 */
+@media (max-width: 1200px) {
+  .contribution-container {
+    grid-template-columns: 1fr;
+  }
+}
+
+@media (max-width: 900px) {
+  .content-section {
+    padding: 25px;
+  }
+  
+  .section-number {
+    width: 45px;
+    height: 45px;
+    font-size: 1.6rem;
+  }
+  
+  h2 {
+    font-size: 1.7rem;
+  }
+  
+  .application-container {
+    grid-template-columns: 1fr;
+  }
+}
+
+@media (max-width: 768px) {
+  .total-flux-intro {
+    padding: 20px;
+  }
+  
+  .concept-card {
+    flex-direction: column;
+    text-align: center;
+  }
+  
+  .concept-icon {
+    margin-right: 0;
+    margin-bottom: 15px;
+  }
+  
+  .contribution-card {
+    flex-direction: column;
+    text-align: center;
+  }
+  
+  .contribution-icon {
+    margin-right: 0;
+    margin-bottom: 15px;
+  }
+}
+
+@media (max-width: 480px) {
+  .total-flux-intro {
+    padding: 15px;
+  }
+  
+  .content-section {
+    padding: 20px;
+  }
+  
+  .section-header {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+  
+  .section-number {
+    margin-right: 0;
+    margin-bottom: 15px;
+  }
+  
+  h2 {
+    font-size: 1.6rem;
+  }
+  
+  .formula {
+    font-size: 1.5rem;
+  }
+}
+</style>

+ 25 - 16
src/views/User/acidModel/Calculation.vue

@@ -126,7 +126,8 @@
 <script setup lang="ts">
 import { reactive, ref, nextTick } from "vue";
 import { ElMessage } from "element-plus";
-import request from "../../../utils/request";
+import { api5000 } from "../../../utils/request"; // 使用api5000
+
 
 // 定义表单数据接口
 interface Form {
@@ -230,32 +231,40 @@ const onSubmit = async () => {
   };
 
   try {
-    const response = await request.post("http://127.0.0.1:5000/predict", data, {
+    const response = await api5000.post('/predict', data, {
       headers: {
         "Content-Type": "application/json",
       },
     });
-    console.log("预测结果:", response.data);
-    if (
-      response.data &&
-      response.data.result &&
-      response.data.result.length > 0
-    ) {
+    
+    if (response.data?.result?.length > 0) {
       result.value = parseFloat(response.data.result[0].toFixed(2));
+      dialogVisible.value = true;
     } else {
-      console.error("未获取到有效的预测结果");
-      ElMessage.error("未获取到有效的预测结果");
+      throw new Error("未获取到有效的预测结果");
     }
     dialogVisible.value = true;
   } catch (error: any) {
-    console.error("请求失败:", error);
-    if (error.response) {
-      ElMessage.error(`请求失败,状态码: ${error.response.status}`);
+    console.error("请求失败详情:", error);
+    
+    let errorMessage = "请求失败";
+    if (error.code === "ECONNABORTED") {
+      errorMessage = "请求超时,请稍后再试";
+    } else if (error.response) {
+      // 服务器有响应但状态码不在2xx范围
+      errorMessage = `请求失败,状态码: ${error.response.status}`;
+      if (error.response.data?.detail) {
+        errorMessage += ` - ${error.response.data.detail}`;
+      }
     } else if (error.request) {
-      ElMessage.error("请求发送成功,但没有收到响应");
-    } else {
-      ElMessage.error("请求过程中发生错误: " + error.message);
+      // 请求已发出但没有收到响应
+      errorMessage = "无法连接到服务器,请检查网络连接";
+      if (error.message.includes("Network Error")) {
+        errorMessage = "网络错误,请检查后端服务是否运行";
+      }
     }
+    
+    ElMessage.error(errorMessage);
   }
 };
 

+ 8 - 6
src/views/User/acidModel/ModelIterationVisualization.vue

@@ -2,7 +2,7 @@
 import { ref, onMounted, nextTick, onUnmounted, defineProps } from 'vue';
 import VueEcharts from 'vue-echarts';
 import 'echarts';
-import request from '../../../utils/request';
+import { api5000 } from '../../../utils/request';
 
 interface HistoryDataItem {
   dataset_id: number;
@@ -91,7 +91,9 @@ const calculateDataRange = (data: [number, number][]) => {
 // 获取折线图数据
 const fetchLineData = async () => {
   try {
-    const response = await request.get<HistoryDataResponse>(`http://127.0.0.1:5000/get-model-history/${props.lineChartPathParam}`);
+
+    // 修改后
+    const response = await api5000.get<HistoryDataResponse>(`/get-model-history/${props.lineChartPathParam}`);    
     const data = response.data;
 
     const timestamps = data.timestamps;
@@ -145,7 +147,7 @@ const fetchLineData = async () => {
 // 获取散点图数据
 const fetchScatterData = async (modelId: number, optionRef: any) => {
   try {
-    const response = await request.get<ScatterDataResponse>(`http://127.0.0.1:5000/model-scatter-data/${modelId}`);
+    const response = await api5000.get<ScatterDataResponse>(`/model-scatter-data/${modelId}`);
     const data = response.data;
 
     const scatterData = data.scatter_data;
@@ -248,12 +250,12 @@ onUnmounted(() => {
 
 <template>
   <div class="container">
-    <template v-if="showLineChart">
-      <!-- 折线图表头 -->
+    <!-- <template v-if="showLineChart">
+       折线图表头
       <div class="chart-container">
         <VueEcharts :option="ecLineOption" ref="ecLineOptionRef" />
       </div>
-    </template>
+    </template> -->
     <template v-if="showInitScatterChart">
       <!-- 初代散点图表头 -->
       <h2 class="chart-header">初代散点图</h2>

+ 126 - 333
src/views/User/cadmiumPrediction/CropCadmiumPrediction.vue

@@ -19,6 +19,15 @@
         <el-icon class="upload-icon"><Document /></el-icon>  
         上传并计算
         </el-button>
+        <!-- 添加从数据库计算按钮 -->
+        <el-button 
+          class="custom-button" 
+          :loading="isCalculatingFromDB" 
+          @click="calculateFromDatabase"
+        >
+        <el-icon class="upload-icon"><Box /></el-icon>  
+        从数据库计算
+        </el-button>
       </div>
       <!-- 操作按钮 -->
       <div class="action-buttons">
@@ -28,88 +37,36 @@
         <el-button class="custom-button" :disabled="!histogramBlob" @click="exportHistogram">
           <el-icon class="upload-icon"><Download /></el-icon>
           导出直方图</el-button>
-        <el-button class="custom-button" :disabled="!statisticsData.length" @click="exportData">
-          <el-icon class="upload-icon"><Download /></el-icon>
-          导出数据</el-button>
       </div>
     </div>
 
     <!-- 主体内容区 -->
     <div class="content-area">
-      <!-- 地图区域 - 修改为横向布局 -->
-      <div class="horizontal-container">
-        <!-- 地图展示 -->
-        <div class="map-section">
-          <h3>作物态Cd预测地图</h3>
-          <div v-if="loadingMap" class="loading-container">
-            <el-icon class="loading-icon"><Loading /></el-icon>
-            <span>地图加载中...</span>
-          </div>
-          <img v-if="mapImageUrl && !loadingMap" :src="mapImageUrl" alt="作物态Cd预测地图" class="map-image">
-          <div v-if="!mapImageUrl && !loadingMap" class="no-data">
-            <el-icon><Picture /></el-icon>
-            <p>暂无地图数据</p>
-          </div>
+      <!-- 地图区域 - 单独一行 -->
+      <div class="map-section">
+        <h3>作物态Cd预测地图</h3>
+        <div v-if="loadingMap" class="loading-container">
+          <el-icon class="loading-icon"><Loading /></el-icon>
+          <span>地图加载中...</span>
         </div>
-        
-        <!-- 直方图展示 -->
-        <div class="histogram-section">
-          <h3>作物态Cd预测直方图</h3>
-          <div v-if="loadingHistogram" class="loading-container">
-            <el-icon class="loading-icon"><Loading /></el-icon>
-            <span>直方图加载中...</span>
-          </div>
-          <img v-if="histogramImageUrl && !loadingHistogram" :src="histogramImageUrl" alt="作物态Cd预测直方图" class="histogram-image">
-          <div v-if="!histogramImageUrl && !loadingHistogram" class="no-data">
-            <el-icon><Histogram /></el-icon>
-            <p>暂无直方图数据</p>
-          </div>
+        <img v-if="mapImageUrl && !loadingMap" :src="mapImageUrl" alt="作物态Cd预测地图" class="map-image">
+        <div v-if="!mapImageUrl && !loadingMap" class="no-data">
+          <el-icon><Picture /></el-icon>
+          <p>暂无地图数据</p>
         </div>
       </div>
-
-      <!-- 统计图表区域 -->
-      <div class="stats-area">
-        <h3>{{countyName}} - 作物Cd预测统计信息</h3>
-        <div class="model-info">
-          <el-tag type="info">{{currentStats?.['模型类型'] || '作物Cd模型'}}</el-tag>
-          <span class="update-time">
-            最后更新: {{currentStats?.['数据更新时间'] ? new Date(currentStats['数据更新时间']).toLocaleString() : '未知'}}
-          </span>
-        </div>
-        
-        <div v-if="loadingStats" class="loading-container">
+      
+      <!-- 直方图区域 - 单独一行 -->
+      <div class="histogram-section">
+        <h3>作物态Cd预测直方图</h3>
+        <div v-if="loadingHistogram" class="loading-container">
           <el-icon class="loading-icon"><Loading /></el-icon>
-          <span>统计数据加载中...</span>
-        </div>
-        
-        <div v-if="!loadingStats && statisticsData.length" class="stats-container">
-          <!-- 统计表格 -->
-         <el-table 
-            :data="statisticsData" 
-            style="width: 100%; margin-bottom: 20px;"
-            border
-            stripe
-          >
-            <el-table-column prop="name" label="统计项" min-width="180" />
-            <el-table-column prop="value" label="值" min-width="150" />
-            <el-table-column prop="unit" label="单位" min-width="100" />
-            <el-table-column prop="description" label="描述" min-width="200" />
-          </el-table>
-          
-          <!-- 统计图表 -->
-          <div class="charts-container">
-            <div class="chart-item">
-              <div ref="distributionChart" style="width: 100%; height: 400px;"></div>
-            </div>
-            <div class="chart-item">
-              <div ref="exceedanceChart" style="width: 100%; height: 400px;"></div>
-            </div>
-          </div>
+          <span>直方图加载中...</span>
         </div>
-        
-        <div v-if="!loadingStats && !statisticsData.length" class="no-data">
-          <el-icon><DataAnalysis /></el-icon>
-          <p>暂无统计数据</p>
+        <img v-if="histogramImageUrl && !loadingHistogram" :src="histogramImageUrl" alt="作物态Cd预测直方图" class="histogram-image">
+        <div v-if="!histogramImageUrl && !loadingHistogram" class="no-data">
+          <el-icon><Histogram /></el-icon>
+          <p>暂无直方图数据</p>
         </div>
       </div>
     </div>
@@ -119,46 +76,39 @@
 <script>
 import * as XLSX from 'xlsx';
 import { saveAs } from 'file-saver';
-import axios from 'axios';
-import * as echarts from 'echarts';
+import { api8000 } from '@/utils/request';
 import { 
-  Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
+  Loading, Upload, Picture, Histogram, Download, Document, Box 
 } from '@element-plus/icons-vue';
 
 export default {
   name: 'CropCadmiumPrediction',
   components: { 
-    Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
+    Loading, Upload, Picture, Histogram, Download, Document, Box 
   },
   data() {
     return {
       isCalculating: false,
+      isCalculatingFromDB: false,
       loadingMap: false,
       loadingHistogram: false,
-      loadingStats: false,
-      statisticsData: [],
       mapImageUrl: null,
       histogramImageUrl: null,
       mapBlob: null,
       histogramBlob: null,
       selectedFile: null,
-      countyName: '乐昌市', // 默认县市名称
-      distributionChart: null,
-      exceedanceChart: null
+      countyName: '乐昌市' // 默认县市名称
     };
   },
 
   mounted() {
     // 组件挂载时获取最新数据
     this.fetchLatestResults();
-    this.fetchStatistics();
   },
 
   beforeDestroy() {
     if (this.mapImageUrl) URL.revokeObjectURL(this.mapImageUrl);
     if (this.histogramImageUrl) URL.revokeObjectURL(this.histogramImageUrl);
-    if (this.distributionChart) this.distributionChart.dispose();
-    if (this.exceedanceChart) this.exceedanceChart.dispose();
   },
   methods: {
     // 触发文件选择
@@ -200,8 +150,8 @@ export default {
     // 获取最新地图
     async fetchLatestMap() {
       try {
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-prediction/crop-cd/latest-map/${this.countyName}`,
+        const response = await api8000.get(
+          `/api/cd-prediction/crop-cd/latest-map/${this.countyName}`,
           { responseType: 'blob' }
         );
         
@@ -216,8 +166,8 @@ export default {
     // 获取最新直方图
     async fetchLatestHistogram() {
       try {
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-prediction/crop-cd/latest-histogram/${this.countyName}`,
+        const response = await api8000.get(
+          `/api/cd-prediction/crop-cd/latest-histogram/${this.countyName}`,
           { responseType: 'blob' }
         );
         
@@ -229,197 +179,6 @@ export default {
       }
     },
     
-    // 格式化统计数据
-    formatStatisticsData(stats) {
-      return [
-        { name: '数据点总数', value: stats['基础统计']['数据点总数'], unit: '个', description: '总样本数量' },
-        { name: '平均值', value: stats['基础统计']['均值'].toFixed(4), unit: '(mg/kg)', description: '所有样本的平均Cd含量' },
-        { name: '中位数', value: stats['基础统计']['中位数'].toFixed(4), unit: '(mg/kg)', description: '样本的中位Cd含量' },
-        { name: '标准差', value: stats['基础统计']['标准差'].toFixed(4), unit: '(mg/kg)', description: 'Cd含量的标准差' },
-        { name: '最小值', value: stats['基础统计']['最小值'].toFixed(4), unit: '(mg/kg)', description: '样本中的最小Cd含量' },
-        { name: '最大值', value: stats['基础统计']['最大值'].toFixed(4), unit: '(mg/kg)', description: '样本中的最大Cd含量' },
-        { name: '偏度', value: stats['基础统计']['偏度'].toFixed(4), unit: '', description: '数据分布偏斜程度' },
-        { name: '峰度', value: stats['基础统计']['峰度'].toFixed(4), unit: '', description: '数据分布峰态' },
-        { 
-          name: '经度范围', 
-          value: `${stats['空间统计']['经度范围']['最小值'].toFixed(6)} - ${stats['空间统计']['经度范围']['最大值'].toFixed(6)}`, 
-          unit: '度', 
-          description: `跨度: ${stats['空间统计']['经度范围']['跨度'].toFixed(6)}度` 
-        },
-        { 
-          name: '纬度范围', 
-          value: `${stats['空间统计']['纬度范围']['最小值'].toFixed(6)} - ${stats['空间统计']['纬度范围']['最大值'].toFixed(6)}`, 
-          unit: '度', 
-          description: `跨度: ${stats['空间统计']['纬度范围']['跨度'].toFixed(6)}度` 
-        }
-      ];
-    },
-
-    // 初始化图表 - 根据实际数据更新
-    initCharts() {
-      if (!this.statisticsData.length || !this.currentStats) return;
-      
-      // 销毁旧图表
-      if (this.distributionChart) this.distributionChart.dispose();
-      if (this.exceedanceChart) this.exceedanceChart.dispose();
-      
-      const histData = this.currentStats['分布直方图'];
-      
-      // 1. 分布直方图
-      this.distributionChart = echarts.init(this.$refs.distributionChart);
-      this.distributionChart.setOption({
-        title: {
-          text: 'Cd含量分布直方图',
-          left: 'center'
-        },
-        tooltip: {
-          trigger: 'item',
-          formatter: params => {
-            const index = params.dataIndex;
-            const lowerBound = histData['区间边界'][index].toFixed(4);
-            const upperBound = histData['区间边界'][index + 1].toFixed(4);
-            return `区间: ${lowerBound} ~ ${upperBound}<br/>频次: ${params.value}`;
-          }
-        },
-        xAxis: {
-          type: 'category',
-          data: histData['区间中心'].map(v => v.toFixed(4)),
-          name: 'Cd含量',
-          axisLabel: {
-            rotate: 45
-          }
-        },
-        yAxis: {
-          type: 'value',
-          name: '频次'
-        },
-        series: [{
-          name: '样本分布',
-          type: 'bar',
-          data: histData['频次'],
-          itemStyle: {
-            color: '#47C3B9'
-          },
-          barWidth: '80%'
-        }],
-        grid: {
-          bottom: '20%'
-        }
-      });
-      
-      // 2. 箱线图/统计图表
-      this.exceedanceChart = echarts.init(this.$refs.exceedanceChart);
-      
-      // 准备箱线图数据
-      const boxData = [
-        [
-          this.currentStats['基础统计']['最小值'],
-          this.currentStats['基础统计']['25%分位数'],
-          this.currentStats['基础统计']['中位数'],
-          this.currentStats['基础统计']['75%分位数'],
-          this.currentStats['基础统计']['最大值'],
-          // 还可以添加离群点数据(如果有)
-        ]
-      ];
-      
-      this.exceedanceChart.setOption({
-        title: {
-          text: 'Cd含量统计指标',
-          left: 'center'
-        },
-        tooltip: {
-          trigger: 'item',
-          axisPointer: {
-            type: 'shadow'
-          },
-          formatter: params => {
-            const data = boxData[0];
-            return [
-              '最大值: ' + data[4].toFixed(4),
-              '75%分位数: ' + data[3].toFixed(4),
-              '中位数: ' + data[2].toFixed(4),
-              '25%分位数: ' + data[1].toFixed(4),
-              '最小值: ' + data[0].toFixed(4)
-            ].join('<br/>');
-          }
-        },
-        xAxis: {
-          type: 'category',
-          data: ['Cd含量统计'],
-          axisLabel: {
-            rotate: 45
-          }
-        },
-        yAxis: {
-          type: 'value',
-          name: '(Cd含量)'
-        },
-        series: [{
-          name: '统计值',
-          type: 'boxplot',
-          data: boxData,
-          itemStyle: {
-            color: '#47C3B9',
-            borderColor: '#2F4554'
-          },
-          emphasis: {
-            itemStyle: {
-              color: '#FF6B6B',
-              borderColor: '#C23531'
-            }
-          },
-          tooltip: {
-            formatter: param => {
-              const data = boxData[0];
-              return [
-                '最大值: ' + data[4].toFixed(4),
-                '75%分位数: ' + data[3].toFixed(4),
-                '中位数: ' + data[2].toFixed(4),
-                '25%分位数: ' + data[1].toFixed(4),
-                '最小值: ' + data[0].toFixed(4)
-              ].join('<br/>');
-            }
-          }
-        }],
-        grid: {
-          bottom: '15%'
-        }
-      });
-      
-      // 响应式调整
-      window.addEventListener('resize', this.handleResize);
-    },
-
-    // 修改fetchStatistics方法
-    async fetchStatistics() {
-      try {
-        this.loadingStats = true;
-        
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-prediction/crop-cd/statistics/${this.countyName}`
-        );
-        
-        if (response.data.success && response.data.data) {
-          this.currentStats = response.data.data; // 保存原始统计数据
-          this.statisticsData = this.formatStatisticsData(response.data.data);
-          this.$nextTick(() => {
-            this.initCharts();
-          });
-        }
-      } catch (error) {
-        console.error('获取统计信息失败:', error);
-        this.$message.warning('获取统计信息失败');
-      } finally {
-        this.loadingStats = false;
-      }
-    },
-    
-    // 处理窗口大小变化
-    handleResize() {
-      if (this.distributionChart) this.distributionChart.resize();
-      if (this.exceedanceChart) this.exceedanceChart.resize();
-    },
-    
     // 上传并计算
     async calculate() {
       if (!this.selectedFile) {
@@ -431,16 +190,16 @@ export default {
         this.isCalculating = true;
         this.loadingMap = true;
         this.loadingHistogram = true;
-        this.loadingStats = true;
         
         // 创建FormData
         const formData = new FormData();
-        formData.append('county_name', this.countyName);
+        formData.append('area', this.countyName);
         formData.append('data_file', this.selectedFile);
+        formData.append('use_database', 'false'); // 使用上传的文件
         
         // 调用作物Cd地图接口
-        const mapResponse = await axios.post(
-          'http://localhost:8000/api/cd-prediction/crop-cd/generate-and-get-map',
+        const mapResponse = await api8000.post(
+          '/api/cd-prediction/crop-cd/generate-and-get-map',
           formData,
           {
             headers: {
@@ -454,9 +213,8 @@ export default {
         this.mapBlob = mapResponse.data;
         this.mapImageUrl = URL.createObjectURL(this.mapBlob);
         
-        // 更新后重新获取直方图和统计数据
+        // 更新后重新获取直方图
         await this.fetchLatestHistogram();
-        await this.fetchStatistics();
         
         this.$message.success('计算完成!');
         
@@ -479,7 +237,61 @@ export default {
         this.isCalculating = false;
         this.loadingMap = false;
         this.loadingHistogram = false;
-        this.loadingStats = false;
+      }
+    },
+    
+    // 从数据库计算
+    async calculateFromDatabase() {
+      try {
+        this.isCalculatingFromDB = true;
+        this.loadingMap = true;
+        this.loadingHistogram = true;
+        
+        // 创建FormData
+        const formData = new FormData();
+        formData.append('area', this.countyName);
+        formData.append('use_database', 'true'); // 使用数据库数据
+        
+        // 调用作物Cd地图接口
+        const mapResponse = await api8000.post(
+          '/api/cd-prediction/crop-cd/generate-and-get-map',
+          formData,
+          {
+            headers: {
+              'Content-Type': 'multipart/form-data'
+            },
+            responseType: 'blob'
+          }
+        );
+        
+        // 保存地图数据
+        this.mapBlob = mapResponse.data;
+        this.mapImageUrl = URL.createObjectURL(this.mapBlob);
+        
+        // 更新后重新获取直方图
+        await this.fetchLatestHistogram();
+        
+        this.$message.success('数据库计算完成!');
+        
+      } catch (error) {
+        console.error('从数据库计算失败:', error);
+        let errorMessage = '数据库计算失败,请重试';
+        
+        if (error.response) {
+          if (error.response.status === 400) {
+            errorMessage = '参数错误:' + (error.response.data.detail || '请检查县市名称');
+          } else if (error.response.status === 404) {
+            errorMessage = '不支持的县市:' + this.countyName;
+          } else if (error.response.status === 500) {
+            errorMessage = '服务器错误:' + (error.response.data.detail || '请稍后重试');
+          }
+        }
+        
+        this.$message.error(errorMessage);
+      } finally {
+        this.isCalculatingFromDB = false;
+        this.loadingMap = false;
+        this.loadingHistogram = false;
       }
     },
     
@@ -510,30 +322,6 @@ export default {
       link.click();
       URL.revokeObjectURL(link.href);
     },
-    
-    // 导出数据 - 修改为获取作物Cd的CSV文件
-    async exportData() {
-      try {
-        this.$message.info('正在获取作物Cd预测数据...');
-        
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-prediction/download-final-crop-cd-csv`,
-          { responseType: 'blob' }
-        );
-        
-        const blob = new Blob([response.data], { type: 'text/csv' });
-        const link = document.createElement('a');
-        link.href = URL.createObjectURL(blob);
-        link.download = `${this.countyName}_作物Cd预测数据.csv`;
-        link.click();
-        URL.revokeObjectURL(link.href);
-        
-        this.$message.success('数据导出成功');
-      } catch (error) {
-        console.error('导出数据失败:', error);
-        this.$message.error('导出数据失败: ' + (error.response?.data?.detail || '请稍后重试'));
-      }
-    }
   }
 };
 </script>
@@ -607,44 +395,54 @@ export default {
   gap: 20px;
 }
 
-/* 横向布局容器 */
-.horizontal-container {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 20px;
-  width: 100%;
-}
-
-.map-section, .histogram-section {
-  flex: 1;
-  min-width: 300px;
+/* 地图区域 */
+.map-section {
   background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   position: relative;
-  min-height: 400px;
+  min-height: 500px;
   backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
-.map-image, .histogram-image {
+.map-image {
   width: 100%;
   height: 100%;
-  max-height: 600px;
+  max-height: 500px;
   object-fit: contain;
   border-radius: 4px;
 }
 
-.table-area {
-  width: 100%;
+/* 统计图表区域 */
+.stats-area {
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
+  border-radius: 8px;
+  padding: 15px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  position: relative;
+  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
+}
+
+/* 直方图区域 */
+.histogram-section {
   background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
-  margin-top: 20px;
+  position: relative;
+  min-height: 500px;
   backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
+.histogram-image {
+  width: 100%;
+  height: 100%;
+  max-height: 600px;
+  object-fit: contain;
+  border-radius: 4px;
+}
+
 .loading-container {
   display: flex;
   flex-direction: column;
@@ -686,13 +484,8 @@ export default {
 
 /* 响应式布局调整 */
 @media (max-width: 992px) {
-  .horizontal-container {
+  .content-area {
     flex-direction: column;
   }
-  
-  .map-section, .histogram-section {
-    width: 100%;
-    flex: none;
-  }
 }
 </style>

+ 120 - 312
src/views/User/cadmiumPrediction/EffectiveCadmiumPrediction.vue

@@ -19,6 +19,15 @@
         <el-icon class="upload-icon"><Document /></el-icon>  
         上传并计算
         </el-button>
+        <!-- 添加从数据库计算按钮 -->
+        <el-button 
+          class="custom-button" 
+          :loading="isCalculatingFromDB" 
+          @click="calculateFromDatabase"
+        >
+        <el-icon class="upload-icon"><Box /></el-icon>  
+        从数据库计算
+        </el-button>
       </div>
       <!-- 操作按钮 -->
       <div class="action-buttons">
@@ -28,88 +37,36 @@
         <el-button class="custom-button" :disabled="!histogramBlob" @click="exportHistogram">
           <el-icon class="upload-icon"><Download /></el-icon>
           导出直方图</el-button>
-        <el-button class="custom-button" :disabled="!statisticsData.length" @click="exportData">
-          <el-icon class="upload-icon"><Download /></el-icon>
-          导出数据</el-button>
       </div>
     </div>
 
     <!-- 主体内容区 -->
     <div class="content-area">
-      <!-- 地图区域 - 修改为横向布局 -->
-      <div class="horizontal-container">
-        <!-- 地图展示 -->
-        <div class="map-section">
-          <h3>有效态Cd预测地图</h3>
-          <div v-if="loadingMap" class="loading-container">
-            <el-icon class="loading-icon"><Loading /></el-icon>
-            <span>地图加载中...</span>
-          </div>
-          <img v-if="mapImageUrl && !loadingMap" :src="mapImageUrl" alt="有效态Cd预测地图" class="map-image">
-          <div v-if="!mapImageUrl && !loadingMap" class="no-data">
-            <el-icon><Picture /></el-icon>
-            <p>暂无地图数据</p>
-          </div>
+      <!-- 地图区域 - 单独一行 -->
+      <div class="map-section">
+        <h3>有效态Cd预测地图</h3>
+        <div v-if="loadingMap" class="loading-container">
+          <el-icon class="loading-icon"><Loading /></el-icon>
+          <span>地图加载中...</span>
         </div>
-        
-        <!-- 直方图展示 -->
-        <div class="histogram-section">
-          <h3>有效态Cd预测直方图</h3>
-          <div v-if="loadingHistogram" class="loading-container">
-            <el-icon class="loading-icon"><Loading /></el-icon>
-            <span>直方图加载中...</span>
-          </div>
-          <img v-if="histogramImageUrl && !loadingHistogram" :src="histogramImageUrl" alt="有效态Cd预测直方图" class="histogram-image">
-          <div v-if="!histogramImageUrl && !loadingHistogram" class="no-data">
-            <el-icon><Histogram /></el-icon>
-            <p>暂无直方图数据</p>
-          </div>
+        <img v-if="mapImageUrl && !loadingMap" :src="mapImageUrl" alt="有效态Cd预测地图" class="map-image">
+        <div v-if="!mapImageUrl && !loadingMap" class="no-data">
+          <el-icon><Picture /></el-icon>
+          <p>暂无地图数据</p>
         </div>
       </div>
-
-      <!-- 统计图表区域 -->
-      <div class="stats-area">
-        <h3>{{countyName}} - 有效Cd预测统计信息</h3>
-        <div class="model-info">
-          <el-tag type="info">{{currentStats?.['模型类型'] || '有效Cd模型'}}</el-tag>
-          <span class="update-time">
-            最后更新: {{currentStats?.['数据更新时间'] ? new Date(currentStats['数据更新时间']).toLocaleString() : '未知'}}
-          </span>
-        </div>
-        
-        <div v-if="loadingStats" class="loading-container">
+      
+      <!-- 直方图区域 - 单独一行,放在统计信息下面 -->
+      <div class="histogram-section">
+        <h3>有效态Cd预测直方图</h3>
+        <div v-if="loadingHistogram" class="loading-container">
           <el-icon class="loading-icon"><Loading /></el-icon>
-          <span>统计数据加载中...</span>
-        </div>
-        
-        <div v-if="!loadingStats && statisticsData.length" class="stats-container">
-          <!-- 统计表格 -->
-         <el-table 
-            :data="statisticsData" 
-            style="width: 100%; margin-bottom: 20px;"
-            border
-            stripe
-          >
-            <el-table-column prop="name" label="统计项" min-width="180" />
-            <el-table-column prop="value" label="值" min-width="150" />
-            <el-table-column prop="unit" label="单位" min-width="100" />
-            <el-table-column prop="description" label="描述" min-width="200" />
-          </el-table>
-          
-          <!-- 统计图表 -->
-          <div class="charts-container">
-            <div class="chart-item">
-              <div ref="distributionChart" style="width: 100%; height: 400px;"></div>
-            </div>
-            <div class="chart-item">
-              <div ref="exceedanceChart" style="width: 100%; height: 400px;"></div>
-            </div>
-          </div>
+          <span>直方图加载中...</span>
         </div>
-        
-        <div v-if="!loadingStats && !statisticsData.length" class="no-data">
-          <el-icon><DataAnalysis /></el-icon>
-          <p>暂无统计数据</p>
+        <img v-if="histogramImageUrl && !loadingHistogram" :src="histogramImageUrl" alt="有效态Cd预测直方图" class="histogram-image">
+        <div v-if="!histogramImageUrl && !loadingHistogram" class="no-data">
+          <el-icon><Histogram /></el-icon>
+          <p>暂无直方图数据</p>
         </div>
       </div>
     </div>
@@ -119,46 +76,40 @@
 <script>
 import * as XLSX from 'xlsx';
 import { saveAs } from 'file-saver';
-import axios from 'axios';
+import { api8000 } from '@/utils/request';
 import * as echarts from 'echarts';
 import { 
-  Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
+  Loading, Upload, Picture, Histogram, Download, Document, Box 
 } from '@element-plus/icons-vue';
 
 export default {
-  name: 'CropCadmiumPrediction',
+  name: 'EffectiveCadmiumPrediction',
   components: { 
-    Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
+    Loading, Upload, Picture, Histogram, Download, Document, Box 
   },
   data() {
     return {
       isCalculating: false,
+      isCalculatingFromDB: false,
       loadingMap: false,
       loadingHistogram: false,
-      loadingStats: false,
-      statisticsData: [],
       mapImageUrl: null,
       histogramImageUrl: null,
       mapBlob: null,
       histogramBlob: null,
       selectedFile: null,
-      countyName: '乐昌市', // 默认县市名称
-      distributionChart: null,
-      exceedanceChart: null
+      countyName: '乐昌市' // 默认县市名称
     };
   },
 
   mounted() {
     // 组件挂载时获取最新数据
     this.fetchLatestResults();
-    this.fetchStatistics();
   },
 
   beforeDestroy() {
     if (this.mapImageUrl) URL.revokeObjectURL(this.mapImageUrl);
     if (this.histogramImageUrl) URL.revokeObjectURL(this.histogramImageUrl);
-    if (this.distributionChart) this.distributionChart.dispose();
-    if (this.exceedanceChart) this.exceedanceChart.dispose();
   },
   methods: {
     // 触发文件选择
@@ -200,8 +151,8 @@ export default {
     // 获取最新地图
     async fetchLatestMap() {
       try {
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-prediction/effective-cd/latest-map/${this.countyName}`,
+        const response = await api8000.get(
+          `/api/cd-prediction/effective-cd/latest-map/${this.countyName}`,
           { responseType: 'blob' }
         );
         
@@ -216,8 +167,8 @@ export default {
     // 获取最新直方图
     async fetchLatestHistogram() {
       try {
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-prediction/effective-cd/latest-histogram/${this.countyName}`,
+        const response = await api8000.get(
+          `/api/cd-prediction/effective-cd/latest-histogram/${this.countyName}`,
           { responseType: 'blob' }
         );
         
@@ -229,197 +180,6 @@ export default {
       }
     },
     
-    // 格式化统计数据
-    formatStatisticsData(stats) {
-      return [
-        { name: '数据点总数', value: stats['基础统计']['数据点总数'], unit: '个', description: '总样本数量' },
-        { name: '平均值', value: stats['基础统计']['均值'].toFixed(4), unit: '(mg/kg)', description: '所有样本的平均Cd含量' },
-        { name: '中位数', value: stats['基础统计']['中位数'].toFixed(4), unit: '(mg/kg)', description: '样本的中位Cd含量' },
-        { name: '标准差', value: stats['基础统计']['标准差'].toFixed(4), unit: '(mg/kg)', description: 'Cd含量的标准差' },
-        { name: '最小值', value: stats['基础统计']['最小值'].toFixed(4), unit: '(mg/kg)', description: '样本中的最小Cd含量' },
-        { name: '最大值', value: stats['基础统计']['最大值'].toFixed(4), unit: '(mg/kg)', description: '样本中的最大Cd含量' },
-        { name: '偏度', value: stats['基础统计']['偏度'].toFixed(4), unit: '', description: '数据分布偏斜程度' },
-        { name: '峰度', value: stats['基础统计']['峰度'].toFixed(4), unit: '', description: '数据分布峰态' },
-        { 
-          name: '经度范围', 
-          value: `${stats['空间统计']['经度范围']['最小值'].toFixed(6)} - ${stats['空间统计']['经度范围']['最大值'].toFixed(6)}`, 
-          unit: '度', 
-          description: `跨度: ${stats['空间统计']['经度范围']['跨度'].toFixed(6)}度` 
-        },
-        { 
-          name: '纬度范围', 
-          value: `${stats['空间统计']['纬度范围']['最小值'].toFixed(6)} - ${stats['空间统计']['纬度范围']['最大值'].toFixed(6)}`, 
-          unit: '度', 
-          description: `跨度: ${stats['空间统计']['纬度范围']['跨度'].toFixed(6)}度` 
-        }
-      ];
-    },
-
-    // 初始化图表 - 根据实际数据更新
-    initCharts() {
-      if (!this.statisticsData.length || !this.currentStats) return;
-      
-      // 销毁旧图表
-      if (this.distributionChart) this.distributionChart.dispose();
-      if (this.exceedanceChart) this.exceedanceChart.dispose();
-      
-      const histData = this.currentStats['分布直方图'];
-      
-      // 1. 分布直方图
-      this.distributionChart = echarts.init(this.$refs.distributionChart);
-      this.distributionChart.setOption({
-        title: {
-          text: 'Cd含量分布直方图',
-          left: 'center'
-        },
-        tooltip: {
-          trigger: 'item',
-          formatter: params => {
-            const index = params.dataIndex;
-            const lowerBound = histData['区间边界'][index].toFixed(4);
-            const upperBound = histData['区间边界'][index + 1].toFixed(4);
-            return `区间: ${lowerBound} ~ ${upperBound}<br/>频次: ${params.value}`;
-          }
-        },
-        xAxis: {
-          type: 'category',
-          data: histData['区间中心'].map(v => v.toFixed(4)),
-          name: 'Cd含量',
-          axisLabel: {
-            rotate: 45
-          }
-        },
-        yAxis: {
-          type: 'value',
-          name: '频次'
-        },
-        series: [{
-          name: '样本分布',
-          type: 'bar',
-          data: histData['频次'],
-          itemStyle: {
-            color: '#47C3B9'
-          },
-          barWidth: '80%'
-        }],
-        grid: {
-          bottom: '20%'
-        }
-      });
-      
-      // 2. 箱线图/统计图表
-      this.exceedanceChart = echarts.init(this.$refs.exceedanceChart);
-      
-      // 准备箱线图数据
-      const boxData = [
-        [
-          this.currentStats['基础统计']['最小值'],
-          this.currentStats['基础统计']['25%分位数'],
-          this.currentStats['基础统计']['中位数'],
-          this.currentStats['基础统计']['75%分位数'],
-          this.currentStats['基础统计']['最大值'],
-          // 还可以添加离群点数据(如果有)
-        ]
-      ];
-      
-      this.exceedanceChart.setOption({
-        title: {
-          text: 'Cd含量统计指标',
-          left: 'center'
-        },
-        tooltip: {
-          trigger: 'item',
-          axisPointer: {
-            type: 'shadow'
-          },
-          formatter: params => {
-            const data = boxData[0];
-            return [
-              '最大值: ' + data[4].toFixed(4),
-              '75%分位数: ' + data[3].toFixed(4),
-              '中位数: ' + data[2].toFixed(4),
-              '25%分位数: ' + data[1].toFixed(4),
-              '最小值: ' + data[0].toFixed(4)
-            ].join('<br/>');
-          }
-        },
-        xAxis: {
-          type: 'category',
-          data: ['Cd含量统计'],
-          axisLabel: {
-            rotate: 45
-          }
-        },
-        yAxis: {
-          type: 'value',
-          name: '(Cd含量)'
-        },
-        series: [{
-          name: '统计值',
-          type: 'boxplot',
-          data: boxData,
-          itemStyle: {
-            color: '#47C3B9',
-            borderColor: '#2F4554'
-          },
-          emphasis: {
-            itemStyle: {
-              color: '#FF6B6B',
-              borderColor: '#C23531'
-            }
-          },
-          tooltip: {
-            formatter: param => {
-              const data = boxData[0];
-              return [
-                '最大值: ' + data[4].toFixed(4),
-                '75%分位数: ' + data[3].toFixed(4),
-                '中位数: ' + data[2].toFixed(4),
-                '25%分位数: ' + data[1].toFixed(4),
-                '最小值: ' + data[0].toFixed(4)
-              ].join('<br/>');
-            }
-          }
-        }],
-        grid: {
-          bottom: '15%'
-        }
-      });
-      
-      // 响应式调整
-      window.addEventListener('resize', this.handleResize);
-    },
-
-    // 修改fetchStatistics方法
-    async fetchStatistics() {
-      try {
-        this.loadingStats = true;
-        
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-prediction/effective-cd/statistics/${this.countyName}`
-        );
-        
-        if (response.data.success && response.data.data) {
-          this.currentStats = response.data.data; // 保存原始统计数据
-          this.statisticsData = this.formatStatisticsData(response.data.data);
-          this.$nextTick(() => {
-            this.initCharts();
-          });
-        }
-      } catch (error) {
-        console.error('获取统计信息失败:', error);
-        this.$message.warning('获取统计信息失败');
-      } finally {
-        this.loadingStats = false;
-      }
-    },
-    
-    // 处理窗口大小变化
-    handleResize() {
-      if (this.distributionChart) this.distributionChart.resize();
-      if (this.exceedanceChart) this.exceedanceChart.resize();
-    },
-    
     // 上传并计算
     async calculate() {
       if (!this.selectedFile) {
@@ -431,16 +191,16 @@ export default {
         this.isCalculating = true;
         this.loadingMap = true;
         this.loadingHistogram = true;
-        this.loadingStats = true;
         
         // 创建FormData
         const formData = new FormData();
-        formData.append('county_name', this.countyName);
+        formData.append('area', this.countyName);
         formData.append('data_file', this.selectedFile);
+        formData.append('use_database', 'false'); // 使用上传的文件
         
         // 调用有效Cd地图接口
-        const mapResponse = await axios.post(
-          'http://localhost:8000/api/cd-prediction/effective-cd/generate-and-get-map',
+        const mapResponse = await api8000.post(
+          '/api/cd-prediction/effective-cd/generate-and-get-map',
           formData,
           {
             headers: {
@@ -454,9 +214,8 @@ export default {
         this.mapBlob = mapResponse.data;
         this.mapImageUrl = URL.createObjectURL(this.mapBlob);
         
-        // 更新后重新获取直方图和统计数据
+        // 更新后重新获取直方图
         await this.fetchLatestHistogram();
-        await this.fetchStatistics();
         
         this.$message.success('计算完成!');
         
@@ -479,7 +238,61 @@ export default {
         this.isCalculating = false;
         this.loadingMap = false;
         this.loadingHistogram = false;
-        this.loadingStats = false;
+      }
+    },
+    
+    // 从数据库计算
+    async calculateFromDatabase() {
+      try {
+        this.isCalculatingFromDB = true;
+        this.loadingMap = true;
+        this.loadingHistogram = true;
+        
+        // 创建FormData
+        const formData = new FormData();
+        formData.append('area', this.countyName);
+        formData.append('use_database', 'true'); // 使用数据库数据
+        
+        // 调用有效Cd地图接口
+        const mapResponse = await api8000.post(
+          '/api/cd-prediction/effective-cd/generate-and-get-map',
+          formData,
+          {
+            headers: {
+              'Content-Type': 'multipart/form-data'
+            },
+            responseType: 'blob'
+          }
+        );
+        
+        // 保存地图数据
+        this.mapBlob = mapResponse.data;
+        this.mapImageUrl = URL.createObjectURL(this.mapBlob);
+        
+        // 更新后重新获取直方图
+        await this.fetchLatestHistogram();
+        
+        this.$message.success('数据库计算完成!');
+        
+      } catch (error) {
+        console.error('从数据库计算失败:', error);
+        let errorMessage = '数据库计算失败,请重试';
+        
+        if (error.response) {
+          if (error.response.status === 400) {
+            errorMessage = '参数错误:' + (error.response.data.detail || '请检查县市名称');
+          } else if (error.response.status === 404) {
+            errorMessage = '不支持的县市:' + this.countyName;
+          } else if (error.response.status === 500) {
+            errorMessage = '服务器错误:' + (error.response.data.detail || '请稍后重试');
+          }
+        }
+        
+        this.$message.error(errorMessage);
+      } finally {
+        this.isCalculatingFromDB = false;
+        this.loadingMap = false;
+        this.loadingHistogram = false;
       }
     },
     
@@ -511,13 +324,13 @@ export default {
       URL.revokeObjectURL(link.href);
     },
     
-    // 导出数据 - 修改为获取有效Cd的CSV文件
+    // 导出数据
     async exportData() {
       try {
         this.$message.info('正在获取有效Cd预测数据...');
         
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-prediction/download-final-effective-cd-csv`,
+        const response = await api8000.get(
+          `/api/cd-prediction/download-final-effective-cd-csv`,
           { responseType: 'blob' }
         );
         
@@ -607,44 +420,44 @@ export default {
   gap: 20px;
 }
 
-/* 横向布局容器 */
-.horizontal-container {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 20px;
-  width: 100%;
-}
-
-.map-section, .histogram-section {
-  flex: 1;
-  min-width: 300px;
+/* 地图区域 */
+.map-section {
   background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   position: relative;
-  min-height: 400px;
+  min-height: 500px;
   backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
-.map-image, .histogram-image {
+.map-image {
   width: 100%;
   height: 100%;
-  max-height: 600px;
+  max-height: 500px;
   object-fit: contain;
   border-radius: 4px;
 }
 
-.table-area {
-  width: 100%;
+/* 直方图区域 */
+.histogram-section {
   background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
-  margin-top: 20px;
+  position: relative;
+  min-height: 500px;
   backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
+.histogram-image {
+  width: 100%;
+  height: 100%;
+  max-height: 600px;
+  object-fit: contain;
+  border-radius: 4px;
+}
+
 .loading-container {
   display: flex;
   flex-direction: column;
@@ -686,13 +499,8 @@ export default {
 
 /* 响应式布局调整 */
 @media (max-width: 992px) {
-  .horizontal-container {
+  .content-area {
     flex-direction: column;
   }
-  
-  .map-section, .histogram-section {
-    width: 100%;
-    flex: none;
-  }
 }
 </style>

+ 3 - 3
src/views/User/cadmiumPrediction/TotalCadmiumPrediction.vue

@@ -149,7 +149,7 @@ export default {
     async fetchLatestMap() {
       try {
         const response = await axios.get(
-          `https://soilgd.com:8000/api/cd-prediction/effective-cd/latest-map/${this.countyName}`,
+          `https://www.soilgd.com:8000/api/cd-prediction/effective-cd/latest-map/${this.countyName}`,
           { responseType: 'blob' }
         );
         
@@ -165,7 +165,7 @@ export default {
     async fetchLatestHistogram() {
       try {
         const response = await axios.get(
-          `https://soilgd.com:8000/api/cd-prediction/effective-cd/latest-histogram/${this.countyName}`,
+          `https://www.soilgd.com:8000/api/cd-prediction/effective-cd/latest-histogram/${this.countyName}`,
           { responseType: 'blob' }
         );
         
@@ -196,7 +196,7 @@ export default {
         
         // 调用有效态Cd地图接口
         const mapResponse = await axios.post(
-          'https://soilgd.com:8000/api/cd-prediction/effective-cd/generate-and-get-map',
+          'https://www.soilgd.com:8000/api/cd-prediction/effective-cd/generate-and-get-map',
           formData,
           {
             headers: {

+ 55 - 78
src/views/User/cadmiumPrediction/currentYearConcentration.vue

@@ -2,7 +2,6 @@
   <div class="container">
     <!-- 顶部操作栏 -->
     <div class="toolbar">
-      
       <!-- 操作按钮 -->
       <div class="action-buttons">
         <el-button class="custom-button" :disabled="!mapBlob" @click="exportMap">
@@ -19,37 +18,20 @@
 
     <!-- 主体内容区 -->
     <div class="content-area">
-      <!-- 地图区域 - 修改为横向布局 -->
-      <div class="horizontal-container">
-        <!-- 地图展示 -->
-        <div class="map-section">
-          <h3>Cd当年浓度空间分布图</h3>
-          <div v-if="loadingMap" class="loading-container">
-            <el-icon class="loading-icon"><Loading /></el-icon>
-            <span>地图加载中...</span>
-          </div>
-          <img v-if="mapImageUrl && !loadingMap" :src="mapImageUrl" alt="Cd当年浓度空间分布图" class="map-image">
-          <div v-if="!mapImageUrl && !loadingMap" class="no-data">
-            <el-icon><Picture /></el-icon>
-            <p>暂无地图数据</p>
-          </div>
+      <!-- 地图区域 - 单独一行 -->
+      <div class="map-section">
+        <h3>Cd当年浓度空间分布图</h3>
+        <div v-if="loadingMap" class="loading-container">
+          <el-icon class="loading-icon"><Loading /></el-icon>
+          <span>地图加载中...</span>
         </div>
-        
-        <!-- 直方图展示 -->
-        <div class="histogram-section">
-          <h3>Cd当年浓度直方图</h3>
-          <div v-if="loadingHistogram" class="loading-container">
-            <el-icon class="loading-icon"><Loading /></el-icon>
-            <span>直方图加载中...</span>
-          </div>
-          <img v-if="histogramImageUrl && !loadingHistogram" :src="histogramImageUrl" alt="Cd当年浓度直方图" class="histogram-image">
-          <div v-if="!histogramImageUrl && !loadingHistogram" class="no-data">
-            <el-icon><Histogram /></el-icon>
-            <p>暂无直方图数据</p>
-          </div>
+        <img v-if="mapImageUrl && !loadingMap" :src="mapImageUrl" alt="Cd当年浓度空间分布图" class="map-image">
+        <div v-if="!mapImageUrl && !loadingMap" class="no-data">
+          <el-icon><Picture /></el-icon>
+          <p>暂无地图数据</p>
         </div>
       </div>
-
+      
       <!-- 统计图表区域 -->
       <div class="stats-area">
         <h3>Cd当年浓度统计信息</h3>
@@ -78,12 +60,25 @@
             <el-table-column prop="unit" label="单位" min-width="100" />
             <el-table-column prop="description" label="描述" min-width="200" />
           </el-table>
-          
+        </div>
         
         <div v-if="!loadingStats && !statisticsData.length" class="no-data">
           <el-icon><DataAnalysis /></el-icon>
           <p>暂无统计数据</p>
         </div>
+      </div>
+      
+      <!-- 直方图区域 - 单独一行,放在统计结果下面 -->
+      <div class="histogram-section">
+        <h3>Cd当年浓度直方图</h3>
+        <div v-if="loadingHistogram" class="loading-container">
+          <el-icon class="loading-icon"><Loading /></el-icon>
+          <span>直方图加载中...</span>
+        </div>
+        <img v-if="histogramImageUrl && !loadingHistogram" :src="histogramImageUrl" alt="Cd当年浓度直方图" class="histogram-image">
+        <div v-if="!histogramImageUrl && !loadingHistogram" class="no-data">
+          <el-icon><Histogram /></el-icon>
+          <p>暂无直方图数据</p>
         </div>
       </div>
     </div>
@@ -93,7 +88,7 @@
 <script>
 import * as XLSX from 'xlsx';
 import { saveAs } from 'file-saver';
-import axios from 'axios';
+import { api8000 } from '@/utils/request';  // 导入api8000
 import * as echarts from 'echarts';
 import { 
   Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
@@ -172,8 +167,8 @@ export default {
     // 获取最新地图
     async fetchLatestMap() {
       try {
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-flux/end-cd/map`,
+        const response = await api8000.get(
+          `/api/cd-flux/end-cd/map`,
           { responseType: 'blob' }
         );
         
@@ -188,8 +183,8 @@ export default {
     // 获取最新直方图
     async fetchLatestHistogram() {
       try {
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-flux/end-cd/histogram`,
+        const response = await api8000.get(
+          `/api/cd-flux/end-cd/histogram`,
           { responseType: 'blob' }
         );
         
@@ -206,10 +201,10 @@ export default {
       if (!stats) return [];
       
       return [
-        { name: '最小值', value: stats.min.toFixed(4), unit: 'mg/kg', description: '样本中的最小Cd当年浓度' },
-        { name: '最大值', value: stats.max.toFixed(4), unit: 'mg/kg', description: '样本中的最大Cd当年浓度' },
-        { name: '平均值', value: stats.mean.toFixed(4), unit: 'mg/kg', description: '所有样本的平均Cd当年浓度' },
-        { name: '标准差', value: stats.std.toFixed(4), unit: 'mg/kg', description: 'Cd当年浓度的标准差' },
+        { name: '最小值', value: stats.min.toFixed(4), unit: 'g/ha/year', description: '样本中的最小Cd通量' },
+        { name: '最大值', value: stats.max.toFixed(4), unit: 'g/ha/year', description: '样本中的最大Cd通量' },
+        { name: '平均值', value: stats.mean.toFixed(4), unit: 'g/ha/year', description: '所有样本的平均Cd通量' },
+        { name: '标准差', value: stats.std.toFixed(4), unit: 'g/ha/year', description: 'Cd通量的标准差' },
       ];
     },
 
@@ -218,18 +213,14 @@ export default {
       try {
         this.loadingStats = true;
         
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-flux/end-cd/statistics`
+        const response = await api8000.get(
+          `/api/cd-flux/end-cd/statistics`
         );
         
         if (response.data) {
           const stats = response.data;
           this.statisticsData = this.formatStatisticsData(stats);
           this.updateTime = new Date().toISOString();
-          
-          this.$nextTick(() => {
-            this.initCharts(stats);
-          });
         }
       } catch (error) {
         console.error('获取统计信息失败:', error);
@@ -262,8 +253,8 @@ export default {
         formData.append('csv_file', this.selectedFile);
         
         // 调用Cd通量计算接口
-        await axios.post(
-          'http://localhost:8000/api/cd-flux/end-cd/calculate',
+        await api8000.post(
+          '/api/cd-flux/end-cd/calculate',
           formData,
           {
             headers: {
@@ -332,8 +323,8 @@ export default {
       try {
         this.$message.info('正在获取Cd当年浓度数据...');
         
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-flux/end-cd/export-csv`,
+        const response = await api8000.get(
+          `/api/cd-flux/end-cd/export-csv`,
           { responseType: 'blob' }
         );
         
@@ -424,44 +415,35 @@ export default {
   gap: 20px;
 }
 
-/* 横向布局容器 */
-.horizontal-container {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 20px;
-  width: 100%;
-}
-
-.map-section, .histogram-section {
-  flex: 1;
-  min-width: 300px;
+.map-section, .histogram-section, .stats-area {
   background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   position: relative;
-  min-height: 400px;
   backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
+.map-section {
+  min-height: 500px;
+}
+
+.histogram-section {
+  min-height: 500px;
+}
+
+.stats-area {
+  min-height: 300px;
+}
+
 .map-image, .histogram-image {
   width: 100%;
   height: 100%;
-  max-height: 600px;
+  max-height: 500px;
   object-fit: contain;
   border-radius: 4px;
 }
 
-.table-area {
-  width: 100%;
-  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
-  border-radius: 8px;
-  padding: 15px;
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
-  margin-top: 20px;
-  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
-}
-
 .loading-container {
   display: flex;
   flex-direction: column;
@@ -503,13 +485,8 @@ export default {
 
 /* 响应式布局调整 */
 @media (max-width: 992px) {
-  .horizontal-container {
+  .content-area {
     flex-direction: column;
   }
-  
-  .map-section, .histogram-section {
-    width: 100%;
-    flex: none;
-  }
 }
 </style>

+ 52 - 77
src/views/User/cadmiumPrediction/netFlux.vue

@@ -18,37 +18,20 @@
 
     <!-- 主体内容区 -->
     <div class="content-area">
-      <!-- 地图区域 - 修改为横向布局 -->
-      <div class="horizontal-container">
-        <!-- 地图展示 -->
-        <div class="map-section">
-          <h3>Cd净通量空间分布图</h3>
-          <div v-if="loadingMap" class="loading-container">
-            <el-icon class="loading-icon"><Loading /></el-icon>
-            <span>地图加载中...</span>
-          </div>
-          <img v-if="mapImageUrl && !loadingMap" :src="mapImageUrl" alt="Cd净通量空间分布图" class="map-image">
-          <div v-if="!mapImageUrl && !loadingMap" class="no-data">
-            <el-icon><Picture /></el-icon>
-            <p>暂无地图数据</p>
-          </div>
+      <!-- 地图区域 - 单独一行 -->
+      <div class="map-section">
+        <h3>Cd净通量空间分布图</h3>
+        <div v-if="loadingMap" class="loading-container">
+          <el-icon class="loading-icon"><Loading /></el-icon>
+          <span>地图加载中...</span>
         </div>
-        
-        <!-- 直方图展示 -->
-        <div class="histogram-section">
-          <h3>Cd净通量直方图</h3>
-          <div v-if="loadingHistogram" class="loading-container">
-            <el-icon class="loading-icon"><Loading /></el-icon>
-            <span>直方图加载中...</span>
-          </div>
-          <img v-if="histogramImageUrl && !loadingHistogram" :src="histogramImageUrl" alt="Cd净通量直方图" class="histogram-image">
-          <div v-if="!histogramImageUrl && !loadingHistogram" class="no-data">
-            <el-icon><Histogram /></el-icon>
-            <p>暂无直方图数据</p>
-          </div>
+        <img v-if="mapImageUrl && !loadingMap" :src="mapImageUrl" alt="Cd净通量空间分布图" class="map-image">
+        <div v-if="!mapImageUrl && !loadingMap" class="no-data">
+          <el-icon><Picture /></el-icon>
+          <p>暂无地图数据</p>
         </div>
       </div>
-
+      
       <!-- 统计图表区域 -->
       <div class="stats-area">
         <h3>Cd净通量统计信息</h3>
@@ -77,12 +60,25 @@
             <el-table-column prop="unit" label="单位" min-width="100" />
             <el-table-column prop="description" label="描述" min-width="200" />
           </el-table>
-          
+        </div>
         
         <div v-if="!loadingStats && !statisticsData.length" class="no-data">
           <el-icon><DataAnalysis /></el-icon>
           <p>暂无统计数据</p>
         </div>
+      </div>
+      
+      <!-- 直方图区域 - 单独一行,放在统计结果下面 -->
+      <div class="histogram-section">
+        <h3>Cd净通量直方图</h3>
+        <div v-if="loadingHistogram" class="loading-container">
+          <el-icon class="loading-icon"><Loading /></el-icon>
+          <span>直方图加载中...</span>
+        </div>
+        <img v-if="histogramImageUrl && !loadingHistogram" :src="histogramImageUrl" alt="Cd净通量直方图" class="histogram-image">
+        <div v-if="!histogramImageUrl && !loadingHistogram" class="no-data">
+          <el-icon><Histogram /></el-icon>
+          <p>暂无直方图数据</p>
         </div>
       </div>
     </div>
@@ -92,7 +88,7 @@
 <script>
 import * as XLSX from 'xlsx';
 import { saveAs } from 'file-saver';
-import axios from 'axios';
+import { api8000 } from '@/utils/request';  // 导入api8000
 import * as echarts from 'echarts';
 import { 
   Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
@@ -132,10 +128,7 @@ export default {
     if (this.distributionChart) this.distributionChart.dispose();
   },
   methods: {
-    // 触发文件选择
-    triggerFileUpload() {
-      this.$refs.fileInput.click();
-    },
+   
     
     // 处理文件上传
     handleFileUpload(event) {
@@ -171,8 +164,8 @@ export default {
     // 获取最新地图
     async fetchLatestMap() {
       try {
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-flux/net/map`,
+        const response = await api8000.get(
+          `/api/cd-flux/net/map`,
           { responseType: 'blob' }
         );
         
@@ -187,8 +180,8 @@ export default {
     // 获取最新直方图
     async fetchLatestHistogram() {
       try {
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-flux/net/histogram`,
+        const response = await api8000.get(
+          `/api/cd-flux/net/histogram`,
           { responseType: 'blob' }
         );
         
@@ -217,18 +210,14 @@ export default {
       try {
         this.loadingStats = true;
         
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-flux/net/statistics`
+        const response = await api8000.get(
+          `/api/cd-flux/net/statistics`
         );
         
         if (response.data) {
           const stats = response.data;
           this.statisticsData = this.formatStatisticsData(stats);
           this.updateTime = new Date().toISOString();
-          
-          this.$nextTick(() => {
-            this.initCharts(stats);
-          });
         }
       } catch (error) {
         console.error('获取统计信息失败:', error);
@@ -261,8 +250,8 @@ export default {
         formData.append('csv_file', this.selectedFile);
         
         // 调用Cd通量计算接口
-        await axios.post(
-          'http://localhost:8000/api/cd-flux/net/calculate',
+        await api8000.post(
+          '/api/cd-flux/net/calculate',
           formData,
           {
             headers: {
@@ -331,8 +320,8 @@ export default {
       try {
         this.$message.info('正在获取Cd净通量数据...');
         
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-flux/net/export-csv`,
+        const response = await api8000.get(
+          `/api/cd-flux/net/export-csv`,
           { responseType: 'blob' }
         );
         
@@ -423,44 +412,35 @@ export default {
   gap: 20px;
 }
 
-/* 横向布局容器 */
-.horizontal-container {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 20px;
-  width: 100%;
-}
-
-.map-section, .histogram-section {
-  flex: 1;
-  min-width: 300px;
+.map-section, .histogram-section, .stats-area {
   background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   position: relative;
-  min-height: 400px;
   backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
+.map-section {
+  min-height: 500px;
+}
+
+.histogram-section {
+  min-height: 500px;
+}
+
+.stats-area {
+  min-height: 300px;
+}
+
 .map-image, .histogram-image {
   width: 100%;
   height: 100%;
-  max-height: 600px;
+  max-height: 500px;
   object-fit: contain;
   border-radius: 4px;
 }
 
-.table-area {
-  width: 100%;
-  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
-  border-radius: 8px;
-  padding: 15px;
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
-  margin-top: 20px;
-  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
-}
-
 .loading-container {
   display: flex;
   flex-direction: column;
@@ -502,13 +482,8 @@ export default {
 
 /* 响应式布局调整 */
 @media (max-width: 992px) {
-  .horizontal-container {
+  .content-area {
     flex-direction: column;
   }
-  
-  .map-section, .histogram-section {
-    width: 100%;
-    flex: none;
-  }
 }
 </style>

+ 56 - 73
src/views/User/cadmiumPrediction/totalInputFlux.vue

@@ -2,7 +2,6 @@
   <div class="container">
     <!-- 顶部操作栏 -->
     <div class="toolbar">
-     
       <!-- 操作按钮 -->
       <div class="action-buttons">
         <el-button class="custom-button" :disabled="!mapBlob" @click="exportMap">
@@ -19,37 +18,20 @@
 
     <!-- 主体内容区 -->
     <div class="content-area">
-      <!-- 地图区域 - 修改为横向布局 -->
-      <div class="horizontal-container">
-        <!-- 地图展示 -->
-        <div class="map-section">
-          <h3>Cd输入通量空间分布图</h3>
-          <div v-if="loadingMap" class="loading-container">
-            <el-icon class="loading-icon"><Loading /></el-icon>
-            <span>地图加载中...</span>
-          </div>
-          <img v-if="mapImageUrl && !loadingMap" :src="mapImageUrl" alt="Cd输入通量空间分布图" class="map-image">
-          <div v-if="!mapImageUrl && !loadingMap" class="no-data">
-            <el-icon><Picture /></el-icon>
-            <p>暂无地图数据</p>
-          </div>
+      <!-- 地图区域 - 单独一行 -->
+      <div class="map-section">
+        <h3>Cd输入通量空间分布图</h3>
+        <div v-if="loadingMap" class="loading-container">
+          <el-icon class="loading-icon"><Loading /></el-icon>
+          <span>地图加载中...</span>
         </div>
-        
-        <!-- 直方图展示 -->
-        <div class="histogram-section">
-          <h3>Cd输入通量直方图</h3>
-          <div v-if="loadingHistogram" class="loading-container">
-            <el-icon class="loading-icon"><Loading /></el-icon>
-            <span>直方图加载中...</span>
-          </div>
-          <img v-if="histogramImageUrl && !loadingHistogram" :src="histogramImageUrl" alt="Cd输入通量直方图" class="histogram-image">
-          <div v-if="!histogramImageUrl && !loadingHistogram" class="no-data">
-            <el-icon><Histogram /></el-icon>
-            <p>暂无直方图数据</p>
-          </div>
+        <img v-if="mapImageUrl && !loadingMap" :src="mapImageUrl" alt="Cd输入通量空间分布图" class="map-image">
+        <div v-if="!mapImageUrl && !loadingMap" class="no-data">
+          <el-icon><Picture /></el-icon>
+          <p>暂无地图数据</p>
         </div>
       </div>
-
+      
       <!-- 统计图表区域 -->
       <div class="stats-area">
         <h3>Cd输入通量统计信息</h3>
@@ -78,12 +60,25 @@
             <el-table-column prop="unit" label="单位" min-width="100" />
             <el-table-column prop="description" label="描述" min-width="200" />
           </el-table>
-          
+        </div>
         
         <div v-if="!loadingStats && !statisticsData.length" class="no-data">
           <el-icon><DataAnalysis /></el-icon>
           <p>暂无统计数据</p>
         </div>
+      </div>
+      
+      <!-- 直方图区域 - 单独一行,放在统计结果下面 -->
+      <div class="histogram-section">
+        <h3>Cd输入通量直方图</h3>
+        <div v-if="loadingHistogram" class="loading-container">
+          <el-icon class="loading-icon"><Loading /></el-icon>
+          <span>直方图加载中...</span>
+        </div>
+        <img v-if="histogramImageUrl && !loadingHistogram" :src="histogramImageUrl" alt="Cd输入通量直方图" class="histogram-image">
+        <div v-if="!histogramImageUrl && !loadingHistogram" class="no-data">
+          <el-icon><Histogram /></el-icon>
+          <p>暂无直方图数据</p>
         </div>
       </div>
     </div>
@@ -95,6 +90,7 @@ import * as XLSX from 'xlsx';
 import { saveAs } from 'file-saver';
 import axios from 'axios';
 import * as echarts from 'echarts';
+import { api8000 } from '@/utils/request';
 import { 
   Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
 } from '@element-plus/icons-vue';
@@ -169,11 +165,10 @@ export default {
       }
     },
     
-    // 获取最新地图
     async fetchLatestMap() {
       try {
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-flux/input/map`,
+        const response = await api8000.get(
+          `/api/cd-flux/input/map`,
           { responseType: 'blob' }
         );
         
@@ -184,12 +179,12 @@ export default {
         this.$message.warning('获取最新地图失败,请先执行预测');
       }
     },
-    
-    // 获取最新直方图
+
+    // 修改fetchLatestHistogram方法
     async fetchLatestHistogram() {
       try {
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-flux/input/histogram`,
+        const response = await api8000.get(
+          `/api/cd-flux/input/histogram`,
           { responseType: 'blob' }
         );
         
@@ -200,6 +195,8 @@ export default {
         this.$message.warning('获取最新直方图失败,请先执行预测');
       }
     },
+
+
     
     // 格式化统计数据
     formatStatisticsData(stats) {
@@ -213,13 +210,13 @@ export default {
       ];
     },
 
-    // 修改fetchStatistics方法
+
     async fetchStatistics() {
       try {
         this.loadingStats = true;
         
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-flux/input/statistics`
+        const response = await api8000.get(
+         '/api/cd-flux/input/statistics'
         );
         
         if (response.data) {
@@ -262,8 +259,8 @@ export default {
         formData.append('csv_file', this.selectedFile);
         
         // 调用Cd通量计算接口
-        await axios.post(
-          'http://localhost:8000/api/cd-flux/input/calculate',
+        await api8000.post(
+          '/api/cd-flux/input/calculate',
           formData,
           {
             headers: {
@@ -332,8 +329,8 @@ export default {
       try {
         this.$message.info('正在获取Cd输入通量数据...');
         
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-flux/input/export-csv`,
+        const response = await api8000.get(
+          `/api/cd-flux/input/export-csv`,
           { responseType: 'blob' }
         );
         
@@ -424,44 +421,35 @@ export default {
   gap: 20px;
 }
 
-/* 横向布局容器 */
-.horizontal-container {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 20px;
-  width: 100%;
-}
-
-.map-section, .histogram-section {
-  flex: 1;
-  min-width: 300px;
+.map-section, .histogram-section, .stats-area {
   background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   position: relative;
-  min-height: 400px;
   backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
+.map-section {
+  min-height: 500px;
+}
+
+.histogram-section {
+  min-height: 500px;
+}
+
+.stats-area {
+  min-height: 300px;
+}
+
 .map-image, .histogram-image {
   width: 100%;
   height: 100%;
-  max-height: 600px;
+  max-height: 500px;
   object-fit: contain;
   border-radius: 4px;
 }
 
-.table-area {
-  width: 100%;
-  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
-  border-radius: 8px;
-  padding: 15px;
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
-  margin-top: 20px;
-  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
-}
-
 .loading-container {
   display: flex;
   flex-direction: column;
@@ -503,13 +491,8 @@ export default {
 
 /* 响应式布局调整 */
 @media (max-width: 992px) {
-  .horizontal-container {
+  .content-area {
     flex-direction: column;
   }
-  
-  .map-section, .histogram-section {
-    width: 100%;
-    flex: none;
-  }
 }
 </style>

+ 51 - 73
src/views/User/cadmiumPrediction/totalOutputFlux.vue

@@ -18,37 +18,20 @@
 
     <!-- 主体内容区 -->
     <div class="content-area">
-      <!-- 地图区域 - 修改为横向布局 -->
-      <div class="horizontal-container">
-        <!-- 地图展示 -->
-        <div class="map-section">
-          <h3>Cd输出通量空间分布图</h3>
-          <div v-if="loadingMap" class="loading-container">
-            <el-icon class="loading-icon"><Loading /></el-icon>
-            <span>地图加载中...</span>
-          </div>
-          <img v-if="mapImageUrl && !loadingMap" :src="mapImageUrl" alt="Cd输出通量空间分布图" class="map-image">
-          <div v-if="!mapImageUrl && !loadingMap" class="no-data">
-            <el-icon><Picture /></el-icon>
-            <p>暂无地图数据</p>
-          </div>
+      <!-- 地图区域 - 单独一行 -->
+      <div class="map-section">
+        <h3>Cd输出通量空间分布图</h3>
+        <div v-if="loadingMap" class="loading-container">
+          <el-icon class="loading-icon"><Loading /></el-icon>
+          <span>地图加载中...</span>
         </div>
-        
-        <!-- 直方图展示 -->
-        <div class="histogram-section">
-          <h3>Cd输出通量直方图</h3>
-          <div v-if="loadingHistogram" class="loading-container">
-            <el-icon class="loading-icon"><Loading /></el-icon>
-            <span>直方图加载中...</span>
-          </div>
-          <img v-if="histogramImageUrl && !loadingHistogram" :src="histogramImageUrl" alt="Cd输出通量直方图" class="histogram-image">
-          <div v-if="!histogramImageUrl && !loadingHistogram" class="no-data">
-            <el-icon><Histogram /></el-icon>
-            <p>暂无直方图数据</p>
-          </div>
+        <img v-if="mapImageUrl && !loadingMap" :src="mapImageUrl" alt="Cd输出通量空间分布图" class="map-image">
+        <div v-if="!mapImageUrl && !loadingMap" class="no-data">
+          <el-icon><Picture /></el-icon>
+          <p>暂无地图数据</p>
         </div>
       </div>
-
+      
       <!-- 统计图表区域 -->
       <div class="stats-area">
         <h3>Cd输出通量统计信息</h3>
@@ -77,12 +60,25 @@
             <el-table-column prop="unit" label="单位" min-width="100" />
             <el-table-column prop="description" label="描述" min-width="200" />
           </el-table>
-          
+        </div>
         
         <div v-if="!loadingStats && !statisticsData.length" class="no-data">
           <el-icon><DataAnalysis /></el-icon>
           <p>暂无统计数据</p>
         </div>
+      </div>
+      
+      <!-- 直方图区域 - 单独一行,放在统计结果下面 -->
+      <div class="histogram-section">
+        <h3>Cd输出通量直方图</h3>
+        <div v-if="loadingHistogram" class="loading-container">
+          <el-icon class="loading-icon"><Loading /></el-icon>
+          <span>直方图加载中...</span>
+        </div>
+        <img v-if="histogramImageUrl && !loadingHistogram" :src="histogramImageUrl" alt="Cd输出通量直方图" class="histogram-image">
+        <div v-if="!histogramImageUrl && !loadingHistogram" class="no-data">
+          <el-icon><Histogram /></el-icon>
+          <p>暂无直方图数据</p>
         </div>
       </div>
     </div>
@@ -92,7 +88,7 @@
 <script>
 import * as XLSX from 'xlsx';
 import { saveAs } from 'file-saver';
-import axios from 'axios';
+import { api8000 } from '@/utils/request';  // 导入api8000
 import * as echarts from 'echarts';
 import { 
   Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
@@ -171,8 +167,8 @@ export default {
     // 获取最新地图
     async fetchLatestMap() {
       try {
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-flux/output/map`,
+        const response = await api8000.get(
+          `/api/cd-flux/output/map`,
           { responseType: 'blob' }
         );
         
@@ -187,8 +183,8 @@ export default {
     // 获取最新直方图
     async fetchLatestHistogram() {
       try {
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-flux/output/histogram`,
+        const response = await api8000.get(
+          `/api/cd-flux/output/histogram`,
           { responseType: 'blob' }
         );
         
@@ -217,18 +213,14 @@ export default {
       try {
         this.loadingStats = true;
         
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-flux/output/statistics`
+        const response = await api8000.get(
+          `/api/cd-flux/output/statistics`
         );
         
         if (response.data) {
           const stats = response.data;
           this.statisticsData = this.formatStatisticsData(stats);
           this.updateTime = new Date().toISOString();
-          
-          this.$nextTick(() => {
-            this.initCharts(stats);
-          });
         }
       } catch (error) {
         console.error('获取统计信息失败:', error);
@@ -261,8 +253,8 @@ export default {
         formData.append('csv_file', this.selectedFile);
         
         // 调用Cd通量计算接口
-        await axios.post(
-          'http://localhost:8000/api/cd-flux/output/calculate',
+        await api8000.post(
+          '/api/cd-flux/output/calculate',
           formData,
           {
             headers: {
@@ -331,8 +323,8 @@ export default {
       try {
         this.$message.info('正在获取Cd输出通量数据...');
         
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-flux/output/export-csv`,
+        const response = await api8000.get(
+          `/api/cd-flux/output/export-csv`,
           { responseType: 'blob' }
         );
         
@@ -423,44 +415,35 @@ export default {
   gap: 20px;
 }
 
-/* 横向布局容器 */
-.horizontal-container {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 20px;
-  width: 100%;
-}
-
-.map-section, .histogram-section {
-  flex: 1;
-  min-width: 300px;
+.map-section, .histogram-section, .stats-area {
   background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   position: relative;
-  min-height: 400px;
   backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
+.map-section {
+  min-height: 500px;
+}
+
+.histogram-section {
+  min-height: 500px;
+}
+
+.stats-area {
+  min-height: 300px;
+}
+
 .map-image, .histogram-image {
   width: 100%;
   height: 100%;
-  max-height: 600px;
+  max-height: 500px;
   object-fit: contain;
   border-radius: 4px;
 }
 
-.table-area {
-  width: 100%;
-  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
-  border-radius: 8px;
-  padding: 15px;
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
-  margin-top: 20px;
-  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
-}
-
 .loading-container {
   display: flex;
   flex-direction: column;
@@ -502,13 +485,8 @@ export default {
 
 /* 响应式布局调整 */
 @media (max-width: 992px) {
-  .horizontal-container {
+  .content-area {
     flex-direction: column;
   }
-  
-  .map-section, .histogram-section {
-    width: 100%;
-    flex: none;
-  }
 }
 </style>

+ 46 - 21
src/views/User/dataStatistics/LandCultivatedStatistics.vue

@@ -6,14 +6,16 @@
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, onUnmounted } from 'vue'
 import * as echarts from 'echarts'
-import axios from 'axios'
+import { api8000 } from '@/utils/request' // 导入 api8000 实例
+
+const chartRef = ref(null)
 
 onMounted(async () => {
   try {
-    // 1. 真实接口请求
-    const res = await axios.get('http://localhost:8000/api/unit-grouping/areas/statistics')
+    // 1. 使用 api8000 实例请求数据
+    const res = await api8000.get('/api/unit-grouping/areas/statistics')
     const apiData = res.data
 
     // 2. 处理数据
@@ -43,27 +45,47 @@ onMounted(async () => {
 
     // 3. 渲染图表
     const chartDom = chartRef.value
+    if (!chartDom) {
+      console.error('图表容器未找到')
+      return
+    }
+    
     const myChart = echarts.init(chartDom)
 
     const option = {
       title: { 
-        text: '耕地质量评估按区域分类数据统计', //Cultivated land quality assessment
-        left: 'center' 
+        text: '耕地质量评估按区域分类数据统计',
+        left: 'center',
+        textStyle: {
+          fontSize: 16,
+          fontWeight: 'bold'
+        }
       },
       tooltip: { 
         trigger: 'axis', 
-        axisPointer: { type: 'shadow' } 
+        axisPointer: { type: 'shadow' },
+        formatter: (params) => {
+          return params.map(item => 
+            `${item.marker} ${item.seriesName}: ${item.value.toLocaleString()}`
+          ).join('<br/>')
+        }
       },
       legend: { 
         data: categories, 
-        bottom: 10 
+        bottom: 10,
+        itemWidth: 20,
+        itemHeight: 14,
+        textStyle: {
+          fontSize: 12
+        }
       },
       xAxis: { 
         type: 'category', 
-        data: allRegions,  // 使用包含全部县的地区列表
+        data: allRegions,
         axisLabel: { 
-          fontSize: 14,
-          // 为了区分汇总项,可以给"全部县"添加特殊样式
+          fontSize: 12,
+          interval: 0,
+          rotate: 30,
           formatter: (value) => {
             return value === '全部县' ? `{total|${value}}` : value
           },
@@ -78,15 +100,19 @@ onMounted(async () => {
       yAxis: { 
         type: 'value', 
         name: '数量', 
-        axisLabel: { fontSize: 14 } 
+        nameTextStyle: { fontSize: 14 },
+        axisLabel: { fontSize: 12 },
+        nameLocation: 'end'
       },
       series: seriesData,
       grid: { 
-        left: '5%', 
-        right: '5%', 
+        left: '3%', 
+        right: '3%', 
         bottom: '15%', 
+        top: '15%',
         containLabel: true 
-      }
+      },
+      color: ['#5470c6', '#91cc75', '#fac858'] // 自定义颜色
     }
 
     myChart.setOption(option)
@@ -98,17 +124,14 @@ onMounted(async () => {
     // 组件卸载时清除事件监听
     onUnmounted(() => {
       window.removeEventListener('resize', handleResize)
+      myChart.dispose() // 销毁图表实例
     })
 
   } catch (error) {
     console.error('接口请求失败:', error)
+    // 可以在这里添加错误处理UI
   }
 })
-
-const chartRef = ref(null)
-
-// 引入onUnmounted
-import { onUnmounted } from 'vue'
 </script>
 
 <style scoped>
@@ -122,5 +145,7 @@ import { onUnmounted } from 'vue'
   height: 500px;
   margin: 20px 0;
   background-color: white;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
 }
-</style>
+</style>

+ 5 - 5
src/views/User/farmlandQualityAssessment/farmlandQualityAssessment.vue

@@ -57,6 +57,7 @@
 
 <script>
 import * as echarts from 'echarts';
+import { api8000 } from '@/utils/request';  // 导入api8000
 
 // 分类颜色配置
 const categoryColors = {
@@ -106,12 +107,11 @@ export default {
     // 获取分类数据
     async fetchGroupingData() {
       try {
-        const response = await fetch(`http://localhost:8000/api/unit-grouping/h_xtfx`);
-        const result = await response.json();
+        const response = await api8000.get(`/api/unit-grouping/h_xtfx`);
         
-        if (result.success) {
-          this.groupingData = result.data;
-          this.statistics = result.statistics;
+        if (response.data.success) {
+          this.groupingData = response.data.data;
+          this.statistics = response.data.statistics;
         }
       } catch (error) {
         console.error('获取分类数据失败:', error);

+ 0 - 7
src/views/User/heavyMetalFluxCalculation/inputFluxCalculation/irrigationWater.vue

@@ -1,11 +1,5 @@
 <template>
   <div class="page-container">
-<<<<<<< HEAD
-   <div>
-    <router-view></router-view> <!-- 关键:子路由渲染位置 -->
-  </div>
-
-=======
     <!-- 上半部分:地图 + 柱状图 -->
     <div class="top-content">
       <!-- 灌溉水输入通量计算模块 -->
@@ -195,7 +189,6 @@
         </el-table-column>
       </el-table>
     </div>
->>>>>>> ding
   </div>
 </template>
 

+ 271 - 288
src/views/User/hmInFlux/grainRemoval/grainRemovalInputFlux.vue

@@ -1,124 +1,117 @@
 <template>
-  <div class="container">
-    <div class="gradient-card">
-      <div class="card-header">
-        <h1>籽粒移除输出通量计算结果</h1>
-        <p>{{ area }}地区作物籽粒移除Cd通量分析报告</p>
-      </div>
-
-      <div v-if="loading" class="loading-section">
-        <i class="fas fa-spinner fa-spin"></i> 正在加载数据...
-      </div>
-
-      <div v-if="error" class="error-section">
-        <i class="fas fa-exclamation-triangle"></i> {{ error }}
-      </div>
-
-      <template v-if="!loading && !error">
-        <div class="statistics-section">
-          <h2><i class="fas fa-chart-bar"></i> 统计分析</h2>
-          <div class="stats-grid">
-            <div class="stat-card">
-              <div class="stat-value">{{ statistics.total_samples.toLocaleString() }}</div>
-              <div class="stat-label">总样本数</div>
+  <div class="agricultural-input-management">
+    <!-- 直接展示结果页面 -->
+    <div class="page-container">
+      <el-card class="results-card">
+        <div class="results-content">
+          <!-- 地图区域 -->
+          <div class="map-section">
+            <h3>籽粒移除Cd通量分布图</h3>
+            <div v-if="loading" class="loading-container">
+              <el-icon class="loading-icon"><Loading /></el-icon>
+              <span>数据加载中...</span>
             </div>
-            <div class="stat-card">
-              <div class="stat-value">{{ statistics.mean_flux.toFixed(2) }} g/ha/a</div>
-              <div class="stat-label">平均通量</div>
+            <div v-else-if="error" class="error-container">
+              <el-icon class="error-icon"><Warning /></el-icon>
+              <span>{{ error }}</span>
             </div>
-            <div class="stat-card">
-              <div class="stat-value">{{ statistics.max_flux.toFixed(2) }} g/ha/a</div>
-              <div class="stat-label">最大通量</div>
+            <div v-else class="image-container">
+              <img :src="visualizationImage" class="result-image"></img>
             </div>
-            <div class="stat-card">
-              <div class="stat-value">{{ statistics.min_flux.toFixed(2) }} g/ha/a</div>
-              <div class="stat-label">最小通量</div>
-            </div>
-          </div>
-        </div>
-
-        <div class="visualization-section">
-          <h2><i class="fas fa-map"></i> 可视化分析图</h2>
-          <img v-if="visualizationImage" :src="visualizationImage" alt="Cd含量地图" class="result-image">
-          <div v-if="visualizationError" class="error-section">
-            <i class="fas fa-exclamation-triangle"></i> {{ visualizationError }}
           </div>
-          <div v-else class="image-container">
-            
-          </div>
-        </div>
-
-        <div class="data-section">
-          <h2><i class="fas fa-table"></i> 详细数据</h2>
-          <el-table
-            :data="tableData"
-            height="400"
-            style="width: 100%"
-            class="custom-table"
-            :header-cell-style="{ background: 'rgba(0, 150, 136, 0.1)', color: '#006064', fontWeight: '600' }"
-          >
-            <el-table-column prop="farmland_id" label="农田ID" width="120" align="center"></el-table-column>
-            <el-table-column prop="sample_id" label="样本ID" width="120" align="center"></el-table-column>
-            <el-table-column prop="crop_cd_value" label="作物镉Cd(mg/kg)" align="center">
-              <template #default="scope">
-                {{ scope.row.crop_cd_value.toFixed(5) }}
-              </template>
-            </el-table-column>
-            <el-table-column prop="f11_yield" label="作物亩产量(斤)" width="150" align="center"></el-table-column>
-            <el-table-column prop="grain_removal_flux" label="籽粒移除通量(g/ha/a)" width="170" align="center">
-              <template #default="scope">
-                {{ scope.row.grain_removal_flux.toFixed(5) }}
-              </template>
-            </el-table-column>
-          </el-table>
           
-          <el-pagination
-            background
-            layout="prev, pager, next, sizes, total"
-            :total="results.length"
-            :page-size="pageSize"
-            :current-page="currentPage"
-            :page-sizes="[5, 10, 20, 50]"
-            @size-change="handleSizeChange"
-            @current-change="handleCurrentChange"
-            class="custom-pagination"
-          >
-          </el-pagination>
-        </div>
-
-        <div class="formula-section">
-          <h2><i class="fas fa-calculator"></i> 计算公式</h2>
-          <div class="formula-card">
-            {{ formula }}
-          </div>
-          <div class="unit-note">
-            <i class="fas fa-info-circle"></i> 单位: {{ unit }}
+          <!-- 统计图表区域 -->
+          <div class="stats-area">
+            <h3>籽粒移除Cd通量统计信息</h3>
+            <div class="model-info">
+              <el-tag type="info">籽粒移除Cd通量模型</el-tag>
+              <span class="update-time">
+                最后更新: {{ reportTime }}
+              </span>
+            </div>
+            
+            <div v-if="loading" class="loading-container">
+              <el-icon class="loading-icon"><Loading /></el-icon>
+              <span>统计数据加载中...</span>
+            </div>
+            
+            <div v-else-if="error" class="error-container">
+              <el-icon class="error-icon"><Warning /></el-icon>
+              <span>{{ error }}</span>
+            </div>
+            
+            <div v-else class="stats-container">
+              <div class="stats-grid">
+                <div class="stat-card">
+                  <div class="stat-value">{{ statistics.total_samples.toLocaleString() }}</div>
+                  <div class="stat-label">总样本数</div>
+                </div>
+                <div class="stat-card">
+                  <div class="stat-value">{{ statistics.mean_flux.toFixed(4) }}</div>
+                  <div class="stat-label">平均通量</div>
+                </div>
+                <div class="stat-card">
+                  <div class="stat-value">{{ statistics.max_flux.toFixed(4) }}</div>
+                  <div class="stat-label">最大通量</div>
+                </div>
+                <div class="stat-card">
+                  <div class="stat-value">{{ statistics.min_flux.toFixed(4) }}</div>
+                  <div class="stat-label">最小通量</div>
+                </div>
+              </div>
+              
+              <div v-if="resultDetails.length > 0" class="details-table">
+                <el-table 
+                  :data="resultDetails" 
+                  style="width: 100%; margin-top: 20px;"
+                  border
+                  stripe
+                >
+                  <el-table-column prop="type" label="区域类型" align="center" min-width="120" />
+                  <el-table-column prop="flux" label="Cd通量(g/ha/a)" align="center" :formatter="formatNumber" min-width="150" />
+                </el-table>
+              </div>
+            </div>
           </div>
         </div>
-
-        <div class="card-footer">
-          <p><i class="fas fa-clock"></i> 报告生成时间: {{ reportTime }}</p>
-        </div>
-      </template>
+      </el-card>
     </div>
   </div>
 </template>
 
 <script>
-import { ref, computed, onMounted } from 'vue';
-import { ElTable, ElTableColumn, ElPagination } from 'element-plus';
+import { ref, onMounted, onBeforeUnmount } from 'vue';
+import { 
+  ElButton, 
+  ElCard, 
+  ElTable, 
+  ElTableColumn,
+  ElTag,
+  ElIcon
+} from 'element-plus';
+import { 
+  Loading, 
+  Picture, 
+  Warning 
+} from '@element-plus/icons-vue';
+import { api8000 } from '@/utils/request';
 
 export default {
-  name: 'GrainRemovalResult',
   components: {
+    ElButton,
+    ElCard,
     ElTable,
     ElTableColumn,
-    ElPagination
+    ElTag,
+    ElIcon,
+    Loading,
+    Picture,
+    Warning
   },
   props: {
     area: {
       type: String,
-      default: '韶关'
+      default: '乐昌市'
     }
   },
   setup(props) {
@@ -130,71 +123,63 @@ export default {
       max_flux: 0,
       min_flux: 0
     });
-    const formula = ref("");
-    const unit = ref("");
     const visualizationImage = ref("");
     const reportTime = ref("");
     const loading = ref(true);
     const error = ref(null);
     
-    // 分页相关变量
-    const currentPage = ref(1);
-    const pageSize = ref(5);
-    
-    // 计算分页后的数据
-    const tableData = computed(() => {
-      const start = (currentPage.value - 1) * pageSize.value;
-      const end = start + pageSize.value;
-      return results.value.slice(start, end);
-    });
-    
-    // 分页事件处理
-    const handleSizeChange = (newSize) => {
-      pageSize.value = newSize;
-      currentPage.value = 1; // 重置到第一页
+    // 格式化数字显示(保留4位小数)
+    const formatNumber = (row, column, cellValue) => {
+      if (typeof cellValue === 'number') {
+        return cellValue.toFixed(4);
+      }
+      return cellValue;
     };
     
-    const handleCurrentChange = (newPage) => {
-      currentPage.value = newPage;
-    };
+    // 计算详情数据
+    const resultDetails = ref([]);
     
     // 从API获取数据
     const fetchData = async () => {
       try {
+        loading.value = true;
+        error.value = null;
+        
         // 获取计算结果数据
-        const dataResponse = await fetch(
-          `http://127.0.0.1:8000/api/cd-flux-removal/grain-removal?area=${encodeURIComponent(props.area)}`
+        const dataResponse = await api8000.get(
+          `/api/cd-flux-removal/grain-removal?area=${encodeURIComponent(props.area)}`
         );
         
-        if (!dataResponse.ok) {
-          throw new Error(`数据获取失败: ${dataResponse.statusText}`);
+        if (!dataResponse.data.success) {
+          throw new Error(`API错误: ${dataResponse.data.message}`);
         }
         
-        const dataJson = await dataResponse.json();
+        // 设置数据
+        results.value = dataResponse.data.data.results;
+        statistics.value = dataResponse.data.data.statistics;
         
-        if (!dataJson.success) {
-          throw new Error(`API错误: ${dataJson.message}`);
+        // 更新详情数据
+        if (dataResponse.data.data.details) {
+          resultDetails.value = Object.entries(dataResponse.data.data.details).map(([type, flux]) => ({
+            type: getTypeName(type),
+            flux: flux
+          }));
         }
         
-        // 设置数据
-        results.value = dataJson.data.results;
-        statistics.value = dataJson.data.statistics;
-        formula.value = dataJson.data.formula;
-        unit.value = dataJson.data.unit;
-        
         // 获取可视化图片
-        const imageResponse = await fetch(
-          `http://127.0.0.1:8000/api/cd-flux-removal/grain-removal/visualize?area=${encodeURIComponent(props.area)}&level=city`
-        );
-        
-        if (!imageResponse.ok) {
-          throw new Error(`图片获取失败: ${imageResponse.statusText}`);
+        try {
+          const imageResponse = await api8000.get(
+            `/api/cd-flux-removal/grain-removal/visualize?area=${encodeURIComponent(props.area)}&level=county`,
+            { responseType: 'blob' }
+          );
+          
+          // 创建图片Blob URL
+          const blob = new Blob([imageResponse.data], { type: imageResponse.headers['content-type'] });
+          visualizationImage.value = URL.createObjectURL(blob);
+        } catch (imgErr) {
+          console.error('图片加载错误:', imgErr);
         }
         
-        // 创建图片Blob URL
-        const blob = await imageResponse.blob();
-        visualizationImage.value = URL.createObjectURL(blob);
-        
         // 设置报告时间
         reportTime.value = new Date().toLocaleString('zh-CN', {
           year: 'numeric',
@@ -217,96 +202,118 @@ export default {
       fetchData();
     });
     
+    onBeforeUnmount(() => {
+      if (visualizationImage.value) {
+        URL.revokeObjectURL(visualizationImage.value);
+      }
+    });
+    
     return {
       results,
       statistics,
-      formula,
-      unit,
       visualizationImage,
       reportTime,
       loading,
       error,
-      tableData,
-      currentPage,
-      pageSize,
-      handleSizeChange,
-      handleCurrentChange
+      resultDetails,
+      formatNumber
     };
   }
 };
 </script>
 
 <style scoped>
-/* 保留您原有的样式,只添加Element UI的自定义样式 */
-.container {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  min-height: 100vh;
+/* 整体布局优化 - 与农业投入品保持一致 */
+.agricultural-input-management {
   padding: 20px;
-  background: linear-gradient(135deg, #e6f7ff, #b3e0ff);
+  background: linear-gradient(
+    135deg, 
+    rgba(230, 247, 255, 0.7) 0%, 
+    rgba(240, 248, 255, 0.7) 100%
+  );
+  min-height: 100vh;
+  box-sizing: border-box;
 }
 
-.gradient-card {
-  background: linear-gradient(135deg,
-      rgba(250, 253, 255, 0.9),
-      rgba(220, 240, 255, 0.9));
-  border-radius: 20px;
-  box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
-  padding: 30px;
+.page-container {
   width: 100%;
-  max-width: 1000px;
-  backdrop-filter: blur(8px);
+  height: 100%;
+}
+
+/* 工具栏样式 */
+.toolbar {
+  display: flex;
+  justify-content: flex-start;
+  gap: 15px;
+  margin-bottom: 20px;
+  padding: 15px;
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(5px);
+}
+
+.custom-button {
+  background-color: #47C3B9 !important;
+  color: #DCFFFA !important;
   border: none;
+  border-radius: 155px;
+  padding: 10px 20px;
+  font-weight: bold;
   display: flex;
-  flex-direction: column;
-  overflow: hidden;
+  align-items: center;
 }
 
-.card-header {
-  text-align: center;
-  margin-bottom: 30px;
-  border-bottom: 2px solid rgba(0, 96, 100, 0.1);
-  padding-bottom: 20px;
+/* 结果卡片样式 */
+.results-card {
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 8px;
+  padding: 20px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(5px);
+  height: 100%;
+  box-sizing: border-box;
 }
 
-.card-header h1 {
-  font-size: 2.2rem;
-  color: #006064;
-  margin-bottom: 10px;
-  font-weight: 700;
+.results-content {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
 }
 
-.card-header p {
-  font-size: 1.2rem;
-  color: #00838f;
-  opacity: 0.9;
+.map-section, .stats-area {
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 8px;
+  padding: 20px;
+  margin-bottom: 20px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
 }
 
-.loading-section, .error-section {
-  text-align: center;
-  padding: 40px;
-  font-size: 1.4rem;
-  color: #006064;
+h3 {
+  margin-bottom: 15px;
+  color: #333;
+  font-size: 18px;
+  font-weight: 600;
 }
 
-.loading-section i {
-  margin-right: 15px;
-  font-size: 1.8rem;
-  color: #00838f;
+.model-info {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  margin-bottom: 15px;
 }
 
-.error-section i {
-  margin-right: 15px;
-  font-size: 1.8rem;
-  color: #d32f2f;
+.update-time {
+  color: #666;
+  font-size: 14px;
 }
 
+/* 统计卡片样式 */
 .stats-grid {
   display: grid;
   grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
   gap: 20px;
-  margin-top: 20px;
+  margin-bottom: 20px;
 }
 
 .stat-card {
@@ -315,7 +322,7 @@ export default {
   padding: 20px;
   text-align: center;
   box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
-  transition: transform 0.3s ease;
+  transition: all 0.3s ease;
 }
 
 .stat-card:hover {
@@ -335,90 +342,98 @@ export default {
   color: #00838f;
 }
 
-.visualization-section, 
-.data-section, 
-.formula-section,
-.statistics-section {
-  margin-bottom: 30px;
-  padding: 20px;
-  background: rgba(255, 255, 255, 0.6);
-  border-radius: 15px;
-  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
-}
-
-.visualization-section h2,
-.data-section h2,
-.formula-section h2,
-.statistics-section h2 {
-  font-size: 1.6rem;
-  color: #006064;
-  margin-bottom: 20px;
+/* 图片容器限定大小 */
+.image-container {
+  width: 100%;
+  height: 500px;
   display: flex;
+  justify-content: center;
   align-items: center;
-  gap: 10px;
+  background-color: #f9f9f9;
+  border-radius: 8px;
+  overflow: hidden;
 }
 
 .result-image {
   max-width: 100%;
-  max-height: 500px;
-  border-radius: 10px;
-  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
-}
-
-.formula-card {
-  font-family: 'Courier New', monospace;
-  font-size: 1.2rem;
-  background: rgba(255, 255, 255, 0.8);
-  padding: 20px;
-  border-radius: 10px;
-  text-align: center;
-  margin: 20px 0;
-  border: 1px dashed #00838f;
-  color: #006064;
+  max-height: 100%;
+  object-fit: contain;
 }
 
-.unit-note {
-  text-align: center;
-  font-size: 1.1rem;
-  color: #00838f;
+.loading-container, .error-container {
   display: flex;
+  flex-direction: column;
   align-items: center;
   justify-content: center;
-  gap: 8px;
+  height: 300px;
+  gap: 15px;
 }
 
-.card-footer {
-  text-align: center;
-  font-size: 1rem;
-  color: #00838f;
-  padding-top: 20px;
-  border-top: 1px solid rgba(0, 150, 136, 0.2);
+.loading-container {
+  color: #47C3B9;
+}
+
+.error-container {
+  color: #F56C6C;
+}
+
+.loading-icon {
+  font-size: 36px;
+  margin-bottom: 10px;
+  animation: rotate 2s linear infinite;
+}
+
+.error-icon {
+  font-size: 36px;
+  margin-bottom: 10px;
+}
+
+.no-data {
   display: flex;
+  flex-direction: column;
   align-items: center;
   justify-content: center;
-  gap: 10px;
+  height: 300px;
+  color: #999;
+  font-size: 16px;
+}
+
+.no-data .el-icon {
+  font-size: 48px;
+  margin-bottom: 10px;
+}
+
+.details-table {
+  margin-top: 20px;
+}
+
+@keyframes rotate {
+  from { transform: rotate(0deg); }
+  to { transform: rotate(360deg); }
 }
 
 /* 响应式设计 */
 @media (max-width: 768px) {
-  .stats-grid {
-    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+  .toolbar {
+    flex-direction: column;
+    align-items: stretch;
   }
   
-  .card-header h1 {
-    font-size: 1.8rem;
+  .stats-grid {
+    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
   }
   
-  .visualization-section h2,
-  .data-section h2,
-  .formula-section h2,
-  .statistics-section h2 {
-    font-size: 1.4rem;
+  .image-container {
+    height: 300px;
   }
 }
 
 @media (max-width: 480px) {
-  .gradient-card {
+  .agricultural-input-management {
+    padding: 10px;
+  }
+  
+  .results-card {
     padding: 15px;
   }
   
@@ -430,44 +445,12 @@ export default {
     padding: 15px;
   }
   
+  .image-container {
+    height: 250px;
+  }
+  
   .stat-value {
     font-size: 1.5rem;
   }
 }
-
-.result-image {
-  width: 100%;
-  max-height: 400px;
-  object-fit: contain;
-  border-radius: 8px;
-  border: 1px solid #e4e7ed;
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
-  background-color: #f8f8f8;
-}
-
-/* Element UI 自定义样式 */
-.custom-table {
-  border-radius: 10px;
-  overflow: hidden;
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
-  margin-top: 20px;
-}
-
-.custom-table .el-table__row {
-  transition: background-color 0.3s;
-}
-
-.custom-table .el-table__row:hover {
-  background-color: rgba(178, 235, 242, 0.3) !important;
-}
-
-.custom-table .el-table__row:nth-child(even) {
-  background-color: rgba(178, 235, 242, 0.15);
-}
-
-.custom-pagination {
-  margin-top: 20px;
-  display: flex;
-  justify-content: center;
-}
 </style>

+ 151 - 285
src/views/User/hmInFlux/grainRemoval/samplingDesc1.vue

@@ -1,41 +1,42 @@
 <template>
   <div class="sampling-process">
-    <div class="header-section">
-      <h2>籽粒移除</h2>
-      <div class="subtitle">重金属在土壤-农作物系统中的迁移与输出机制</div>
-    </div>
-    
-    <div class="content-section">
-      <div class="text-content">
-        <p>
-          籽粒移除与人体健康风险的关联更为直接。重金属在作物籽粒中积累后,会通过食物链对人体健康构成潜在威胁,像中国湖南水稻的镉污染就已成为突出的环境健康问题,珠江三角洲各地市水稻籽粒对镉的富集程度甚至超过了蔬菜和水果。
-        </p>
-        <p>
-          图生动描绘了重金属在籽粒中的积累过程,以及籽粒收获后重金属随之输出农田并进入食物链的关联链条。如图所示,土壤中的重金属通过植物根系被吸收并最终富集于籽粒中。当农作物成熟后,农民收割作物并将富含重金属的籽粒收集起来,这一过程使得籽粒中积累的重金属随之离开农田系统,构成重要的重金属输出途径。
-        </p>
+    <!-- 籽粒移除部分 -->
+    <div class="section-container">
+      <div class="section-header">
+        <div class="section-number">1</div>
+        <h2>籽粒移除</h2>
       </div>
-
-      <!-- 图片区域 -->
-      <div class="image-row">
-        <div class="image-container">
-          <el-image :src="image1" alt="籽粒移除是重金属输出的重要途径" class="sampling-image"></el-image>
-          <p class="image-caption">
-            籽粒移除是重金属输出的重要途径
-          </p>
+      
+      <div class="section-content">
+        <div class="description-card">
+          <div class="icon">🌾</div>
+          <div class="description-text">
+            <p>
+              籽粒移除与人体健康风险的关联更为直接。重金属在作物籽粒中积累后,会通过食物链对人体健康构成潜在威胁,像中国湖南水稻的镉污染就已成为突出的环境健康问题。
+            </p>
+            <p>
+              如图所示,土壤中的重金属通过植物根系被吸收并最终富集于籽粒中。当农作物成熟后,农民收割作物并将富含重金属的籽粒收集起来,这一过程使得籽粒中积累的重金属随之离开农田系统,构成重要的重金属输出途径。
+            </p>
+          </div>
         </div>
-      </div>
-
-      <!-- 新增视频区域 -->
-      <div class="video-section">
-        <h3 class="video-title">籽粒移除过程视频演示</h3>
-        <div class="video-container">
-          <video controls class="sampling-video">
-            <source src="@/assets/videos/秸秆移除和籽粒移除.mp4" type="video/mp4">
-            您的浏览器不支持HTML5视频播放。
-          </video>
-          <p class="video-caption">
-            视频展示了重金属在籽粒中的积累和移除过程
-          </p>
+        
+        <div class="image-gallery">
+          <div class="image-card">
+            <el-image :src="image1" alt="籽粒移除是重金属输出的重要途径" class="sampling-image"></el-image>
+            <div class="image-info">
+              <h3>籽粒移除过程</h3>
+            </div>
+          </div>
+          
+          <div class="image-card">
+            <video controls class="sampling-image">
+              <source src="@/assets/videos/秸秆移除和籽粒移除.mp4" type="video/mp4">
+              您的浏览器不支持HTML5视频播放。
+            </video>
+            <div class="image-info">
+              <h3>籽粒移除视频演示</h3>
+            </div>
+          </div>
         </div>
       </div>
     </div>
@@ -55,11 +56,10 @@ export default {
 <style scoped>
 .sampling-process {
   padding: 30px;
-  background: linear-gradient(135deg, rgba(230, 247, 255, 0.7) 0%, rgba(240, 248, 255, 0.7) 100%);
+  background: linear-gradient(135deg, rgba(245, 250, 255, 0.85) 0%, rgba(235, 245, 255, 0.85) 100%);
   position: relative;
   overflow: hidden;
-  border-radius: 16px;
-  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
+  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 }
 
 .sampling-process::before {
@@ -70,334 +70,200 @@ export default {
   width: 100%;
   height: 100%;
   background: url('https://images.unsplash.com/photo-1518834107812-67b0b7c58434?q=80&w=2070&auto=format&fit=crop') center/cover;
-  opacity: 0.15;
+  opacity: 0.12;
   z-index: -1;
   filter: blur(2px);
 }
 
-/* 标题区域 */
-.header-section {
-  text-align: center;
-  margin-bottom: 30px;
-  padding-bottom: 20px;
-  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
-}
-
-.header-section h2 {
-  position: relative;
-  padding-bottom: 15px;
-  margin-bottom: 10px;
-  color: #1a365d;
-  font-size: 2rem;
-  font-weight: 700;
-  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-}
-
-.header-section h2::after {
-  content: "";
-  position: absolute;
-  bottom: 0;
-  left: 50%;
-  transform: translateX(-50%);
-  width: 80px;
-  height: 4px;
-  background: linear-gradient(90deg, #4a9ef7, #3acfd5);
-  border-radius: 3px;
-}
-
-.subtitle {
-  font-size: 1.2rem;
-  color: #2d5986;
-  font-weight: 500;
-  letter-spacing: 0.5px;
-}
-
-/* 内容区域 */
-.content-section {
-  margin-bottom: 40px;
-  padding: 25px;
-  background: rgba(255, 255, 255, 0.85);
-  border-radius: 12px;
-  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
+.section-container {
+  margin-bottom: 50px;
+  padding: 30px;
+  border-radius: 18px;
+  background: rgba(255, 255, 255, 0.88);
+  box-shadow: 0 8px 25px rgba(0, 60, 120, 0.08);
   transition: all 0.4s ease;
   overflow: hidden;
+  position: relative;
 }
 
-.content-section:hover {
+.section-container:hover {
   transform: translateY(-5px);
-  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
-  background: rgba(255, 255, 255, 0.92);
-}
-
-.text-content {
-  margin-bottom: 30px;
+  box-shadow: 0 12px 35px rgba(0, 60, 120, 0.15);
 }
 
-p {
-  text-indent: 2em;
-  margin: 20px 0;
-  line-height: 1.8;
-  color: #2d3748;
-  font-size: 1.1rem;
+.section-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 25px;
   position: relative;
-  padding-left: 20px;
-}
-
-p::before {
-  content: "•";
-  position: absolute;
-  left: 0;
-  top: 0;
-  color: #3a9fd3;
-  font-size: 1.5rem;
-  line-height: 1.8;
+  padding-bottom: 15px;
+  border-bottom: 2px solid rgba(58, 160, 207, 0.25);
 }
 
-/* 图片区域 */
-.image-row {
+.section-number {
+  width: 50px;
+  height: 50px;
+  background: linear-gradient(135deg, #4a9ef7, #3a9fd3);
+  color: white;
+  border-radius: 50%;
   display: flex;
+  align-items: center;
   justify-content: center;
-  margin-bottom: 30px;
+  font-size: 1.8rem;
+  font-weight: bold;
+  margin-right: 20px;
+  box-shadow: 0 5px 12px rgba(74, 158, 247, 0.25);
 }
 
-.image-container {
-  border-radius: 12px;
-  overflow: hidden;
-  position: relative;
-  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
-  max-width: 800px;
-  margin: 0 auto;
-  transition: all 0.4s ease;
+h2 {
+  color: #1a365d;
+  font-size: 1.9rem;
+  margin: 0;
+  font-weight: 650;
+  letter-spacing: 0.5px;
 }
 
-.image-container:hover {
-  transform: scale(1.01);
-  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
+.section-content {
+  padding: 0 10px;
 }
 
-.sampling-image {
-  width: 100%;
-  height: auto;
-  min-height: 350px;
-  display: block;
-  background: rgba(245, 249, 255, 0.4);
-  object-fit: cover;
-  transition: transform 0.5s ease;
+.description-card {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 30px;
+  padding: 20px;
+  background: rgba(235, 245, 255, 0.6);
+  border-radius: 15px;
 }
 
-.image-container:hover .sampling-image {
-  transform: scale(1.02);
+.icon {
+  font-size: 2.5rem;
+  margin-right: 25px;
+  color: #3a9fd3;
 }
 
-.image-caption {
-  text-align: center;
+.description-text p {
   font-size: 1.1rem;
-  color: #1a365d;
-  padding: 15px;
-  font-weight: 600;
-  background: linear-gradient(to right, rgba(248, 250, 252, 0.9), rgba(240, 248, 255, 0.9));
+  line-height: 1.8;
+  color: #2d3748;
   margin: 0;
-  border-top: 1px dashed #cbd5e0;
-  position: relative;
-}
-
-.image-caption::before {
-  content: "📌";
-  position: absolute;
-  left: 20px;
-  top: 50%;
-  transform: translateY(-50%);
-}
-
-/* 视频区域 */
-.video-section {
-  margin-top: 40px;
-  padding-top: 30px;
-  border-top: 1px solid rgba(0, 0, 0, 0.1);
 }
 
-.video-title {
-  text-align: center;
-  color: #1a365d;
-  font-size: 1.5rem;
-  margin-bottom: 20px;
-  position: relative;
-  padding-bottom: 10px;
+/* 图片画廊样式 */
+.image-gallery {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 25px;
+  margin-top: 20px;
 }
 
-.video-title::after {
-  content: "";
-  position: absolute;
-  bottom: 0;
-  left: 50%;
-  transform: translateX(-50%);
-  width: 60px;
-  height: 3px;
-  background: linear-gradient(90deg, #4a9ef7, #3acfd5);
-  border-radius: 2px;
-}
-
-.video-container {
-  border-radius: 12px;
+.image-card {
+  border-radius: 15px;
   overflow: hidden;
-  position: relative;
-  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
-  max-width: 800px;
-  margin: 0 auto;
+  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1);
   transition: all 0.4s ease;
+  background: white;
 }
 
-.video-container:hover {
-  transform: translateY(-5px);
-  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
+.image-card:hover {
+  transform: translateY(-8px);
+  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
 }
 
-.sampling-video {
+.sampling-image {
   width: 100%;
-  height: auto;
+  height: 250px;
   display: block;
-  background: #f8fafc;
-  min-height: 350px;
-}
-
-.video-caption {
-  text-align: center;
-  font-size: 1.1rem;
-  color: #1a365d;
-  padding: 15px;
-  font-weight: 600;
-  background: linear-gradient(to right, rgba(248, 250, 252, 0.9), rgba(240, 248, 255, 0.9));
-  margin: 0;
-  border-top: 1px dashed #cbd5e0;
-  position: relative;
+  object-fit: cover;
+  transition: transform 0.4s ease;
+  background: rgba(245, 249, 255, 0.4);
 }
 
-.video-caption::before {
-  content: "📹";
-  position: absolute;
-  left: 20px;
-  top: 50%;
-  transform: translateY(-50%);
+.image-card:hover .sampling-image {
+  transform: scale(1.05);
 }
 
-/* 装饰元素 */
-.decorative-element {
-  position: absolute;
-  width: 100px;
-  height: 100px;
-  border-radius: 50%;
-  background: linear-gradient(135deg, rgba(74, 158, 247, 0.1), rgba(58, 207, 213, 0.1));
-  z-index: -1;
+.image-info {
+  padding: 20px;
 }
 
-.decorative-element:nth-child(1) {
-  top: 10%;
-  left: 5%;
-  width: 80px;
-  height: 80px;
+.image-info h3 {
+  color: #1a365d;
+  font-size: 1.25rem;
+  margin-top: 0;
+  margin-bottom: 10px;
 }
 
-.decorative-element:nth-child(2) {
-  bottom: 15%;
-  right: 8%;
-  width: 60px;
-  height: 60px;
+.image-info p {
+  font-size: 0.95rem;
+  color: #4a5568;
+  margin: 0;
+  line-height: 1.6;
 }
 
 /* 响应式设计 */
-@media (max-width: 900px) {
-  .sampling-process {
-    padding: 20px;
-  }
-  
-  .header-section h2 {
-    font-size: 1.8rem;
-  }
-  
-  .subtitle {
-    font-size: 1.1rem;
-  }
-  
-  .content-section {
-    padding: 20px;
-  }
-  
-  .sampling-image,
-  .sampling-video {
-    min-height: 300px;
+@media (max-width: 1100px) {
+  .image-gallery {
+    grid-template-columns: repeat(2, 1fr);
   }
 }
 
 @media (max-width: 768px) {
-  .sampling-process {
-    padding: 15px;
+  .image-gallery {
+    grid-template-columns: 1fr;
   }
   
-  .header-section h2 {
-    font-size: 1.6rem;
+  .section-container {
+    padding: 25px;
   }
   
-  p {
-    font-size: 1rem;
-    padding-left: 15px;
+  .section-number {
+    width: 45px;
+    height: 45px;
+    font-size: 1.6rem;
   }
   
-  .image-caption,
-  .video-caption {
-    font-size: 1rem;
-    padding: 12px;
+  h2 {
+    font-size: 1.7rem;
   }
   
-  .sampling-image,
-  .sampling-video {
-    min-height: 250px;
+  .description-card {
+    flex-direction: column;
+    align-items: center;
+    text-align: center;
   }
   
-  .video-title {
-    font-size: 1.3rem;
+  .icon {
+    margin-right: 0;
+    margin-bottom: 15px;
   }
 }
 
 @media (max-width: 480px) {
   .sampling-process {
-    padding: 10px;
-  }
-  
-  .header-section h2 {
-    font-size: 1.4rem;
-  }
-  
-  .subtitle {
-    font-size: 0.95rem;
-  }
-  
-  .content-section {
-    padding: 15px;
+    padding: 20px;
   }
   
-  p {
-    font-size: 0.95rem;
-    padding-left: 10px;
+  .section-container {
+    padding: 20px;
   }
   
-  .image-caption,
-  .video-caption {
-    font-size: 0.9rem;
-    padding: 10px 15px 10px 35px;
+  .section-header {
+    flex-direction: column;
+    align-items: flex-start;
   }
   
-  .image-caption::before,
-  .video-caption::before {
-    left: 10px;
+  .section-number {
+    margin-right: 0;
+    margin-bottom: 15px;
   }
   
-  .sampling-image,
-  .sampling-video {
-    min-height: 200px;
+  h2 {
+    font-size: 1.6rem;
   }
   
-  .video-title {
-    font-size: 1.1rem;
+  .sampling-image {
+    height: 200px;
   }
 }
 </style>

+ 146 - 286
src/views/User/hmInFlux/strawRemoval/samplingDesc2.vue

@@ -1,40 +1,42 @@
 <template>
   <div class="sampling-process">
-    <div class="header-section">
-      <h2>秸秆移除</h2>
-      <div class="subtitle">重金属在土壤-农作物系统中的迁移与输出机制</div>
-    </div>
-    
-    <div class="content-section">
-      <div class="text-content">
-        <p>
-          秸秆移除是重金属输出的重要方式之一。重金属在植物地上部分(包括秸秆)积累,其生物可利用性及在土壤-农作物系统中的迁移转化备受关注。秸秆作为作物残茬,在农田生态系统的重金属循环中扮演关键角色,同时也可能带来人体暴露风险。
-        </p>
-        <p>
-          图清晰地展示了秸秆移除过程中重金属输出的具体路径。如图所示,土壤中的重金属通过植物根系被吸收并转运至地上部分,最终富集于秸秆中。当秸秆被移除时,其中积累的重金属也随之离开农田系统,构成重要的重金属输出途径。图中通过不同颜色标注,展示了不同作物秸秆中重金属种类及含量的差异,进一步说明了在估算秸秆移除的重金属输出量时,必须收集秸秆样品进行处理和化学分析,并将其重金属含量纳入作物收获输出通量计算的必要性。
-        </p>
+    <!-- 秸秆移除部分 -->
+    <div class="section-container">
+      <div class="section-header">
+        <div class="section-number">1</div>
+        <h2>秸秆移除</h2>
       </div>
-
-      <div class="image-row">
-        <div class="image-container">
-          <el-image :src="image1" alt="秸秆移除是重金属输出的重要途径" class="sampling-image"></el-image>
-          <p class="image-caption">
-            秸秆移除是重金属输出的重要途径
-          </p>
+      
+      <div class="section-content">
+        <div class="description-card">
+          <div class="icon">🌿</div>
+          <div class="description-text">
+            <p>
+              移除秸秆是重金属输出的重要途径之一,这些重金属主要积累在植物的地上部分(包括秸秆)。
+            </p>
+            <p>
+              如图所示,土壤中的重金属通过植物根系被吸收并转运至地上部分,最终富集于秸秆中。当秸秆被移除时,其中积累的重金属也随之离开农田系统,构成重要的重金属输出途径。
+            </p>
+          </div>
         </div>
-      </div>
-
-       <!-- 新增视频区域 -->
-      <div class="video-section">
-        <h3 class="video-title">秸秆移除过程视频演示</h3>
-        <div class="video-container">
-          <video controls class="sampling-video">
-            <source src="@/assets/videos/秸秆移除和籽粒移除.mp4" type="video/mp4">
-            您的浏览器不支持HTML5视频播放。
-          </video>
-          <p class="video-caption">
-            视频展示了重金属在秸秆中的积累和移除过程
-          </p>
+        
+        <div class="image-gallery">
+          <div class="image-card">
+            <el-image :src="image1" alt="秸秆移除是重金属输出的重要途径" class="sampling-image"></el-image>
+            <div class="image-info">
+              <h3>秸秆移除过程</h3>
+            </div>
+          </div>
+          
+          <div class="image-card">
+            <video controls class="sampling-image">
+              <source src="@/assets/videos/秸秆移除和籽粒移除.mp4" type="video/mp4">
+              您的浏览器不支持HTML5视频播放。
+            </video>
+            <div class="image-info">
+              <h3>秸秆移除视频演示</h3>
+            </div>
+          </div>
         </div>
       </div>
     </div>
@@ -54,11 +56,10 @@ export default {
 <style scoped>
 .sampling-process {
   padding: 30px;
-  background: linear-gradient(135deg, rgba(230, 247, 255, 0.7) 0%, rgba(240, 248, 255, 0.7) 100%);
+  background: linear-gradient(135deg, rgba(230, 247, 255, 0.85) 0%, rgba(240, 248, 255, 0.85) 100%);
   position: relative;
   overflow: hidden;
-  border-radius: 16px;
-  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
+  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 }
 
 .sampling-process::before {
@@ -69,334 +70,193 @@ export default {
   width: 100%;
   height: 100%;
   background: url('https://images.unsplash.com/photo-1518834107812-67b0b7c58434?q=80&w=2070&auto=format&fit=crop') center/cover;
-  opacity: 0.15;
+  opacity: 0.12;
   z-index: -1;
   filter: blur(2px);
 }
 
-/* 标题区域 */
-.header-section {
-  text-align: center;
-  margin-bottom: 30px;
-  padding-bottom: 20px;
-  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
-}
-
-.header-section h2 {
-  position: relative;
-  padding-bottom: 15px;
-  margin-bottom: 10px;
-  color: #1a365d;
-  font-size: 2rem;
-  font-weight: 700;
-  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-}
-
-.header-section h2::after {
-  content: "";
-  position: absolute;
-  bottom: 0;
-  left: 50%;
-  transform: translateX(-50%);
-  width: 80px;
-  height: 4px;
-  background: linear-gradient(90deg, #4a9ef7, #3acfd5);
-  border-radius: 3px;
-}
-
-.subtitle {
-  font-size: 1.2rem;
-  color: #2d5986;
-  font-weight: 500;
-  letter-spacing: 0.5px;
-}
-
-/* 内容区域 */
-.content-section {
-  margin-bottom: 40px;
-  padding: 25px;
-  background: rgba(255, 255, 255, 0.85);
-  border-radius: 12px;
-  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
+.section-container {
+  margin-bottom: 50px;
+  padding: 30px;
+  border-radius: 18px;
+  background: rgba(255, 255, 255, 0.88);
+  box-shadow: 0 8px 25px rgba(0, 60, 120, 0.08);
   transition: all 0.4s ease;
   overflow: hidden;
+  position: relative;
 }
 
-.content-section:hover {
+.section-container:hover {
   transform: translateY(-5px);
-  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
-  background: rgba(255, 255, 255, 0.92);
-}
-
-.text-content {
-  margin-bottom: 30px;
+  box-shadow: 0 12px 35px rgba(0, 60, 120, 0.15);
 }
 
-p {
-  text-indent: 2em;
-  margin: 20px 0;
-  line-height: 1.8;
-  color: #2d3748;
-  font-size: 1.1rem;
+.section-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 25px;
   position: relative;
-  padding-left: 20px;
-}
-
-p::before {
-  content: "•";
-  position: absolute;
-  left: 0;
-  top: 0;
-  color: #3a9fd3;
-  font-size: 1.5rem;
-  line-height: 1.8;
+  padding-bottom: 15px;
+  border-bottom: 2px solid rgba(58, 160, 207, 0.25);
 }
 
-/* 图片区域 */
-.image-row {
+.section-number {
+  width: 50px;
+  height: 50px;
+  background: linear-gradient(135deg, #4a9ef7, #3a9fd3);
+  color: white;
+  border-radius: 50%;
   display: flex;
+  align-items: center;
   justify-content: center;
-  margin-bottom: 30px;
+  font-size: 1.8rem;
+  font-weight: bold;
+  margin-right: 20px;
+  box-shadow: 0 5px 12px rgba(74, 158, 247, 0.25);
 }
 
-.image-container {
-  border-radius: 12px;
-  overflow: hidden;
-  position: relative;
-  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
-  max-width: 800px;
-  margin: 0 auto;
-  transition: all 0.4s ease;
+h2 {
+  color: #1a365d;
+  font-size: 1.9rem;
+  margin: 0;
+  font-weight: 650;
+  letter-spacing: 0.5px;
 }
 
-.image-container:hover {
-  transform: scale(1.01);
-  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
+.section-content {
+  padding: 0 10px;
 }
 
-.sampling-image {
-  width: 100%;
-  height: auto;
-  min-height: 350px;
-  display: block;
-  background: rgba(245, 249, 255, 0.4);
-  object-fit: cover;
-  transition: transform 0.5s ease;
+.description-card {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 30px;
+  padding: 20px;
+  background: rgba(235, 245, 255, 0.6);
+  border-radius: 15px;
 }
 
-.image-container:hover .sampling-image {
-  transform: scale(1.02);
+.icon {
+  font-size: 2.5rem;
+  margin-right: 25px;
+  color: #3a9fd3;
 }
 
-.image-caption {
-  text-align: center;
+.description-text p {
   font-size: 1.1rem;
-  color: #1a365d;
-  padding: 15px;
-  font-weight: 600;
-  background: linear-gradient(to right, rgba(248, 250, 252, 0.9), rgba(240, 248, 255, 0.9));
+  line-height: 1.8;
+  color: #2d3748;
   margin: 0;
-  border-top: 1px dashed #cbd5e0;
-  position: relative;
-}
-
-.image-caption::before {
-  content: "📌";
-  position: absolute;
-  left: 20px;
-  top: 50%;
-  transform: translateY(-50%);
-}
-
-/* 视频区域 */
-.video-section {
-  margin-top: 40px;
-  padding-top: 30px;
-  border-top: 1px solid rgba(0, 0, 0, 0.1);
 }
 
-.video-title {
-  text-align: center;
-  color: #1a365d;
-  font-size: 1.5rem;
-  margin-bottom: 20px;
-  position: relative;
-  padding-bottom: 10px;
+/* 图片画廊样式 */
+.image-gallery {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 25px;
+  margin-top: 20px;
 }
 
-.video-title::after {
-  content: "";
-  position: absolute;
-  bottom: 0;
-  left: 50%;
-  transform: translateX(-50%);
-  width: 60px;
-  height: 3px;
-  background: linear-gradient(90deg, #4a9ef7, #3acfd5);
-  border-radius: 2px;
-}
-
-.video-container {
-  border-radius: 12px;
+.image-card {
+  border-radius: 15px;
   overflow: hidden;
-  position: relative;
-  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
-  max-width: 800px;
-  margin: 0 auto;
+  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1);
   transition: all 0.4s ease;
+  background: white;
 }
 
-.video-container:hover {
-  transform: translateY(-5px);
-  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
+.image-card:hover {
+  transform: translateY(-8px);
+  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
 }
 
-.sampling-video {
+.sampling-image {
   width: 100%;
-  height: auto;
+  height: 250px;
   display: block;
-  background: #f8fafc;
-  min-height: 350px;
-}
-
-.video-caption {
-  text-align: center;
-  font-size: 1.1rem;
-  color: #1a365d;
-  padding: 15px;
-  font-weight: 600;
-  background: linear-gradient(to right, rgba(248, 250, 252, 0.9), rgba(240, 248, 255, 0.9));
-  margin: 0;
-  border-top: 1px dashed #cbd5e0;
-  position: relative;
+  object-fit: cover;
+  transition: transform 0.4s ease;
+  background: rgba(245, 249, 255, 0.4);
 }
 
-.video-caption::before {
-  content: "📹";
-  position: absolute;
-  left: 20px;
-  top: 50%;
-  transform: translateY(-50%);
+.image-card:hover .sampling-image {
+  transform: scale(1.05);
 }
 
-/* 装饰元素 */
-.decorative-element {
-  position: absolute;
-  width: 100px;
-  height: 100px;
-  border-radius: 50%;
-  background: linear-gradient(135deg, rgba(74, 158, 247, 0.1), rgba(58, 207, 213, 0.1));
-  z-index: -1;
+.image-info {
+  padding: 20px;
 }
 
-.decorative-element:nth-child(1) {
-  top: 10%;
-  left: 5%;
-  width: 80px;
-  height: 80px;
-}
-
-.decorative-element:nth-child(2) {
-  bottom: 15%;
-  right: 8%;
-  width: 60px;
-  height: 60px;
+.image-info h3 {
+  color: #1a365d;
+  font-size: 1.25rem;
+  margin-top: 0;
+  margin-bottom: 10px;
 }
 
 /* 响应式设计 */
-@media (max-width: 900px) {
-  .sampling-process {
-    padding: 20px;
-  }
-  
-  .header-section h2 {
-    font-size: 1.8rem;
-  }
-  
-  .subtitle {
-    font-size: 1.1rem;
-  }
-  
-  .content-section {
-    padding: 20px;
-  }
-  
-  .sampling-image,
-  .sampling-video {
-    min-height: 300px;
+@media (max-width: 1100px) {
+  .image-gallery {
+    grid-template-columns: repeat(2, 1fr);
   }
 }
 
 @media (max-width: 768px) {
-  .sampling-process {
-    padding: 15px;
+  .image-gallery {
+    grid-template-columns: 1fr;
   }
   
-  .header-section h2 {
-    font-size: 1.6rem;
+  .section-container {
+    padding: 25px;
   }
   
-  p {
-    font-size: 1rem;
-    padding-left: 15px;
+  .section-number {
+    width: 45px;
+    height: 45px;
+    font-size: 1.6rem;
   }
   
-  .image-caption,
-  .video-caption {
-    font-size: 1rem;
-    padding: 12px;
+  h2 {
+    font-size: 1.7rem;
   }
   
-  .sampling-image,
-  .sampling-video {
-    min-height: 250px;
+  .description-card {
+    flex-direction: column;
+    align-items: center;
+    text-align: center;
   }
   
-  .video-title {
-    font-size: 1.3rem;
+  .icon {
+    margin-right: 0;
+    margin-bottom: 15px;
   }
 }
 
 @media (max-width: 480px) {
   .sampling-process {
-    padding: 10px;
-  }
-  
-  .header-section h2 {
-    font-size: 1.4rem;
-  }
-  
-  .subtitle {
-    font-size: 0.95rem;
-  }
-  
-  .content-section {
-    padding: 15px;
+    padding: 20px;
   }
   
-  p {
-    font-size: 0.95rem;
-    padding-left: 10px;
+  .section-container {
+    padding: 20px;
   }
   
-  .image-caption,
-  .video-caption {
-    font-size: 0.9rem;
-    padding: 10px 15px 10px 35px;
+  .section-header {
+    flex-direction: column;
+    align-items: flex-start;
   }
   
-  .image-caption::before,
-  .video-caption::before {
-    left: 10px;
+  .section-number {
+    margin-right: 0;
+    margin-bottom: 15px;
   }
   
-  .sampling-image,
-  .sampling-video {
-    min-height: 200px;
+  h2 {
+    font-size: 1.6rem;
   }
   
-  .video-title {
-    font-size: 1.1rem;
+  .sampling-image {
+    height: 200px;
   }
 }
 </style>

+ 251 - 261
src/views/User/hmInFlux/strawRemoval/strawRemovalInputFlux.vue

@@ -1,124 +1,107 @@
 <template>
-  <div class="container">
-    <div class="gradient-card">
-      <div class="card-header">
-        <h1>秸秆移除输出通量计算结果</h1>
-        <p>{{ area }}地区作物秸秆移除Cd通量分析报告</p>
-      </div>
-
-      <div v-if="loading" class="loading-section">
-        <i class="fas fa-spinner fa-spin"></i> 正在加载数据...
-      </div>
-
-      <div v-if="error" class="error-section">
-        <i class="fas fa-exclamation-triangle"></i> {{ error }}
-      </div>
-
-      <template v-if="!loading && !error">
-        <div class="statistics-section">
-          <h2><i class="fas fa-chart-bar"></i> 统计分析</h2>
-          <div class="stats-grid">
-            <div class="stat-card">
-              <div class="stat-value">{{ statistics.total_samples.toLocaleString() }}</div>
-              <div class="stat-label">总样本数</div>
+  <div class="agricultural-input-management">
+    <!-- 直接展示结果页面 -->
+    <div class="page-container">
+      <el-card class="results-card">
+        <div class="results-content">
+          <!-- 地图区域 -->
+          <div class="map-section">
+            <h3>秸秆移除Cd通量分布图</h3>
+            <div v-if="loading" class="loading-container">
+              <el-icon class="loading-icon"><Loading /></el-icon>
+              <span>数据加载中...</span>
             </div>
-            <div class="stat-card">
-              <div class="stat-value">{{ statistics.mean_flux.toFixed(2) }} g/ha/a</div>
-              <div class="stat-label">平均通量</div>
+            <div v-else-if="error" class="error-container">
+              <el-icon class="error-icon"><Warning /></el-icon>
+              <span>{{ error }}</span>
             </div>
-            <div class="stat-card">
-              <div class="stat-value">{{ statistics.max_flux.toFixed(2) }} g/ha/a</div>
-              <div class="stat-label">最大通量</div>
+            <div v-else class="image-container">
+              <img :src="visualizationImage" class="result-image"></img>
             </div>
-            <div class="stat-card">
-              <div class="stat-value">{{ statistics.min_flux.toFixed(2) }} g/ha/a</div>
-              <div class="stat-label">最小通量</div>
-            </div>
-          </div>
-        </div>
-
-        <div class="visualization-section">
-          <h2><i class="fas fa-map"></i> 可视化分析图</h2>
-          <img v-if="visualizationImage" :src="visualizationImage" alt="Cd含量地图" class="result-image">
-          <div v-if="visualizationError" class="error-section">
-            <i class="fas fa-exclamation-triangle"></i> {{ visualizationError }}
           </div>
-          <div v-else class="image-container">
-            
-          </div>
-        </div>
-
-        <div class="data-section">
-          <h2><i class="fas fa-table"></i> 详细数据</h2>
-          <el-table
-            :data="tableData"
-            height="400"
-            style="width: 100%"
-            class="custom-table"
-            :header-cell-style="{ background: 'rgba(0, 150, 136, 0.1)', color: '#006064', fontWeight: '600' }"
-          >
-            <el-table-column prop="farmland_id" label="农田ID" width="120" align="center"></el-table-column>
-            <el-table-column prop="sample_id" label="样本ID" width="120" align="center"></el-table-column>
-            <el-table-column prop="crop_cd_value" label="作物镉Cd(mg/kg)" align="center">
-              <template #default="scope">
-                {{ scope.row.crop_cd_value.toFixed(5) }}
-              </template>
-            </el-table-column>
-            <el-table-column prop="f11_yield" label="作物亩产量(斤)" width="150" align="center"></el-table-column>
-            <el-table-column prop="straw_removal_flux" label="秸秆移除通量(g/ha/a)" width="170" align="center">
-              <template #default="scope">
-                {{ scope.row.straw_removal_flux.toFixed(5) }}
-              </template>
-            </el-table-column>
-          </el-table>
           
-          <el-pagination
-            background
-            layout="prev, pager, next, sizes, total"
-            :total="results.length"
-            :page-size="pageSize"
-            :current-page="currentPage"
-            :page-sizes="[5, 10, 20, 50]"
-            @size-change="handleSizeChange"
-            @current-change="handleCurrentChange"
-            class="custom-pagination"
-          >
-          </el-pagination>
-        </div>
-
-        <div class="formula-section">
-          <h2><i class="fas fa-calculator"></i> 计算公式</h2>
-          <div class="formula-card">
-            {{ formula }}
-          </div>
-          <div class="unit-note">
-            <i class="fas fa-info-circle"></i> 单位: {{ unit }}
+          <!-- 统计图表区域 -->
+          <div class="stats-area">
+            <h3>秸秆移除Cd通量统计信息</h3>
+            <div class="model-info">
+              <el-tag type="info">秸秆移除Cd通量模型</el-tag>
+              <span class="update-time">
+                最后更新: {{ reportTime }}
+              </span>
+            </div>
+            
+            <div v-if="loading" class="loading-container">
+              <el-icon class="loading-icon"><Loading /></el-icon>
+              <span>统计数据加载中...</span>
+            </div>
+            
+            <div v-else-if="error" class="error-container">
+              <el-icon class="error-icon"><Warning /></el-icon>
+              <span>{{ error }}</span>
+            </div>
+            
+            <div v-else class="stats-container">
+              <div class="stats-grid">
+                <div class="stat-card">
+                  <div class="stat-value">{{ statistics.total_samples.toLocaleString() }}</div>
+                  <div class="stat-label">总样本数</div>
+                </div>
+                <div class="stat-card">
+                  <div class="stat-value">{{ statistics.mean_flux.toFixed(4) }}</div>
+                  <div class="stat-label">平均通量</div>
+                </div>
+                <div class="stat-card">
+                  <div class="stat-value">{{ statistics.max_flux.toFixed(4) }}</div>
+                  <div class="stat-label">最大通量</div>
+                </div>
+                <div class="stat-card">
+                  <div class="stat-value">{{ statistics.min_flux.toFixed(4) }}</div>
+                  <div class="stat-label">最小通量</div>
+                </div>
+              </div>
+            </div>
           </div>
         </div>
-
-        <div class="card-footer">
-          <p><i class="fas fa-clock"></i> 报告生成时间: {{ reportTime }}</p>
-        </div>
-      </template>
+      </el-card>
     </div>
   </div>
 </template>
 
 <script>
-import { ref, computed, onMounted } from 'vue';
-import { ElTable, ElTableColumn, ElPagination } from 'element-plus';
+import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
+import { 
+  ElButton, 
+  ElCard, 
+  ElTable, 
+  ElTableColumn,
+  ElTag,
+  ElIcon,
+  ElPagination
+} from 'element-plus';
+import { 
+  Loading, 
+  Picture, 
+  Warning 
+} from '@element-plus/icons-vue';
+import { api8000 } from '@/utils/request';
 
 export default {
-  name: 'StrawRemovalResult',
   components: {
+    ElButton,
+    ElCard,
     ElTable,
     ElTableColumn,
-    ElPagination
+    ElTag,
+    ElIcon,
+    ElPagination,
+    Loading,
+    Picture,
+    Warning
   },
   props: {
     area: {
       type: String,
-      default: '韶关'
+      default: '乐昌市'
     }
   },
   setup(props) {
@@ -161,40 +144,38 @@ export default {
     // 从API获取数据
     const fetchData = async () => {
       try {
+        loading.value = true;
+        error.value = null;
+        
         // 获取计算结果数据
-        const dataResponse = await fetch(
-          `http://127.0.0.1:8000/api/cd-flux-removal/straw-removal?area=${encodeURIComponent(props.area)}`
+        const dataResponse = await api8000.get(
+          `/api/cd-flux-removal/straw-removal?area=${encodeURIComponent(props.area)}`
         );
         
-        if (!dataResponse.ok) {
-          throw new Error(`数据获取失败: ${dataResponse.statusText}`);
-        }
-        
-        const dataJson = await dataResponse.json();
-        
-        if (!dataJson.success) {
-          throw new Error(`API错误: ${dataJson.message}`);
+        if (!dataResponse.data.success) {
+          throw new Error(`API错误: ${dataResponse.data.message}`);
         }
         
         // 设置数据
-        results.value = dataJson.data.results;
-        statistics.value = dataJson.data.statistics;
-        formula.value = dataJson.data.formula;
-        unit.value = dataJson.data.unit;
+        results.value = dataResponse.data.data.results;
+        statistics.value = dataResponse.data.data.statistics;
+        formula.value = dataResponse.data.data.formula;
+        unit.value = dataResponse.data.data.unit;
         
         // 获取可视化图片
-        const imageResponse = await fetch(
-          `http://127.0.0.1:8000/api/cd-flux-removal/straw-removal/visualize?area=${encodeURIComponent(props.area)}&level=city`
-        );
-        
-        if (!imageResponse.ok) {
-          throw new Error(`图片获取失败: ${imageResponse.statusText}`);
+        try {
+          const imageResponse = await api8000.get(
+            `/api/cd-flux-removal/straw-removal/visualize?area=${encodeURIComponent(props.area)}&level=county`,
+            { responseType: 'blob' }
+          );
+          
+          // 创建图片Blob URL
+          const blob = new Blob([imageResponse.data], { type: imageResponse.headers['content-type'] });
+          visualizationImage.value = URL.createObjectURL(blob);
+        } catch (imgErr) {
+          console.error('图片加载错误:', imgErr);
         }
         
-        // 创建图片Blob URL
-        const blob = await imageResponse.blob();
-        visualizationImage.value = URL.createObjectURL(blob);
-        
         // 设置报告时间
         reportTime.value = new Date().toLocaleString('zh-CN', {
           year: 'numeric',
@@ -217,6 +198,12 @@ export default {
       fetchData();
     });
     
+    onBeforeUnmount(() => {
+      if (visualizationImage.value) {
+        URL.revokeObjectURL(visualizationImage.value);
+      }
+    });
+    
     return {
       results,
       statistics,
@@ -237,76 +224,97 @@ export default {
 </script>
 
 <style scoped>
-/* 保留您原有的样式,只添加Element UI的自定义样式 */
-.container {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  min-height: 100vh;
+/* 整体布局优化 - 与农业投入品保持一致 */
+.agricultural-input-management {
   padding: 20px;
-  background: linear-gradient(135deg, #e6f7ff, #b3e0ff);
+  background: linear-gradient(
+    135deg, 
+    rgba(230, 247, 255, 0.7) 0%, 
+    rgba(240, 248, 255, 0.7) 100%
+  );
+  min-height: 100vh;
+  box-sizing: border-box;
 }
 
-.gradient-card {
-  background: linear-gradient(135deg,
-      rgba(250, 253, 255, 0.9),
-      rgba(220, 240, 255, 0.9));
-  border-radius: 20px;
-  box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
-  padding: 30px;
+.page-container {
   width: 100%;
-  max-width: 1000px;
-  backdrop-filter: blur(8px);
+  height: 100%;
+}
+
+/* 工具栏样式 */
+.toolbar {
+  display: flex;
+  justify-content: flex-start;
+  gap: 15px;
+  margin-bottom: 20px;
+  padding: 15px;
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(5px);
+}
+
+.custom-button {
+  background-color: #47C3B9 !important;
+  color: #DCFFFA !important;
   border: none;
+  border-radius: 155px;
+  padding: 10px 20px;
+  font-weight: bold;
   display: flex;
-  flex-direction: column;
-  overflow: hidden;
+  align-items: center;
 }
 
-.card-header {
-  text-align: center;
-  margin-bottom: 30px;
-  border-bottom: 2px solid rgba(0, 96, 100, 0.1);
-  padding-bottom: 20px;
+/* 结果卡片样式 */
+.results-card {
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 8px;
+  padding: 20px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(5px);
+  height: 100%;
+  box-sizing: border-box;
 }
 
-.card-header h1 {
-  font-size: 2.2rem;
-  color: #006064;
-  margin-bottom: 10px;
-  font-weight: 700;
+.results-content {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
 }
 
-.card-header p {
-  font-size: 1.2rem;
-  color: #00838f;
-  opacity: 0.9;
+.map-section, .stats-area {
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 8px;
+  padding: 20px;
+  margin-bottom: 20px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
 }
 
-.loading-section, .error-section {
-  text-align: center;
-  padding: 40px;
-  font-size: 1.4rem;
-  color: #006064;
+h3 {
+  margin-bottom: 15px;
+  color: #333;
+  font-size: 18px;
+  font-weight: 600;
 }
 
-.loading-section i {
-  margin-right: 15px;
-  font-size: 1.8rem;
-  color: #00838f;
+.model-info {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+ margin-bottom: 15px;
 }
 
-.error-section i {
-  margin-right: 15px;
-  font-size: 1.8rem;
-  color: #d32f2f;
+.update-time {
+  color: #666;
+  font-size: 14px;
 }
 
+/* 统计卡片样式 */
 .stats-grid {
   display: grid;
   grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
   gap: 20px;
-  margin-top: 20px;
+  margin-bottom: 20px;
 }
 
 .stat-card {
@@ -315,7 +323,7 @@ export default {
   padding: 20px;
   text-align: center;
   box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
-  transition: transform 0.3s ease;
+  transition: all 0.3s ease;
 }
 
 .stat-card:hover {
@@ -335,90 +343,104 @@ export default {
   color: #00838f;
 }
 
-.visualization-section, 
-.data-section, 
-.formula-section,
-.statistics-section {
-  margin-bottom: 30px;
-  padding: 20px;
-  background: rgba(255, 255, 255, 0.6);
-  border-radius: 15px;
-  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
-}
-
-.visualization-section h2,
-.data-section h2,
-.formula-section h2,
-.statistics-section h2 {
-  font-size: 1.6rem;
-  color: #006064;
-  margin-bottom: 20px;
+/* 图片容器限定大小 */
+.image-container {
+  width: 100%;
+  height: 500px;
   display: flex;
+  justify-content: center;
   align-items: center;
-  gap: 10px;
+  background-color: #f9f9f9;
+  border-radius: 8px;
+  overflow: hidden;
 }
 
 .result-image {
   max-width: 100%;
-  max-height: 500px;
-  border-radius: 10px;
-  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
+  max-height: 100%;
+  object-fit: contain;
 }
 
-.formula-card {
-  font-family: 'Courier New', monospace;
-  font-size: 1.2rem;
-  background: rgba(255, 255, 255, 0.8);
-  padding: 20px;
-  border-radius: 10px;
-  text-align: center;
-  margin: 20px 0;
-  border: 1px dashed #00838f;
-  color: #006064;
+.loading-container, .error-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 300px;
+  gap: 15px;
 }
 
-.unit-note {
-  text-align: center;
-  font-size: 1.1rem;
-  color: #00838f;
+.loading-container {
+  color: #47C3B9;
+}
+
+.error-container {
+  color: #F56C6C;
+}
+
+.loading-icon {
+  font-size: 36px;
+  margin-bottom: 10px;
+  animation: rotate 2s linear infinite;
+}
+
+.error-icon {
+  font-size: 36px;
+  margin-bottom: 10px;
+}
+
+.no-data {
   display: flex;
+  flex-direction: column;
   align-items: center;
   justify-content: center;
-  gap: 8px;
+  height: 300px;
+  color: #999;
+  font-size: 16px;
 }
 
-.card-footer {
-  text-align: center;
-  font-size: 1rem;
-  color: #00838f;
-  padding-top: 20px;
-  border-top: 1px solid rgba(0, 150, 136, 0.2);
+.no-data .el-icon {
+  font-size: 48px;
+  margin-bottom: 10px;
+}
+
+.details-table {
+  margin-top: 20px;
+}
+
+.custom-pagination {
+  margin-top: 20px;
   display: flex;
-  align-items: center;
   justify-content: center;
-  gap: 10px;
+}
+
+@keyframes rotate {
+  from { transform: rotate(0deg); }
+  to { transform: rotate(360deg); }
 }
 
 /* 响应式设计 */
 @media (max-width: 768px) {
-  .stats-grid {
-    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+  .toolbar {
+    flex-direction: column;
+    align-items: stretch;
   }
   
-  .card-header h1 {
-    font-size: 1.8rem;
+  .stats-grid {
+    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
   }
   
-  .visualization-section h2,
-  .data-section h2,
-  .formula-section h2,
-  .statistics-section h2 {
-    font-size: 1.4rem;
+  .image-container {
+    height: 300px;
   }
 }
 
 @media (max-width: 480px) {
-  .gradient-card {
+  .agricultural-input-management {
+    padding: 10px;
+  }
+  
+  .results-card {
     padding: 15px;
   }
   
@@ -430,44 +452,12 @@ export default {
     padding: 15px;
   }
   
+  .image-container {
+    height: 250px;
+  }
+  
   .stat-value {
     font-size: 1.5rem;
   }
 }
-
-.result-image {
-  width: 100%;
-  max-height: 400px;
-  object-fit: contain;
-  border-radius: 8px;
-  border: 1px solid #e4e7ed;
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
-  background-color: #f8f8f8;
-}
-
-/* Element UI 自定义样式 */
-.custom-table {
-  border-radius: 10px;
-  overflow: hidden;
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
-  margin-top: 20px;
-}
-
-.custom-table .el-table__row {
-  transition: background-color 0.3s;
-}
-
-.custom-table .el-table__row:hover {
-  background-color: rgba(178, 235, 242, 0.3) !important;
-}
-
-.custom-table .el-table__row:nth-child(even) {
-  background-color: rgba(178, 235, 242, 0.15);
-}
-
-.custom-pagination {
-  margin-top: 20px;
-  display: flex;
-  justify-content: center;
-}
 </style>

+ 146 - 285
src/views/User/hmInFlux/subsurfaceLeakage/samplingDesc3.vue

@@ -1,42 +1,45 @@
 <template>
   <div class="sampling-process">
-    <div class="header-section">
-      <h2>土壤渗流</h2>
-      <div class="subtitle">重金属在土壤-农作物系统中的迁移与输出机制</div>
-    </div>
-    
-    <div class="content-section">
-      <div class="text-content">
+    <!-- 地下渗漏部分 -->
+    <div class="section-container">
+      <div class="section-header">
+        <div class="section-number">1</div>
+        <h2>地下渗漏</h2>
+      </div>
+      
+      <div class="section-content">
+        <div class="description-card">
+          <div class="icon">💧</div>
+          <div class="description-text">
             <p>
-              土壤渗流是农田土壤重金属输出的重要途径,过度耕种会加剧土壤水分和营养的流失,而降水会对这一过程产生影响。通常,土壤重金属渗流通量通过质量平衡模型间接计算,该模型假设土壤中未渗流的重金属通过径流途径输出。
+              地下渗漏是农田土壤重金属输出的重要途径
             </p>
-             <p>
-              图详细展示了土壤渗流作为重金属纵向迁移重要途径的过程。如图所示,土壤中的重金属在降水和重力作用下,通过土壤孔隙向下渗透,最终进入地下水系统。图中左侧部分清晰地描绘了农田土壤剖面,包括表层土壤、根系分布区以及下方的地下水层。土壤中的水分和溶解态重金属随着水流向下移动,形成明显的渗流路径。
+            <p>
+              如图所示,土壤中的重金属在降水和重力作用下,通过土壤孔隙向下渗透,最终进入地下水系统。
             </p>
-      </div>
-    </div>
-
-    <div class="image-row">
-      <div class="image-container">
-        <el-image :src="image3" alt="土壤渗流是重金属迁移的重要途径" class="sampling-image"></el-image>
-        <p class="image-caption">
-          土壤渗流是重金属迁移的重要途径
-        </p>
-      </div>
-    </div>
-     <!-- 新增视频区域 -->
-      <div class="video-section">
-        <h3 class="video-title">土壤渗流过程视频演示</h3>
-        <div class="video-container">
-          <video controls class="sampling-video">
-            <source src="@/assets/videos/地下渗漏.mp4" type="video/mp4">
-            您的浏览器不支持HTML5视频播放。
-          </video>
-          <p class="video-caption">
-            视频展示了重金属在地下中的积累过程
-          </p>
+          </div>
+        </div>
+        
+        <div class="image-gallery">
+          <div class="image-card">
+            <el-image :src="image3" alt="地下渗漏是重金属迁移的重要途径" class="sampling-image"></el-image>
+            <div class="image-info">
+              <h3>地下渗漏过程</h3>
+            </div>
+          </div>
+          
+          <div class="image-card">
+            <video controls class="sampling-image">
+              <source src="@/assets/videos/地下渗漏.mp4" type="video/mp4">
+              您的浏览器不支持HTML5视频播放。
+            </video>
+            <div class="image-info">
+              <h3>地下渗漏视频演示</h3>
+            </div>
+          </div>
         </div>
       </div>
+    </div>
   </div>
 </template>
 
@@ -53,11 +56,10 @@ export default {
 <style scoped>
 .sampling-process {
   padding: 30px;
-  background: linear-gradient(135deg, rgba(230, 247, 255, 0.7) 0%, rgba(240, 248, 255, 0.7) 100%);
+  background: linear-gradient(135deg, rgba(230, 247, 255, 0.85) 0%, rgba(240, 248, 255, 0.85) 100%);
   position: relative;
   overflow: hidden;
-  border-radius: 16px;
-  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
+  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 }
 
 .sampling-process::before {
@@ -68,334 +70,193 @@ export default {
   width: 100%;
   height: 100%;
   background: url('https://images.unsplash.com/photo-1518834107812-67b0b7c58434?q=80&w=2070&auto=format&fit=crop') center/cover;
-  opacity: 0.15;
+  opacity: 0.12;
   z-index: -1;
   filter: blur(2px);
 }
 
-/* 标题区域 */
-.header-section {
-  text-align: center;
-  margin-bottom: 30px;
-  padding-bottom: 20px;
-  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
-}
-
-.header-section h2 {
-  position: relative;
-  padding-bottom: 15px;
-  margin-bottom: 10px;
-  color: #1a365d;
-  font-size: 2rem;
-  font-weight: 700;
-  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-}
-
-.header-section h2::after {
-  content: "";
-  position: absolute;
-  bottom: 0;
-  left: 50%;
-  transform: translateX(-50%);
-  width: 80px;
-  height: 4px;
-  background: linear-gradient(90deg, #4a9ef7, #3acfd5);
-  border-radius: 3px;
-}
-
-.subtitle {
-  font-size: 1.2rem;
-  color: #2d5986;
-  font-weight: 500;
-  letter-spacing: 0.5px;
-}
-
-/* 内容区域 */
-.content-section {
-  margin-bottom: 40px;
-  padding: 25px;
-  background: rgba(255, 255, 255, 0.85);
-  border-radius: 12px;
-  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
+.section-container {
+  margin-bottom: 50px;
+  padding: 30px;
+  border-radius: 18px;
+  background: rgba(255, 255, 255, 0.88);
+  box-shadow: 0 8px 25px rgba(0, 60, 120, 0.08);
   transition: all 0.4s ease;
   overflow: hidden;
+  position: relative;
 }
 
-.content-section:hover {
+.section-container:hover {
   transform: translateY(-5px);
-  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
-  background: rgba(255, 255, 255, 0.92);
-}
-
-.text-content {
-  margin-bottom: 30px;
+  box-shadow: 0 12px 35px rgba(0, 60, 120, 0.15);
 }
 
-p {
-  text-indent: 2em;
-  margin: 20px 0;
-  line-height: 1.8;
-  color: #2d3748;
-  font-size: 1.1rem;
+.section-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 25px;
   position: relative;
-  padding-left: 20px;
-}
-
-p::before {
-  content: "•";
-  position: absolute;
-  left: 0;
-  top: 0;
-  color: #3a9fd3;
-  font-size: 1.5rem;
-  line-height: 1.8;
+  padding-bottom: 15px;
+  border-bottom: 2px solid rgba(58, 160, 207, 0.25);
 }
 
-/* 图片区域 */
-.image-row {
+.section-number {
+  width: 50px;
+  height: 50px;
+  background: linear-gradient(135deg, #4a9ef7, #3a9fd3);
+  color: white;
+  border-radius: 50%;
   display: flex;
+  align-items: center;
   justify-content: center;
-  margin-bottom: 30px;
+  font-size: 1.8rem;
+  font-weight: bold;
+  margin-right: 20px;
+  box-shadow: 0 5px 12px rgba(74, 158, 247, 0.25);
 }
 
-.image-container {
-  border-radius: 12px;
-  overflow: hidden;
-  position: relative;
-  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
-  max-width: 800px;
-  margin: 0 auto;
-  transition: all 0.4s ease;
+h2 {
+  color: #1a365d;
+  font-size: 1.9rem;
+  margin: 0;
+  font-weight: 650;
+  letter-spacing: 0.5px;
 }
 
-.image-container:hover {
-  transform: scale(1.01);
-  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
+.section-content {
+  padding: 0 10px;
 }
 
-.sampling-image {
-  width: 100%;
-  height: auto;
-  min-height: 350px;
-  display: block;
-  background: rgba(245, 249, 255, 0.4);
-  object-fit: cover;
-  transition: transform 0.5s ease;
+.description-card {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 30px;
+  padding: 20px;
+  background: rgba(235, 245, 255, 0.6);
+  border-radius: 15px;
 }
 
-.image-container:hover .sampling-image {
-  transform: scale(1.02);
+.icon {
+  font-size: 2.5rem;
+  margin-right: 25px;
+  color: #3a9fd3;
 }
 
-.image-caption {
-  text-align: center;
+.description-text p {
   font-size: 1.1rem;
-  color: #1a365d;
-  padding: 15px;
-  font-weight: 600;
-  background: linear-gradient(to right, rgba(248, 250, 252, 0.9), rgba(240, 248, 255, 0.9));
+  line-height: 1.8;
+  color: #2d3748;
   margin: 0;
-  border-top: 1px dashed #cbd5e0;
-  position: relative;
-}
-
-.image-caption::before {
-  content: "📌";
-  position: absolute;
-  left: 20px;
-  top: 50%;
-  transform: translateY(-50%);
 }
 
-/* 视频区域 */
-.video-section {
-  margin-top: 40px;
-  padding-top: 30px;
-  border-top: 1px solid rgba(0, 0, 0, 0.1);
+/* 图片画廊样式 */
+.image-gallery {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 25px;
+  margin-top: 20px;
 }
 
-.video-title {
-  text-align: center;
-  color: #1a365d;
-  font-size: 1.5rem;
-  margin-bottom: 20px;
-  position: relative;
-  padding-bottom: 10px;
-}
-
-.video-title::after {
-  content: "";
-  position: absolute;
-  bottom: 0;
-  left: 50%;
-  transform: translateX(-50%);
-  width: 60px;
-  height: 3px;
-  background: linear-gradient(90deg, #4a9ef7, #3acfd5);
-  border-radius: 2px;
-}
-
-.video-container {
-  border-radius: 12px;
+.image-card {
+  border-radius: 15px;
   overflow: hidden;
-  position: relative;
-  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
-  max-width: 800px;
-  margin: 0 auto;
+  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1);
   transition: all 0.4s ease;
+  background: white;
 }
 
-.video-container:hover {
-  transform: translateY(-5px);
-  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
+.image-card:hover {
+  transform: translateY(-8px);
+  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
 }
 
-.sampling-video {
+.sampling-image {
   width: 100%;
-  height: auto;
+  height: 250px;
   display: block;
-  background: #f8fafc;
-  min-height: 350px;
-}
-
-.video-caption {
-  text-align: center;
-  font-size: 1.1rem;
-  color: #1a365d;
-  padding: 15px;
-  font-weight: 600;
-  background: linear-gradient(to right, rgba(248, 250, 252, 0.9), rgba(240, 248, 255, 0.9));
-  margin: 0;
-  border-top: 1px dashed #cbd5e0;
-  position: relative;
-}
-
-.video-caption::before {
-  content: "📹";
-  position: absolute;
-  left: 20px;
-  top: 50%;
-  transform: translateY(-50%);
+  object-fit: cover;
+  transition: transform 0.4s ease;
+  background: rgba(245, 249, 255, 0.4);
 }
 
-/* 装饰元素 */
-.decorative-element {
-  position: absolute;
-  width: 100px;
-  height: 100px;
-  border-radius: 50%;
-  background: linear-gradient(135deg, rgba(74, 158, 247, 0.1), rgba(58, 207, 213, 0.1));
-  z-index: -1;
+.image-card:hover .sampling-image {
+  transform: scale(1.05);
 }
 
-.decorative-element:nth-child(1) {
-  top: 10%;
-  left: 5%;
-  width: 80px;
-  height: 80px;
+.image-info {
+  padding: 20px;
 }
 
-.decorative-element:nth-child(2) {
-  bottom: 15%;
-  right: 8%;
-  width: 60px;
-  height: 60px;
+.image-info h3 {
+  color: #1a365d;
+  font-size: 1.25rem;
+  margin-top: 0;
+  margin-bottom: 10px;
 }
 
 /* 响应式设计 */
-@media (max-width: 900px) {
-  .sampling-process {
-    padding: 20px;
-  }
-  
-  .header-section h2 {
-    font-size: 1.8rem;
-  }
-  
-  .subtitle {
-    font-size: 1.1rem;
-  }
-  
-  .content-section {
-    padding: 20px;
-  }
-  
-  .sampling-image,
-  .sampling-video {
-    min-height: 300px;
+@media (max-width: 1100px) {
+  .image-gallery {
+    grid-template-columns: repeat(2, 1fr);
   }
 }
 
 @media (max-width: 768px) {
-  .sampling-process {
-    padding: 15px;
+  .image-gallery {
+    grid-template-columns: 1fr;
   }
   
-  .header-section h2 {
-    font-size: 1.6rem;
+  .section-container {
+    padding: 25px;
   }
   
-  p {
-    font-size: 1rem;
-    padding-left: 15px;
+  .section-number {
+    width: 45px;
+    height: 45px;
+    font-size: 1.6rem;
   }
   
-  .image-caption,
-  .video-caption {
-    font-size: 1rem;
-    padding: 12px;
+  h2 {
+    font-size: 1.7rem;
   }
   
-  .sampling-image,
-  .sampling-video {
-    min-height: 250px;
+  .description-card {
+    flex-direction: column;
+    align-items: center;
+    text-align: center;
   }
   
-  .video-title {
-    font-size: 1.3rem;
+  .icon {
+    margin-right: 0;
+    margin-bottom: 15px;
   }
 }
 
 @media (max-width: 480px) {
   .sampling-process {
-    padding: 10px;
-  }
-  
-  .header-section h2 {
-    font-size: 1.4rem;
-  }
-  
-  .subtitle {
-    font-size: 0.95rem;
-  }
-  
-  .content-section {
-    padding: 15px;
+    padding: 20px;
   }
   
-  p {
-    font-size: 0.95rem;
-    padding-left: 10px;
+  .section-container {
+    padding: 20px;
   }
   
-  .image-caption,
-  .video-caption {
-    font-size: 0.9rem;
-    padding: 10px 15px 10px 35px;
+  .section-header {
+    flex-direction: column;
+    align-items: flex-start;
   }
   
-  .image-caption::before,
-  .video-caption::before {
-    left: 10px;
+  .section-number {
+    margin-right: 0;
+    margin-bottom: 15px;
   }
   
-  .sampling-image,
-  .sampling-video {
-    min-height: 200px;
+  h2 {
+    font-size: 1.6rem;
   }
   
-  .video-title {
-    font-size: 1.1rem;
+  .sampling-image {
+    height: 200px;
   }
 }
 </style>

+ 118 - 241
src/views/User/hmInFlux/subsurfaceLeakage/subsurfaceLeakageInputFlux.vue

@@ -1,47 +1,48 @@
 <template>
-  <div class="leakage-flux-container">
-    <div class="map-title">地下渗漏Cd通量分布图</div>
-    <div class="map-content">
-      <div class="map-image-container">
-        <!-- 加载状态 -->
-        <div v-if="isLoading" class="loading-overlay">
-          <div class="spinner"></div>
-          <p>地图加载中...</p>
+  <div class="agricultural-input-management">
+    <div class="page-container">
+      <el-card class="results-card">
+        <div class="results-content">
+          <!-- 地图区域 -->
+          <div class="map-section">
+            <h3>地下渗漏Cd通量分布图</h3>
+            <div v-if="isLoading" class="loading-container">
+              <el-icon class="loading-icon"><Loading /></el-icon>
+              <span>数据加载中...</span>
+            </div>
+            <div v-else-if="isError" class="error-container">
+              <el-icon class="error-icon"><Warning /></el-icon>
+              <span>{{ errorMessage }}</span>
+              <el-button class="retry-btn" @click="fetchMap">
+                <el-icon><Refresh /></el-icon> 重新加载
+              </el-button>
+            </div>
+            <div v-else class="image-container">
+              <img :src="mapImageUrl" class="result-image"></img>
+              </div>
+          </div>
         </div>
-        
-        <!-- 错误状态 -->
-        <div v-if="isError" class="error-overlay">
-          <i class="fas fa-exclamation-triangle"></i>
-          <p>{{ errorMessage }}</p>
-          <button class="retry-btn" @click="fetchMap">
-            <i class="fas fa-redo"></i> 重新加载
-          </button>
-        </div>
-        
-        <!-- 地图图片 -->
-        <img 
-          v-if="mapImageUrl"
-          :src="mapImageUrl" 
-          alt="地下渗漏Cd通量分布图" 
-          class="map-image"
-          @load="handleImageLoad"
-        >
-        
-        <!-- 默认占位图 -->
-        <div v-else class="placeholder">
-          <i class="fas fa-map"></i>
-          <p>准备加载地下渗漏Cd通量分布图</p>
-        </div>
-      </div>
+      </el-card>
     </div>
   </div>
 </template>
 
 <script>
-import axios from 'axios';
+import { ElButton, ElCard, ElIcon } from 'element-plus';
+import { Loading, Warning, Picture, Refresh } from '@element-plus/icons-vue';
+import { api8000 } from '@/utils/request';
 
 export default {
   name: 'LeakageFluxMap',
+  components: {
+    ElButton,
+    ElCard,
+    ElIcon,
+    Loading,
+    Warning,
+    Picture,
+    Refresh
+  },
   data() {
     return {
       mapImageUrl: null,
@@ -51,8 +52,6 @@ export default {
       area: '乐昌市',
       level: 'county',
       colormap: 'blues',
-      avgFlux: '0.023',
-      lastUpdated: '2025年8月21日',
     };
   },
   mounted() {
@@ -64,22 +63,19 @@ export default {
       this.isError = false;
       
       try {
-        // 构建API URL
-        const apiUrl = `http://localhost:8000/api/cd-flux-removal/groundwater_leaching/visualize`;
-        const params = {
-          area: this.area,
-          level: this.level,
-          colormap: this.colormap
-        };
+        const response = await api8000.get(
+          `/api/cd-flux-removal/groundwater_leaching/visualize`,
+          {
+            params: {
+              area: this.area,
+              level: this.level,
+              colormap: this.colormap
+            },
+            responseType: 'blob'
+          }
+        );
         
-        // 发送API请求
-        const response = await axios.get(apiUrl, {
-          params,
-          responseType: 'blob' // 接收二进制数据
-        });
-        
-        // 创建图片URL
-        const blob = new Blob([response.data], { type: 'image/jpeg' });
+        const blob = new Blob([response.data], { type: response.headers['content-type'] });
         this.mapImageUrl = URL.createObjectURL(blob);
         
       } catch (error) {
@@ -91,7 +87,6 @@ export default {
       }
     },
     handleImageLoad() {
-      // 图片加载完成后的处理
       console.log('地图图片加载完成');
     }
   }
@@ -99,251 +94,133 @@ export default {
 </script>
 
 <style scoped>
-.leakage-flux-container {
+
+.page-container {
   width: 100%;
-  max-width: 900px;
-  margin: 0 auto;
-  background: white;
-  border-radius: 12px;
-  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15);
-  overflow: hidden;
-  font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
+  height: 100%;
 }
-
-.map-title {
-  background: linear-gradient(to right, #1a5fad, #2c8fd1);
-  color: white;
-  text-align: center;
-  padding: 18px 0;
-  font-size: 1.8rem;
-  font-weight: 600;
-  letter-spacing: 1px;
-  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
+/* 结果卡片样式 */
+.results-card {
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 8px;
+  padding: 20px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(5px);
+  height: 100%;
+  box-sizing: border-box;
 }
 
-.map-content {
+.results-content {
+  height: 100%;
   display: flex;
   flex-direction: column;
+}
+
+.map-section {
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 8px;
   padding: 20px;
-  background: #f8fafc;
+  margin-bottom: 20px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
 }
 
-.map-image-container {
-  flex: 1;
+h3 {
+  margin-bottom: 15px;
+  color: #333;
+  font-size: 18px;
+  font-weight: 600;
+}
+
+/* 图片容器限定大小 */
+.image-container {
+  width: 100%;
+  height: 500px;
   display: flex;
   justify-content: center;
   align-items: center;
-  padding: 10px;
-  background: white;
-  border-radius: 12px;
-  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
-  margin-bottom: 20px;
-  min-height: 600px;
-  position: relative;
+  background-color: #f9f9f9;
+  border-radius: 8px;
+  overflow: hidden;
 }
 
-.map-image {
+.result-image {
   max-width: 100%;
   max-height: 100%;
   object-fit: contain;
-  border-radius: 8px;
-  box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
   transition: transform 0.3s ease;
-  height: 600px;
-  width: 100%;
 }
 
-.map-image:hover {
+.result-image:hover {
   transform: scale(1.02);
 }
 
-/* 修改后的map-info样式 - 居中显示 */
-.map-info {
-  display: flex;
-  justify-content: center; /* 水平居中 */
-  align-items: center; /* 垂直居中 */
-  background: rgba(26, 95, 173, 0.05);
-  border-radius: 10px;
-  padding: 20px;
-}
-
-.info-item {
-  display: flex;
-  align-items: center;
-  padding: 10px 20px; /* 增加左右内边距 */
-  background: white;
-  border-radius: 8px;
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
-  width: fit-content; /* 使宽度适应内容 */
-}
-
-.info-item i {
-  font-size: 1.2rem;
-  color: #1a5fad;
-  margin-right: 10px;
-}
-
-.map-footer {
-  background: rgba(26, 95, 173, 0.1);
-  padding: 15px;
-  text-align: center;
-  font-size: 0.9rem;
-  color: #1a5fad;
-}
-
-/* 加载状态样式 */
-.loading-overlay {
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
+/* 加载和错误状态 */
+.loading-container, .error-container {
   display: flex;
   flex-direction: column;
-  justify-content: center;
   align-items: center;
-  background: rgba(255, 255, 255, 0.8);
-  z-index: 10;
-}
-
-.spinner {
-  width: 50px;
-  height: 50px;
-  border: 5px solid rgba(26, 95, 173, 0.2);
-  border-top: 5px solid #1a5fad;
-  border-radius: 50%;
-  animation: spin 1s linear infinite;
-  margin-bottom: 15px;
-}
-
-@keyframes spin {
-  0% { transform: rotate(0deg); }
-  100% { transform: rotate(360deg); }
-}
-
-.loading-overlay p {
-  font-size: 1.2rem;
-  color: #1a5fad;
-  font-weight: 500;
-}
-
-/* 错误状态样式 */
-.error-overlay {
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  display: flex;
-  flex-direction: column;
   justify-content: center;
-  align-items: center;
-  background: rgba(255, 255, 255, 0.9);
-  z-index: 10;
-  padding: 20px;
-  text-align: center;
+  height: 300px;
+  gap: 15px;
 }
 
-.error-overlay i {
-  font-size: 3rem;
-  color: #e74c3c;
-  margin-bottom: 15px;
+.loading-container {
+  color: #47C3B9;
 }
 
-.error-overlay p {
-  font-size: 1.2rem;
-  color: #333;
-  margin-bottom: 20px;
+.error-container {
+  color: #F56C6C;
 }
 
-.retry-btn {
-  background: #1a5fad;
-  color: white;
-  border: none;
-  border-radius: 30px;
-  padding: 10px 25px;
-  font-size: 1rem;
-  cursor: pointer;
-  display: flex;
-  align-items: center;
-  transition: background 0.3s;
+.loading-icon {
+  font-size: 36px;
+  margin-bottom: 10px;
+  animation: rotate 2s linear infinite;
 }
 
-.retry-btn:hover {
-  background: #154a8a;
+.error-icon {
+  font-size: 36px;
+  margin-bottom: 10px;
 }
 
-.retry-btn i {
-  font-size: 1rem;
-  margin-right: 8px;
-  color: white;
+.retry-btn {
+  margin-top: 15px;
 }
 
-/* 占位符样式 */
-.placeholder {
+.no-data {
   display: flex;
   flex-direction: column;
   align-items: center;
   justify-content: center;
-  width: 100%;
-  height: 100%;
-  color: #1a5fad;
+  height: 300px;
+  color: #999;
+  font-size: 16px;
 }
 
-.placeholder i {
-  font-size: 4rem;
-  margin-bottom: 20px;
+.no-data .el-icon {
+  font-size: 48px;
+  margin-bottom: 10px;
 }
 
-.placeholder p {
-  font-size: 1.2rem;
-  font-weight: 500;
+@keyframes rotate {
+  from { transform: rotate(0deg); }
+  to { transform: rotate(360deg); }
 }
 
+/* 响应式设计 */
 @media (max-width: 768px) {
-  .map-title {
-    font-size: 1.5rem;
-    padding: 15px 0;
-  }
-  
-  .map-image-container {
-    min-height: 450px;
-  }
-  
-  .map-image {
-    height: 450px;
-  }
-  
-  /* 移动端调整信息项样式 */
-  .map-info {
-    padding: 15px;
-  }
-  
-  .info-item {
-    padding: 10px 15px;
+  .image-container {
+    height: 400px;
   }
 }
 
 @media (max-width: 480px) {
-  .map-title {
-    font-size: 1.3rem;
-  }
-  
-  .map-image-container {
-    min-height: 350px;
-  }
-  
-  .map-image {
-    height: 350px;
-  }
-  
-  .info-item {
-    font-size: 0.9rem;
-    padding: 8px 12px;
+  .agricultural-input-management {
+    padding: 10px;
   }
   
-  .info-item i {
-    font-size: 1rem;
+  .image-container {
+    height: 300px;
   }
 }
 </style>

+ 148 - 286
src/views/User/hmInFlux/surfaceRunoff/samplingDesc4.vue

@@ -1,40 +1,45 @@
 <template>
   <div class="sampling-process">
-     <div class="header-section"> 
-      <h2>地表径流</h2>
-      <div class="subtitle">重金属在土壤-农作物系统中的迁移与输出机制</div>
-    </div>
-   <div class="content-section">
-      <div class="text-content">
-    <p>
-      地表径流是重力作用下的降水沿地表流动并汇入水体的过程,也是土壤重金属输出的主要途径。研究显示,地表径流中重金属浓度与土壤中重金属浓度相关,其输出量则与土壤中重金属溶解态和颗粒态的分配情况有关。随暴雨径流迁移是不同土地利用类型中重金属转移的最主要方式,也是造成地表水大面积非点源重金属污染的根本原因。
-    </p>
-    <p>
-      图动态模拟了降雨条件下土壤中镉、铅、铬等重金属随地表径流进行横向迁移的过程,以及其迁移量和形态随时间的变化规律。如图所示,降水落在农田表面后,在重力作用下沿地表流动,形成地表径流。土壤中的重金属随着地表径流一起被冲刷并汇入水体,最终在河流或湖泊中沉积。
-    </p>
-      </div>
-   </div>
-    <div class="image-row">
-      <div class="image-container">
-        <el-image :src="image4" alt="地表径流是重金属迁移的主要途径" class="sampling-image"></el-image>
-        <p class="image-caption">
-           地表径流是重金属迁移的主要途径
-        </p>
+    <!-- 地表径流部分 -->
+    <div class="section-container">
+      <div class="section-header">
+        <div class="section-number">1</div>
+        <h2>地表径流</h2>
       </div>
-    </div>
-    <!-- 新增视频区域 -->
-      <div class="video-section">
-        <h3 class="video-title">地表径流过程视频演示</h3>
-        <div class="video-container">
-          <video controls class="sampling-video">
-            <source src="@/assets/videos/地表径流.mp4" type="video/mp4">
-            您的浏览器不支持HTML5视频播放。
-          </video>
-          <p class="video-caption">
-            视频展示了重金属在地表中的积累过程
-          </p>
+      
+      <div class="section-content">
+        <div class="description-card">
+          <div class="icon">🌊</div>
+          <div class="description-text">
+            <p>
+              地表径流是重力作用下的降水沿地表流动并汇入水体的过程,也是土壤重金属输出的主要途径。研究显示,地表径流中重金属浓度输出量与土壤中重金属溶解态和颗粒态的分配情况有关。
+            </p>
+            <p>
+              如图所示,降水落在农田表面后,在重力作用下沿地表流动,形成地表径流。土壤中的重金属随着地表径流一起被冲刷并汇入水体,最终在河流或湖泊中沉积。
+            </p>
+          </div>
+        </div>
+        
+        <div class="image-gallery">
+          <div class="image-card">
+            <el-image :src="image4" alt="地表径流是重金属迁移的主要途径" class="sampling-image"></el-image>
+            <div class="image-info">
+              <h3>地表径流过程</h3>
+            </div>
+          </div>
+          
+          <div class="image-card">
+            <video controls class="sampling-image">
+              <source src="@/assets/videos/地表径流.mp4" type="video/mp4">
+              您的浏览器不支持HTML5视频播放。
+            </video>
+            <div class="image-info">
+              <h3>地表径流视频演示</h3>
+            </div>
+          </div>
         </div>
       </div>
+    </div>
   </div>
 </template>
 
@@ -42,7 +47,6 @@
 export default {
   data() {
     return {
-    
       image4: 'https://hunyuan-plugin-1258344706.cos.ap-nanjing.myqcloud.com/pdf_youtu/img/baf4078408e6b4dee9eb96875aac3573-image.png'
     };
   }
@@ -52,11 +56,10 @@ export default {
 <style scoped>
 .sampling-process {
   padding: 30px;
-  background: linear-gradient(135deg, rgba(230, 247, 255, 0.7) 0%, rgba(240, 248, 255, 0.7) 100%);
+  background: linear-gradient(135deg, rgba(230, 247, 255, 0.85) 0%, rgba(240, 248, 255, 0.85) 100%);
   position: relative;
   overflow: hidden;
-  border-radius: 16px;
-  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
+  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 }
 
 .sampling-process::before {
@@ -67,334 +70,193 @@ export default {
   width: 100%;
   height: 100%;
   background: url('https://images.unsplash.com/photo-1518834107812-67b0b7c58434?q=80&w=2070&auto=format&fit=crop') center/cover;
-  opacity: 0.15;
+  opacity: 0.12;
   z-index: -1;
   filter: blur(2px);
 }
 
-/* 标题区域 */
-.header-section {
-  text-align: center;
-  margin-bottom: 30px;
-  padding-bottom: 20px;
-  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
-}
-
-.header-section h2 {
-  position: relative;
-  padding-bottom: 15px;
-  margin-bottom: 10px;
-  color: #1a365d;
-  font-size: 2rem;
-  font-weight: 700;
-  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-}
-
-.header-section h2::after {
-  content: "";
-  position: absolute;
-  bottom: 0;
-  left: 50%;
-  transform: translateX(-50%);
-  width: 80px;
-  height: 4px;
-  background: linear-gradient(90deg, #4a9ef7, #3acfd5);
-  border-radius: 3px;
-}
-
-.subtitle {
-  font-size: 1.2rem;
-  color: #2d5986;
-  font-weight: 500;
-  letter-spacing: 0.5px;
-}
-
-/* 内容区域 */
-.content-section {
-  margin-bottom: 40px;
-  padding: 25px;
-  background: rgba(255, 255, 255, 0.85);
-  border-radius: 12px;
-  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
+.section-container {
+  margin-bottom: 50px;
+  padding: 30px;
+  border-radius: 18px;
+  background: rgba(255, 255, 255, 0.88);
+  box-shadow: 0 8px 25px rgba(0, 60, 120, 0.08);
   transition: all 0.4s ease;
   overflow: hidden;
+  position: relative;
 }
 
-.content-section:hover {
+.section-container:hover {
   transform: translateY(-5px);
-  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
-  background: rgba(255, 255, 255, 0.92);
+  box-shadow: 0 12px 35px rgba(0, 60, 120, 0.15);
 }
 
-.text-content {
-  margin-bottom: 30px;
-}
-
-p {
-  text-indent: 2em;
-  margin: 20px 0;
-  line-height: 1.8;
-  color: #2d3748;
-  font-size: 1.1rem;
+.section-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 25px;
   position: relative;
-  padding-left: 20px;
-}
-
-p::before {
-  content: "•";
-  position: absolute;
-  left: 0;
-  top: 0;
-  color: #3a9fd3;
-  font-size: 1.5rem;
-  line-height: 1.8;
+  padding-bottom: 15px;
+  border-bottom: 2px solid rgba(58, 160, 207, 0.25);
 }
 
-/* 图片区域 */
-.image-row {
+.section-number {
+  width: 50px;
+  height: 50px;
+  background: linear-gradient(135deg, #4a9ef7, #3a9fd3);
+  color: white;
+  border-radius: 50%;
   display: flex;
+  align-items: center;
   justify-content: center;
-  margin-bottom: 30px;
+  font-size: 1.8rem;
+  font-weight: bold;
+  margin-right: 20px;
+  box-shadow: 0 5px 12px rgba(74, 158, 247, 0.25);
 }
 
-.image-container {
-  border-radius: 12px;
-  overflow: hidden;
-  position: relative;
-  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
-  max-width: 800px;
-  margin: 0 auto;
-  transition: all 0.4s ease;
+h2 {
+  color: #1a365d;
+  font-size: 1.9rem;
+  margin: 0;
+  font-weight: 650;
+  letter-spacing: 0.5px;
 }
 
-.image-container:hover {
-  transform: scale(1.01);
-  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
+.section-content {
+  padding: 0 10px;
 }
 
-.sampling-image {
-  width: 100%;
-  height: auto;
-  min-height: 350px;
-  display: block;
-  background: rgba(245, 249, 255, 0.4);
-  object-fit: cover;
-  transition: transform 0.5s ease;
+.description-card {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 30px;
+  padding: 20px;
+  background: rgba(235, 245, 255, 0.6);
+  border-radius: 15px;
 }
 
-.image-container:hover .sampling-image {
-  transform: scale(1.02);
+.icon {
+  font-size: 2.5rem;
+  margin-right: 25px;
+  color: #3a9fd3;
 }
 
-.image-caption {
-  text-align: center;
+.description-text p {
   font-size: 1.1rem;
-  color: #1a365d;
-  padding: 15px;
-  font-weight: 600;
-  background: linear-gradient(to right, rgba(248, 250, 252, 0.9), rgba(240, 248, 255, 0.9));
+  line-height: 1.8;
+  color: #2d3748;
   margin: 0;
-  border-top: 1px dashed #cbd5e0;
-  position: relative;
-}
-
-.image-caption::before {
-  content: "📌";
-  position: absolute;
-  left: 20px;
-  top: 50%;
-  transform: translateY(-50%);
-}
-
-/* 视频区域 */
-.video-section {
-  margin-top: 40px;
-  padding-top: 30px;
-  border-top: 1px solid rgba(0, 0, 0, 0.1);
 }
 
-.video-title {
-  text-align: center;
-  color: #1a365d;
-  font-size: 1.5rem;
-  margin-bottom: 20px;
-  position: relative;
-  padding-bottom: 10px;
+/* 图片画廊样式 */
+.image-gallery {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 25px;
+  margin-top: 20px;
 }
 
-.video-title::after {
-  content: "";
-  position: absolute;
-  bottom: 0;
-  left: 50%;
-  transform: translateX(-50%);
-  width: 60px;
-  height: 3px;
-  background: linear-gradient(90deg, #4a9ef7, #3acfd5);
-  border-radius: 2px;
-}
-
-.video-container {
-  border-radius: 12px;
+.image-card {
+  border-radius: 15px;
   overflow: hidden;
-  position: relative;
-  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
-  max-width: 800px;
-  margin: 0 auto;
+  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1);
   transition: all 0.4s ease;
+  background: white;
 }
 
-.video-container:hover {
-  transform: translateY(-5px);
-  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
+.image-card:hover {
+  transform: translateY(-8px);
+  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
 }
 
-.sampling-video {
+.sampling-image {
   width: 100%;
-  height: auto;
+  height: 250px;
   display: block;
-  background: #f8fafc;
-  min-height: 350px;
-}
-
-.video-caption {
-  text-align: center;
-  font-size: 1.1rem;
-  color: #1a365d;
-  padding: 15px;
-  font-weight: 600;
-  background: linear-gradient(to right, rgba(248, 250, 252, 0.9), rgba(240, 248, 255, 0.9));
-  margin: 0;
-  border-top: 1px dashed #cbd5e0;
-  position: relative;
+  object-fit: cover;
+  transition: transform 0.4s ease;
+  background: rgba(245, 249, 255, 0.4);
 }
 
-.video-caption::before {
-  content: "📹";
-  position: absolute;
-  left: 20px;
-  top: 50%;
-  transform: translateY(-50%);
+.image-card:hover .sampling-image {
+  transform: scale(1.05);
 }
 
-/* 装饰元素 */
-.decorative-element {
-  position: absolute;
-  width: 100px;
-  height: 100px;
-  border-radius: 50%;
-  background: linear-gradient(135deg, rgba(74, 158, 247, 0.1), rgba(58, 207, 213, 0.1));
-  z-index: -1;
+.image-info {
+  padding: 20px;
 }
 
-.decorative-element:nth-child(1) {
-  top: 10%;
-  left: 5%;
-  width: 80px;
-  height: 80px;
-}
-
-.decorative-element:nth-child(2) {
-  bottom: 15%;
-  right: 8%;
-  width: 60px;
-  height: 60px;
+.image-info h3 {
+  color: #1a365d;
+  font-size: 1.25rem;
+  margin-top: 0;
+  margin-bottom: 10px;
 }
 
 /* 响应式设计 */
-@media (max-width: 900px) {
-  .sampling-process {
-    padding: 20px;
-  }
-  
-  .header-section h2 {
-    font-size: 1.8rem;
-  }
-  
-  .subtitle {
-    font-size: 1.1rem;
-  }
-  
-  .content-section {
-    padding: 20px;
-  }
-  
-  .sampling-image,
-  .sampling-video {
-    min-height: 300px;
+@media (max-width: 1100px) {
+  .image-gallery {
+    grid-template-columns: repeat(2, 1fr);
   }
 }
 
 @media (max-width: 768px) {
-  .sampling-process {
-    padding: 15px;
+  .image-gallery {
+    grid-template-columns: 1fr;
   }
   
-  .header-section h2 {
-    font-size: 1.6rem;
+  .section-container {
+    padding: 25px;
   }
   
-  p {
-    font-size: 1rem;
-    padding-left: 15px;
+  .section-number {
+    width: 45px;
+    height: 45px;
+    font-size: 1.6rem;
   }
   
-  .image-caption,
-  .video-caption {
-    font-size: 1rem;
-    padding: 12px;
+  h2 {
+    font-size: 1.7rem;
   }
   
-  .sampling-image,
-  .sampling-video {
-    min-height: 250px;
+  .description-card {
+    flex-direction: column;
+    align-items: center;
+    text-align: center;
   }
   
-  .video-title {
-    font-size: 1.3rem;
+  .icon {
+    margin-right: 0;
+    margin-bottom: 15px;
   }
 }
 
 @media (max-width: 480px) {
   .sampling-process {
-    padding: 10px;
-  }
-  
-  .header-section h2 {
-    font-size: 1.4rem;
-  }
-  
-  .subtitle {
-    font-size: 0.95rem;
-  }
-  
-  .content-section {
-    padding: 15px;
+    padding: 20px;
   }
   
-  p {
-    font-size: 0.95rem;
-    padding-left: 10px;
+  .section-container {
+    padding: 20px;
   }
   
-  .image-caption,
-  .video-caption {
-    font-size: 0.9rem;
-    padding: 10px 15px 10px 35px;
+  .section-header {
+    flex-direction: column;
+    align-items: flex-start;
   }
   
-  .image-caption::before,
-  .video-caption::before {
-    left: 10px;
+  .section-number {
+    margin-right: 0;
+    margin-bottom: 15px;
   }
   
-  .sampling-image,
-  .sampling-video {
-    min-height: 200px;
+  h2 {
+    font-size: 1.6rem;
   }
   
-  .video-title {
-    font-size: 1.1rem;
+  .sampling-image {
+    height: 200px;
   }
 }
 </style>

+ 119 - 242
src/views/User/hmInFlux/surfaceRunoff/surfaceRunoffInputFlux.vue

@@ -1,47 +1,48 @@
 <template>
-  <div class="runoff-flux-container">
-    <div class="map-title">地表径流Cd通量分布图</div>
-    <div class="map-content">
-      <div class="map-image-container">
-        <!-- 加载状态 -->
-        <div v-if="isLoading" class="loading-overlay">
-          <div class="spinner"></div>
-          <p>地图加载中...</p>
+  <div class="agricultural-input-management">
+    <div class="page-container">
+      <el-card class="results-card">
+        <div class="results-content">
+          <!-- 地图区域 -->
+          <div class="map-section">
+            <h3>地下渗漏Cd通量分布图</h3>
+            <div v-if="isLoading" class="loading-container">
+              <el-icon class="loading-icon"><Loading /></el-icon>
+              <span>数据加载中...</span>
+            </div>
+            <div v-else-if="isError" class="error-container">
+              <el-icon class="error-icon"><Warning /></el-icon>
+              <span>{{ errorMessage }}</span>
+              <el-button class="retry-btn" @click="fetchMap">
+                <el-icon><Refresh /></el-icon> 重新加载
+              </el-button>
+            </div>
+            <div v-else class="image-container">
+              <img :src="mapImageUrl" class="result-image"></img>
+              </div>
+          </div>
         </div>
-        
-        <!-- 错误状态 -->
-        <div v-if="isError" class="error-overlay">
-          <i class="fas fa-exclamation-triangle"></i>
-          <p>{{ errorMessage }}</p>
-          <button class="retry-btn" @click="fetchMap">
-            <i class="fas fa-redo"></i> 重新加载
-          </button>
-        </div>
-        
-        <!-- 地图图片 -->
-        <img 
-          v-if="mapImageUrl"
-          :src="mapImageUrl" 
-          alt="地表径流Cd通量分布图" 
-          class="map-image"
-          @load="handleImageLoad"
-        >
-        
-        <!-- 默认占位图 -->
-        <div v-else class="placeholder">
-          <i class="fas fa-map"></i>
-          <p>准备加载地表径流Cd通量分布图</p>
-        </div>
-      </div>
+      </el-card>
     </div>
   </div>
 </template>
 
 <script>
-import axios from 'axios';
+import { ElButton, ElCard, ElIcon } from 'element-plus';
+import { Loading, Warning, Picture, Refresh } from '@element-plus/icons-vue';
+import { api8000 } from '@/utils/request';
 
 export default {
-  name: 'RunoffFluxMap',
+  name: 'LeakageFluxMap',
+  components: {
+    ElButton,
+    ElCard,
+    ElIcon,
+    Loading,
+    Warning,
+    Picture,
+    Refresh
+  },
   data() {
     return {
       mapImageUrl: null,
@@ -51,8 +52,6 @@ export default {
       area: '乐昌市',
       level: 'county',
       colormap: 'blues',
-      
-      lastUpdated: '2025年8月21日',
     };
   },
   mounted() {
@@ -64,22 +63,19 @@ export default {
       this.isError = false;
       
       try {
-        // 构建API URL
-        const apiUrl = `http://localhost:8000/api/cd-flux-removal/surface_runoff/visualize`;
-        const params = {
-          area: this.area,
-          level: this.level,
-          colormap: this.colormap
-        };
-        
-        // 发送API请求
-        const response = await axios.get(apiUrl, {
-          params,
-          responseType: 'blob' // 接收二进制数据
-        });
+        const response = await api8000.get(
+          `/api/cd-flux-removal/surface_runoff/visualize`,
+          {
+            params: {
+              area: this.area,
+              level: this.level,
+              colormap: this.colormap
+            },
+            responseType: 'blob'
+          }
+        );
         
-        // 创建图片URL
-        const blob = new Blob([response.data], { type: 'image/jpeg' });
+        const blob = new Blob([response.data], { type: response.headers['content-type'] });
         this.mapImageUrl = URL.createObjectURL(blob);
         
       } catch (error) {
@@ -91,7 +87,6 @@ export default {
       }
     },
     handleImageLoad() {
-      // 图片加载完成后的处理
       console.log('地图图片加载完成');
     }
   }
@@ -99,251 +94,133 @@ export default {
 </script>
 
 <style scoped>
-.runoff-flux-container {
+
+.page-container {
   width: 100%;
-  max-width: 900px;
-  margin: 0 auto;
-  background: white;
-  border-radius: 12px;
-  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15);
-  overflow: hidden;
-  font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
+  height: 100%;
 }
-
-.map-title {
-  background: linear-gradient(to right, #1a5fad, #2c8fd1);
-  color: white;
-  text-align: center;
-  padding: 18px 0;
-  font-size: 1.8rem;
-  font-weight: 600;
-  letter-spacing: 1px;
-  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
+/* 结果卡片样式 */
+.results-card {
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 8px;
+  padding: 20px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(5px);
+  height: 100%;
+  box-sizing: border-box;
 }
 
-.map-content {
+.results-content {
+  height: 100%;
   display: flex;
   flex-direction: column;
+}
+
+.map-section {
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 8px;
   padding: 20px;
-  background: #f8fafc;
+  margin-bottom: 20px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
 }
 
-.map-image-container {
-  flex: 1;
+h3 {
+  margin-bottom: 15px;
+  color: #333;
+  font-size: 18px;
+  font-weight: 600;
+}
+
+/* 图片容器限定大小 */
+.image-container {
+  width: 100%;
+  height: 500px;
   display: flex;
   justify-content: center;
   align-items: center;
-  padding: 10px;
-  background: white;
-  border-radius: 12px;
-  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
-  margin-bottom: 20px;
-  min-height: 600px;
-  position: relative;
+  background-color: #f9f9f9;
+  border-radius: 8px;
+  overflow: hidden;
 }
 
-.map-image {
+.result-image {
   max-width: 100%;
   max-height: 100%;
   object-fit: contain;
-  border-radius: 8px;
-  box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
   transition: transform 0.3s ease;
-  height: 600px;
-  width: 100%;
 }
 
-.map-image:hover {
+.result-image:hover {
   transform: scale(1.02);
 }
 
-/* 修改后的map-info样式 - 居中显示 */
-.map-info {
-  display: flex;
-  justify-content: center; /* 水平居中 */
-  align-items: center; /* 垂直居中 */
-  background: rgba(26, 95, 173, 0.05);
-  border-radius: 10px;
-  padding: 20px;
-}
-
-.info-item {
-  display: flex;
-  align-items: center;
-  padding: 10px 20px; /* 增加左右内边距 */
-  background: white;
-  border-radius: 8px;
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
-  width: fit-content; /* 使宽度适应内容 */
-}
-
-.info-item i {
-  font-size: 1.2rem;
-  color: #1a5fad;
-  margin-right: 10px;
-}
-
-.map-footer {
-  background: rgba(26, 95, 173, 0.1);
-  padding: 15px;
-  text-align: center;
-  font-size: 0.9rem;
-  color: #1a5fad;
-}
-
-/* 加载状态样式 */
-.loading-overlay {
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
+/* 加载和错误状态 */
+.loading-container, .error-container {
   display: flex;
   flex-direction: column;
-  justify-content: center;
   align-items: center;
-  background: rgba(255, 255, 255, 0.8);
-  z-index: 10;
-}
-
-.spinner {
-  width: 50px;
-  height: 50px;
-  border: 5px solid rgba(26, 95, 173, 0.2);
-  border-top: 5px solid #1a5fad;
-  border-radius: 50%;
-  animation: spin 1s linear infinite;
-  margin-bottom: 15px;
-}
-
-@keyframes spin {
-  0% { transform: rotate(0deg); }
-  100% { transform: rotate(360deg); }
-}
-
-.loading-overlay p {
-  font-size: 1.2rem;
-  color: #1a5fad;
-  font-weight: 500;
-}
-
-/* 错误状态样式 */
-.error-overlay {
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  display: flex;
-  flex-direction: column;
   justify-content: center;
-  align-items: center;
-  background: rgba(255, 255, 255, 0.9);
-  z-index: 10;
-  padding: 20px;
-  text-align: center;
+  height: 300px;
+  gap: 15px;
 }
 
-.error-overlay i {
-  font-size: 3rem;
-  color: #e74c3c;
-  margin-bottom: 15px;
+.loading-container {
+  color: #47C3B9;
 }
 
-.error-overlay p {
-  font-size: 1.2rem;
-  color: #333;
-  margin-bottom: 20px;
+.error-container {
+  color: #F56C6C;
 }
 
-.retry-btn {
-  background: #1a5fad;
-  color: white;
-  border: none;
-  border-radius: 30px;
-  padding: 10px 25px;
-  font-size: 1rem;
-  cursor: pointer;
-  display: flex;
-  align-items: center;
-  transition: background 0.3s;
+.loading-icon {
+  font-size: 36px;
+  margin-bottom: 10px;
+  animation: rotate 2s linear infinite;
 }
 
-.retry-btn:hover {
-  background: #154a8a;
+.error-icon {
+  font-size: 36px;
+  margin-bottom: 10px;
 }
 
-.retry-btn i {
-  font-size: 1rem;
-  margin-right: 8px;
-  color: white;
+.retry-btn {
+  margin-top: 15px;
 }
 
-/* 占位符样式 */
-.placeholder {
+.no-data {
   display: flex;
   flex-direction: column;
   align-items: center;
   justify-content: center;
-  width: 100%;
-  height: 100%;
-  color: #1a5fad;
+  height: 300px;
+  color: #999;
+  font-size: 16px;
 }
 
-.placeholder i {
-  font-size: 4rem;
-  margin-bottom: 20px;
+.no-data .el-icon {
+  font-size: 48px;
+  margin-bottom: 10px;
 }
 
-.placeholder p {
-  font-size: 1.2rem;
-  font-weight: 500;
+@keyframes rotate {
+  from { transform: rotate(0deg); }
+  to { transform: rotate(360deg); }
 }
 
+/* 响应式设计 */
 @media (max-width: 768px) {
-  .map-title {
-    font-size: 1.5rem;
-    padding: 15px 0;
-  }
-  
-  .map-image-container {
-    min-height: 450px;
-  }
-  
-  .map-image {
-    height: 450px;
-  }
-  
-  /* 移动端调整信息项样式 */
-  .map-info {
-    padding: 15px;
-  }
-  
-  .info-item {
-    padding: 10px 15px;
+  .image-container {
+    height: 400px;
   }
 }
 
 @media (max-width: 480px) {
-  .map-title {
-    font-size: 1.3rem;
-  }
-  
-  .map-image-container {
-    min-height: 350px;
-  }
-  
-  .map-image {
-    height: 350px;
-  }
-  
-  .info-item {
-    font-size: 0.9rem;
-    padding: 8px 12px;
+  .agricultural-input-management {
+    padding: 10px;
   }
   
-  .info-item i {
-    font-size: 1rem;
+  .image-container {
+    height: 300px;
   }
 }
 </style>

+ 371 - 0
src/views/User/hmInFlux/totalOutputFluxDesc.vue

@@ -0,0 +1,371 @@
+<template>
+  <div class="output-flux-intro">
+    <!-- 第一部分:输出总通量概念介绍 -->
+    <div class="content-section">
+      <div class="section-header">
+        <div class="section-number">1</div>
+        <h2>重金属输出总通量概念</h2>
+      </div>
+      
+      <div class="concept-card">
+        <div class="concept-icon">📉</div>
+        <div class="concept-content">
+          <p>
+            <span class="highlight">重金属输出总通量</span>是指通过籽粒移除、秸秆移除、地下渗漏和地表径流四种主要途径从农田生态系统输出的重金属总量,通常以克/公顷/年(g/ha/a)为单位表示。该指标综合反映了农田系统向外部环境输出的重金属污染负荷,是评估农田重金属平衡和生态风险的重要依据。
+          </p>
+        </div>
+      </div>
+      
+      <div class="formula-container">
+        <div class="formula-card">
+          <h3>输出总通量计算公式</h3>
+          <div class="formula-content">
+            <p class="formula">F<sub>输出</sub> = F<sub>籽粒</sub> + F<sub>秸秆</sub> + F<sub>渗漏</sub> + F<sub>径流</sub></p>
+            <div class="formula-explain">
+              <p>式中:</p>
+              <p>F<sub>输出</sub> —— 重金属输出总通量 (g/ha/a)</p>
+              <p>F<sub>籽粒</sub> —— 籽粒移除途径输出通量 (g/ha/a)</p>
+              <p>F<sub>秸秆</sub> —— 秸秆移除途径输出通量 (g/ha/a)</p>
+              <p>F<sub>渗漏</sub> —— 地下渗漏途径输出通量 (g/ha/a)</p>
+              <p>F<sub>径流</sub> —— 地表径流途径输出通量 (g/ha/a)</p>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      contributionImage: '/输出途径贡献比例.png'
+    };
+  }
+};
+</script>
+
+<style scoped>
+.output-flux-intro {
+  padding: 30px;
+  background: linear-gradient(135deg, rgba(245, 252, 240, 0.9) 0%, rgba(235, 248, 255, 0.9) 100%);
+  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+}
+
+.content-section {
+  margin-bottom: 50px;
+  padding: 30px;
+  border-radius: 15px;
+  background: rgba(255, 255, 255, 0.92);
+  box-shadow: 0 8px 25px rgba(0, 60, 120, 0.08);
+  transition: all 0.4s ease;
+}
+
+.content-section:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 12px 35px rgba(0, 60, 120, 0.15);
+}
+
+.section-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 25px;
+  padding-bottom: 15px;
+  border-bottom: 2px solid rgba(58, 160, 207, 0.25);
+}
+
+.section-number {
+  width: 50px;
+  height: 50px;
+  background: linear-gradient(135deg, #4a9ef7, #3a9fd3);
+  color: white;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 1.8rem;
+  font-weight: bold;
+  margin-right: 20px;
+  box-shadow: 0 5px 12px rgba(74, 158, 247, 0.25);
+}
+
+h2 {
+  color: #1a365d;
+  font-size: 1.9rem;
+  margin: 0;
+  font-weight: 650;
+}
+
+.concept-card {
+  display: flex;
+  align-items: center;
+  padding: 25px;
+  background: rgba(235, 245, 255, 0.6);
+  border-radius: 15px;
+  margin-bottom: 30px;
+  border-left: 4px solid #3a9fd3;
+}
+
+.concept-icon {
+  font-size: 2.5rem;
+  margin-right: 25px;
+  color: #3a9fd3;
+}
+
+.concept-content p {
+  font-size: 1.15rem;
+  line-height: 1.8;
+  color: #2d3748;
+  margin: 0 0 15px 0;
+}
+
+.highlight {
+  font-weight: 600;
+  color: #1a6fb3;
+}
+
+.formula-container {
+  display: flex;
+  justify-content: center;
+}
+
+.formula-card {
+  width: 100%;
+  max-width: 700px;
+  padding: 25px;
+  background: rgba(245, 252, 255, 0.8);
+  border-radius: 15px;
+  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
+  border-top: 3px solid #3a9fd3;
+}
+
+.formula-card h3 {
+  color: #1a365d;
+  font-size: 1.5rem;
+  margin-top: 0;
+  margin-bottom: 20px;
+  text-align: center;
+}
+
+.formula-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.formula {
+  font-size: 1.8rem;
+  font-weight: bold;
+  color: #2c3e50;
+  margin-bottom: 20px;
+  text-align: center;
+  padding: 15px;
+  background: white;
+  border-radius: 10px;
+  width: 100%;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.formula-explain {
+  width: 100%;
+}
+
+.formula-explain p {
+  font-size: 1.1rem;
+  line-height: 1.8;
+  color: #2d3748;
+  margin: 5px 0;
+}
+
+.formula-explain p:first-child {
+  font-weight: 600;
+  margin-bottom: 10px;
+}
+
+.contribution-container {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 25px;
+  margin-bottom: 30px;
+}
+
+.contribution-card {
+  display: flex;
+  padding: 20px;
+  background: rgba(245, 252, 255, 0.7);
+  border-radius: 15px;
+  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
+  transition: all 0.3s ease;
+}
+
+.contribution-card:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
+}
+
+.contribution-icon {
+  font-size: 2rem;
+  margin-right: 20px;
+  color: #3a9fd3;
+}
+
+.contribution-content h3 {
+  color: #1a365d;
+  font-size: 1.3rem;
+  margin-top: 0;
+  margin-bottom: 12px;
+}
+
+.contribution-content p {
+  font-size: 1.05rem;
+  line-height: 1.6;
+  color: #2d3748;
+  margin: 0 0 10px 0;
+}
+
+.image-container {
+  max-width: 700px;
+  margin: 0 auto;
+  border-radius: 15px;
+  overflow: hidden;
+  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+}
+
+.flux-image {
+  width: 100%;
+  display: block;
+}
+
+.image-caption {
+  text-align: center;
+  font-size: 16px;
+  color: #2d3748;
+  padding: 15px;
+  background: rgba(248, 250, 252, 0.8);
+  margin: 0;
+  border-top: 1px dashed #cbd5e0;
+}
+
+.application-container {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 25px;
+}
+
+.application-card {
+  display: flex;
+  padding: 20px;
+  background: rgba(245, 252, 255, 0.7);
+  border-radius: 15px;
+  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
+  transition: all 0.3s ease;
+  border-left: 4px solid #5cb85c;
+}
+
+.application-card:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
+}
+
+.application-icon {
+  font-size: 2rem;
+  margin-right: 20px;
+  color: #5cb85c;
+}
+
+.application-content h3 {
+  color: #1a365d;
+  font-size: 1.3rem;
+  margin-top: 0;
+  margin-bottom: 12px;
+}
+
+.application-content p {
+  font-size: 1.05rem;
+  line-height: 1.6;
+  color: #2d3748;
+  margin: 0;
+}
+
+/* 响应式设计 */
+@media (max-width: 1200px) {
+  .contribution-container {
+    grid-template-columns: 1fr;
+  }
+}
+
+@media (max-width: 900px) {
+  .content-section {
+    padding: 25px;
+  }
+  
+  .section-number {
+    width: 45px;
+    height: 45px;
+    font-size: 1.6rem;
+  }
+  
+  h2 {
+    font-size: 1.7rem;
+  }
+  
+  .application-container {
+    grid-template-columns: 1fr;
+  }
+}
+
+@media (max-width: 768px) {
+  .output-flux-intro {
+    padding: 20px;
+  }
+  
+  .concept-card {
+    flex-direction: column;
+    text-align: center;
+  }
+  
+  .concept-icon {
+    margin-right: 0;
+    margin-bottom: 15px;
+  }
+  
+  .contribution-card {
+    flex-direction: column;
+    text-align: center;
+  }
+  
+  .contribution-icon {
+    margin-right: 0;
+    margin-bottom: 15px;
+  }
+}
+
+@media (max-width: 480px) {
+  .output-flux-intro {
+    padding: 15px;
+  }
+  
+  .content-section {
+    padding: 20px;
+  }
+  
+  .section-header {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+  
+  .section-number {
+    margin-right: 0;
+    margin-bottom: 15px;
+  }
+  
+  h2 {
+    font-size: 1.6rem;
+  }
+  
+  .formula {
+    font-size: 1.5rem;
+  }
+}
+</style>

+ 2 - 2
src/views/User/neutralizationModel/AcidNeutralizationModel.vue

@@ -137,7 +137,7 @@
 import { reactive, ref, nextTick } from "vue";
 import { ElMessage } from 'element-plus';
 import axios from 'axios';
-import request from '../../../utils/request';
+import { api5000 } from '../../../utils/request'; // 修改导入
 
 const form = reactive<{
   init_pH: number | null,
@@ -271,7 +271,7 @@ const onSubmit = async () => {
   };
   console.log('提交的数据:', data);
   try {
-    const response = await request.post('http://127.0.0.1:5000/predict', data, {
+    const response = await api5000.post('/predict', data, {
       headers: {
         'Content-Type': 'application/json'
       }

+ 6 - 6
src/views/User/neutralizationModel/ModelIterationVisualization.vue

@@ -2,7 +2,7 @@
 import { ref, onMounted, nextTick, onUnmounted, defineProps } from 'vue';
 import VueEcharts from 'vue-echarts';
 import 'echarts';
-import request from '../../../utils/request';
+import { api5000 } from '../../../utils/request';
 
 interface HistoryDataItem {
   dataset_id: number;
@@ -91,7 +91,7 @@ const calculateDataRange = (data: [number, number][]) => {
 // 获取折线图数据
 const fetchLineData = async () => {
   try {
-    const response = await request.get<HistoryDataResponse>(`http://127.0.0.1:5000/get-model-history/${props.lineChartPathParam}`);
+    const response = await api5000.get<HistoryDataResponse>(`/get-model-history/${props.lineChartPathParam}`);
     const data = response.data;
 
     const timestamps = data.timestamps;
@@ -145,7 +145,7 @@ const fetchLineData = async () => {
 // 获取散点图数据
 const fetchScatterData = async (modelId: number, optionRef: any) => {
   try {
-    const response = await request.get<ScatterDataResponse>(`http://127.0.0.1:5000/model-scatter-data/${modelId}`);
+    const response = await api5000.get<ScatterDataResponse>(`/model-scatter-data/${modelId}`);
     const data = response.data;
 
     const scatterData = data.scatter_data;
@@ -247,12 +247,12 @@ onUnmounted(() => {
 </script>
 <template>
   <div class="container">
-    <template v-if="showLineChart">
-      <!-- 折线图表头 -->
+    <!-- <template v-if="showLineChart">
+      折线图表头 
       <div class="chart-container">
         <VueEcharts :option="ecLineOption" ref="ecLineOptionRef" />
       </div>
-    </template>
+    </template> -->
     <template v-if="showInitScatterChart">
       <!-- 初代散点图表头 -->
       <h2 class="chart-header">初代散点图</h2>

+ 111 - 27
src/views/login/loginView.vue

@@ -38,6 +38,11 @@
           </el-form-item>
         </div>
 
+        <!-- 错误提示区域 -->
+        <div v-if="showError" class="error-message">
+          {{ errorMessage }}
+        </div>
+
         <div class="language-toggle-wrapper">
           <span class="text-toggle" @click="toggleLanguage">{{
             currentLanguageName
@@ -98,6 +103,11 @@
           </el-form-item>
         </div>
 
+        <!-- 错误提示区域 -->
+        <div v-if="showError" class="error-message">
+          {{ errorMessage }}
+        </div>
+
         <div class="language-toggle-wrapper">
           <span class="text-toggle" @click="toggleLanguage">{{
             currentLanguageName
@@ -128,7 +138,7 @@
 
 <script setup lang="ts">
 import { reactive, ref, computed, watch, onMounted } from "vue";
-import { ElForm, ElMessage } from "element-plus";
+import { ElForm } from "element-plus";
 import type { FormRules } from "element-plus";
 import { login, register } from "@/API/users";
 import { useTokenStore } from "@/stores/mytoken";
@@ -156,6 +166,8 @@ const router = useRouter();
 const isLogin = ref(true);
 const userType = ref<"user" | "admin">("user");
 const loading = ref(false);
+const showError = ref(false);
+const errorMessage = ref("");
 
 const form = reactive<LoginForm>({ name: "", password: "" });
 const registerForm = reactive<RegisterForm>({
@@ -170,6 +182,7 @@ const registerFormRef = ref<InstanceType<typeof ElForm> | null>(null);
 // ============ 表单切换 ============
 const toggleForm = () => {
   isLogin.value = !isLogin.value;
+  showError.value = false; // 切换表单时隐藏错误提示
 };
 const toggleUserType = () => {
   userType.value = userType.value === "user" ? "admin" : "user";
@@ -187,46 +200,70 @@ const currentUserTypeName = computed(() =>
   userType.value === "user" ? t("login.switchToAdmin") : t("login.switchToUser")
 );
 
+// ============ 显示错误消息 ============
+const showErrorMsg = (message: string) => {
+  errorMessage.value = message;
+  showError.value = true;
+  
+  // 3秒后自动隐藏消息
+  setTimeout(() => {
+    showError.value = false;
+  }, 3000);
+};
+
 // ============ 登录逻辑 ============
 const onSubmit = async () => {
   if (!formRef.value) return;
+  
   try {
+    // 验证表单
     await formRef.value.validate();
     loading.value = true;
+    showError.value = false; // 开始验证时隐藏错误提示
 
-    // 1) 这里把 username 改为 name,跟后端一致
-    const res = await login({
+    // 调用登录API
+    const response = await login({
       name: form.name,
       password: form.password,
       usertype: userType.value,
     });
 
-    // 2) 按你的后端返回结构取值
-    const ok = res?.data?.success === true;
-    if (!ok) {
-      ElMessage.error(res?.data?.message || t("login.loginFailed"));
-      return;
+    console.log('完整登录响应:', response);
+
+    // 检查响应结构
+    if (!response.data?.user) {
+      throw new Error('后端返回的用户信息不完整');
     }
 
-    const userId = res.data.userId;
-    const name = res.data.name;
+    // 提取用户信息
+    const userData = response.data.user;
+
+    if (!userData.id || !userData.name) {
+      throw new Error('缺少必要的用户字段');
+    }
 
-    // 3) 保存“已登录”状态(按你的 store 需要来)
-    //    如果你的全局守卫检查 token,这里塞一个标记防止被拦截
+    // 保存用户信息到store
     store.saveToken({
-      userId,
-      name,
-      loginType: userType.value,   // 后端没返回 userType,就以当前选择为准
+      userId: Number(userData.id),
+      name: userData.name,
+      loginType: userData.userType || userType.value, // 优先使用后端返回的userType
     });
 
-    ElMessage.success(res.data?.message || t("login.loginSuccess"));
-
-    // 4) 跳转(建议 await,能捕获潜在错误)
-    await router.push({ name: "selectCityAndCounty" });
+    // 跳转到目标页面
+    await router.push({ name: 'samplingMethodDevice1' });
 
   } catch (error: any) {
-    console.error("登录异常:", error);
-    ElMessage.error(error?.response?.data?.detail || t("login.loginFailed"));
+    console.error('登录失败:', {
+      error: error.message,
+      stack: error.stack,
+      response: error.response?.data
+    });
+    
+    // 显示错误消息
+    const errorMsg = error.response?.data.message || 
+                    error.message || 
+                    '登录失败,请检查用户名和密码';
+    showErrorMsg(errorMsg);
   } finally {
     loading.value = false;
   }
@@ -238,6 +275,7 @@ const onRegister = async () => {
   try {
     await registerFormRef.value.validate();
     loading.value = true;
+    showError.value = false; // 开始验证时隐藏错误提示
 
     const res = await register({
       name: registerForm.name,
@@ -246,15 +284,46 @@ const onRegister = async () => {
     });
 
     if (res.data?.message) {
-      ElMessage.success(res.data.message);
-      toggleForm();
+      // 注册成功后自动登录
+      try {
+        // 调用登录API
+        const loginResponse = await login({
+          name: registerForm.name,
+          password: registerForm.password,
+          usertype: userType.value,
+        });
+
+        // 检查登录响应结构
+        if (loginResponse.data?.user) {
+          const userData = loginResponse.data.user;
+          
+          // 保存用户信息到store
+          store.saveToken({
+            userId: Number(userData.id),
+            name: userData.name,
+            loginType: userData.userType || userType.value,
+          });
+
+          // 跳转到目标页面
+          await router.push({ name: 'samplingMethodDevice1' });
+        } else {
+          showErrorMsg(loginResponse.data?.message || '自动登录失败,请手动登录');
+          toggleForm(); // 返回登录页面
+        }
+      } catch (loginError: any) {
+        console.error('自动登录失败:', loginError);
+        showErrorMsg(
+          loginError?.response?.data?.message || '自动登录失败,请手动登录'
+        );
+        toggleForm(); // 返回登录页面
+      }
     } else {
-      ElMessage.error(res.data?.message || t("register.registerFailed"));
+      showErrorMsg(res.data?.message || t("register.registerFailed"));
     }
   } catch (error: any) {
     console.error("注册异常:", error);
-    ElMessage.error(
-      error?.response?.data?.detail || t("register.registerFailed")
+    showErrorMsg(
+      error?.response?.data?.message || t("register.registerFailed")
     );
   } finally {
     loading.value = false;
@@ -336,6 +405,7 @@ onMounted(() => {
   display: flex;
   height: 100vh;
   background-color: #f6f6f6;
+  position: relative;
 }
 .auth-left {
   width: 35%;
@@ -449,4 +519,18 @@ onMounted(() => {
   padding: 15px 10px;
   margin-bottom: 20px;
 }
-</style>
+
+/* 错误提示样式 */
+.error-message {
+  color: #f56c6c;
+  font-size: 16px;
+  text-align: center;
+  margin: 15px 0;
+  animation: fadeIn 0.3s ease-out forwards;
+}
+
+@keyframes fadeIn {
+  from { opacity: 0; }
+  to { opacity: 1; }
+}
+</style>

Some files were not shown because too many files changed in this diff