Просмотр исходного кода

修改页面样式,统一接口调用方式

yangtaodemon 3 месяцев назад
Родитель
Сommit
7e5678d942
62 измененных файлов с 2432 добавлено и 877 удалено
  1. 3 0
      .env.development
  2. 3 0
      .env.production
  3. 0 2
      components.d.ts
  4. 16 33
      src/API/admin.ts
  5. 4 4
      src/API/menus.ts
  6. 8 8
      src/API/users.ts
  7. 0 1
      src/App.vue
  8. 18 22
      src/components/atmpollution/airsampleChart.vue
  9. 19 20
      src/components/atmpollution/airsampleLine.vue
  10. 18 18
      src/components/atmpollution/atmcompanyline.vue
  11. 11 8
      src/components/atmpollution/atmcompanymap.vue
  12. 52 53
      src/components/atmpollution/atmsamplemap.vue
  13. 13 12
      src/components/atmpollution/heavyMetalEnterprisechart.vue
  14. 104 54
      src/components/cdStatictics/reducedataStatistics.vue
  15. 98 16
      src/components/cdStatictics/refluxcdStatictics.vue
  16. 64 20
      src/components/detectionStatistics/atmcompanyStatics.vue
  17. 3 3
      src/components/detectionStatistics/atmsampleStatistics.vue
  18. 68 22
      src/components/detectionStatistics/crosscetionStatistics.vue
  19. 48 21
      src/components/detectionStatistics/irrigationstatistics.vue
  20. 19 24
      src/components/irrpollution/crossSectionSamplelineData.vue
  21. 16 16
      src/components/irrpollution/crossSetionData1.vue
  22. 68 16
      src/components/irrpollution/crossSetionData2.vue
  23. 5 7
      src/components/irrpollution/crosssectionmap.vue
  24. 6 8
      src/components/irrpollution/irrwatermap.vue
  25. 2 2
      src/components/irrpollution/tencentMapView.vue
  26. 1 1
      src/components/irrpollution/waterassaydata1.vue
  27. 49 9
      src/components/irrpollution/waterassaydata2.vue
  28. 1 1
      src/components/irrpollution/waterassaydata3.vue
  29. 2 2
      src/components/irrpollution/waterassaydata4.vue
  30. 58 7
      src/components/irrpollution/waterdataline.vue
  31. 7 0
      src/components/layout/AppLayout.vue
  32. 32 4
      src/components/layout/menuItems.ts
  33. 16 17
      src/components/soilcdStatistics/cropcdStatictics.vue
  34. 109 33
      src/components/soilcdStatistics/effcdStatistics.vue
  35. 62 41
      src/components/soilcdStatistics/fluxcdStatictics.vue
  36. 14 0
      src/router/index.ts
  37. 1 0
      src/stores/mytoken.ts
  38. 140 41
      src/utils/request.ts
  39. 29 42
      src/views/User/HmOutFlux/agriInput/prodInputFlux.vue
  40. 77 52
      src/views/User/HmOutFlux/irrigationWater/irriWaterInputFlux.vue
  41. 450 0
      src/views/User/HmOutFlux/totalInputFluxDesc.vue
  42. 25 16
      src/views/User/acidModel/Calculation.vue
  43. 5 3
      src/views/User/acidModel/ModelIterationVisualization.vue
  44. 12 12
      src/views/User/cadmiumPrediction/CropCadmiumPrediction.vue
  45. 12 12
      src/views/User/cadmiumPrediction/EffectiveCadmiumPrediction.vue
  46. 3 3
      src/views/User/cadmiumPrediction/TotalCadmiumPrediction.vue
  47. 11 15
      src/views/User/cadmiumPrediction/currentYearConcentration.vue
  48. 13 20
      src/views/User/cadmiumPrediction/netFlux.vue
  49. 16 14
      src/views/User/cadmiumPrediction/totalInputFlux.vue
  50. 11 11
      src/views/User/cadmiumPrediction/totalOutputFlux.vue
  51. 46 21
      src/views/User/dataStatistics/LandCultivatedStatistics.vue
  52. 5 5
      src/views/User/farmlandQualityAssessment/farmlandQualityAssessment.vue
  53. 0 7
      src/views/User/heavyMetalFluxCalculation/inputFluxCalculation/irrigationWater.vue
  54. 13 21
      src/views/User/hmInFlux/grainRemoval/grainRemovalInputFlux.vue
  55. 13 22
      src/views/User/hmInFlux/strawRemoval/strawRemovalInputFlux.vue
  56. 5 5
      src/views/User/hmInFlux/subsurfaceLeakage/samplingDesc3.vue
  57. 13 14
      src/views/User/hmInFlux/subsurfaceLeakage/subsurfaceLeakageInputFlux.vue
  58. 13 15
      src/views/User/hmInFlux/surfaceRunoff/surfaceRunoffInputFlux.vue
  59. 463 0
      src/views/User/hmInFlux/totalOutputFluxDesc.vue
  60. 2 2
      src/views/User/neutralizationModel/AcidNeutralizationModel.vue
  61. 3 3
      src/views/User/neutralizationModel/ModelIterationVisualization.vue
  62. 34 16
      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 - 2
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']

+ 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;
   }
@@ -485,4 +481,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);
 
@@ -389,4 +390,4 @@ onUnmounted(() => window.removeEventListener('resize', handleResize));
   color: #1890ff;
   z-index: 10;
 }
-</style>
+</style>

+ 104 - 54
src/components/cdStatictics/reducedataStatistics.vue

@@ -1,28 +1,32 @@
 <template>
   <div class="chart-container">
-
     <div v-if="loading" class="loading">
       <p>数据加载中...</p>
     </div>
+    <div v-if="error" class="error">
+      <p>{{ error }}</p>
+      <button @click="fetchData">重试</button>
+    </div>
     <div ref="chartRef" class="chart"></div>
   </div>
 </template>
 
 <script>
-import { ref, onMounted, onUnmounted, watch ,nextTick} from 'vue';
+import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
 import * as echarts from 'echarts';
+import { api5000 } from '@/utils/request'; // 导入 api5000 实例
 
 export default {
   name: 'HeavyMetalChart',
   setup() {
-    // 接口数据
+    // 接口数据 - 使用相对路径
     const interfaces = [
-      { time: '20241229_201018', url: 'http://localhost:5000/api/table-averages?table_name=dataset_5' },
-      { time: '20250104_171827', url: 'http://localhost:5000/api/table-averages?table_name=dataset_35' },
-      { time: '20250104_171959', url: 'http://localhost:5000/api/table-averages?table_name=dataset_36' },
-      { time: '20250104_214026', url: 'http://localhost:5000/api/table-averages?table_name=dataset_37' },
-      { time: '20250308_161945', url: 'http://localhost:5000/api/table-averages?table_name=dataset_65' },
-      { time: '20250308_163248', url: 'http://localhost:5000/api/table-averages?table_name=dataset_66' },
+      { time: '20241229_201018', url: '/api/table-averages?table_name=dataset_5' },
+      { time: '20250104_171827', url: '/api/table-averages?table_name=dataset_35' },
+      { time: '20250104_171959', url: '/api/table-averages?table_name=dataset_36' },
+      { time: '20250104_214026', url: '/api/table-averages?table_name=dataset_37' },
+      { time: '20250308_161945', url: '/api/table-averages?table_name=dataset_65' },
+      { time: '20250308_163248', url: '/api/table-averages?table_name=dataset_66' },
     ];
     
     const chartRef = ref(null);
@@ -52,43 +56,38 @@ export default {
         loading.value = true;
         error.value = null;
         
-        // 尝试获取真实数据,失败则使用模拟数据
-        try {
-          const responses = await Promise.all(
-            interfaces.map(intf => 
-              fetch(intf.url)
-                .then(response => {
-                  if (!response.ok) throw new Error(`网络响应错误: ${response.status}`);
-                  return response.json();
-                })
-                .then(data => {
-                  if (data.success !== "true" && data.success !== true) {
-                    throw new Error('接口返回失败状态');
-                  }
-                  return { time: intf.time, data: data.averages };
-                })
-            )
-          );
-          
-          // 处理数据 - 直接使用原始时间字符串
-          chartData.value.timestamps = responses.map(item => item.time);
-          
-          // 初始化所有数据系列,并将数值固定为小数点后五位
-          Object.keys(chartData.value.series).forEach(key => {
-            chartData.value.series[key] = responses.map(item => {
-              const value = parseFloat(item.data[key]);
-              return isNaN(value) ? null : parseFloat(value.toFixed(5));
-            });
+        // 使用 api5000 实例获取数据
+        const responses = await Promise.all(
+          interfaces.map(intf => 
+            api5000.get(intf.url)
+              .then(response => {
+                const data = response.data;
+                if (data.success !== "true" && data.success !== true) {
+                  throw new Error('接口返回失败状态');
+                }
+                return { time: intf.time, data: data.averages };
+              })
+              .catch(err => {
+                throw new Error(`请求失败: ${err.message}`);
+              })
+          )
+        );
+        
+        // 处理数据 - 直接使用原始时间字符串
+        chartData.value.timestamps = responses.map(item => item.time);
+        
+        // 初始化所有数据系列,并将数值固定为小数点后五位
+        Object.keys(chartData.value.series).forEach(key => {
+          chartData.value.series[key] = responses.map(item => {
+            const value = parseFloat(item.data[key]);
+            return isNaN(value) ? null : parseFloat(value.toFixed(5));
           });
-        } catch (err) {
-          error.value = '获取真实数据失败: ' + err.message;
-          chartData.value = generateMockData();
-        }
+        });
         
         renderChart();
         lastUpdate.value = new Date().toLocaleString();
       } catch (err) {
-        error.value = err.message || '获取数据失败';
+        error.value = '获取数据失败: ' + err.message;
         console.error('获取数据错误:', err);
       } finally {
         loading.value = false;
@@ -177,7 +176,12 @@ export default {
           type: 'category',
           boundaryGap: false,
           data: chartData.value.timestamps, // 直接使用原始时间字符串
-          axisLabel:{rotate:30}
+          axisLabel: {
+            rotate: 30,
+            fontSize: 12,
+            interval: 0, // 强制显示所有标签
+            formatter: (value) => value.replace('_', ' ') // 优化时间显示
+          }
         },
         yAxis: {
           type: 'value',
@@ -189,7 +193,8 @@ export default {
             }
           }
         },
-        series: series
+        series: series,
+        color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272'] // 自定义颜色方案
       };
       
       chartInstance.setOption(option);
@@ -203,28 +208,31 @@ export default {
     
     onMounted(() => {
       fetchData().then(() => {
-        // 等待 Vue 完成 DOM 更新(如父元素布局、样式生效)
+        // 等待 Vue 完成 DOM 更新
         nextTick(() => {
           if (chartInstance) {
-            chartInstance.resize(); // 确保图表适配最终尺寸
+            chartInstance.resize();
           }
         });
       });
 
-      // 保留原有窗口 resize 监听
-      window.addEventListener('resize', () => {
+      // 窗口 resize 监听
+      const handleResize = () => {
         if (chartInstance) {
           chartInstance.resize();
         }
+      };
+      
+      window.addEventListener('resize', handleResize);
+      
+      onUnmounted(() => {
+        window.removeEventListener('resize', handleResize);
+        if (chartInstance) {
+          chartInstance.dispose();
+        }
       });
     });
     
-    onUnmounted(() => {
-      if (chartInstance) {
-        chartInstance.dispose();
-      }
-    });
-    
     // 监听显示选项变化
     watch([selectedData, displayOption], () => {
       renderChart();
@@ -238,7 +246,8 @@ export default {
       selectedData,
       displayOption,
       lastUpdate,
-      refreshData
+      refreshData,
+      fetchData // 暴露fetchData用于重试
     };
   }
 }
@@ -250,6 +259,7 @@ export default {
   padding: 20px;
   height: 470px;
   box-sizing: border-box;
+  position: relative;
 }
 
 .chart {
@@ -257,4 +267,44 @@ export default {
   height: 100%;
   margin-bottom: 20px;
 }
+
+.loading {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(255, 255, 255, 0.8);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 10;
+}
+
+.error {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(255, 255, 255, 0.8);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  z-index: 10;
+  color: #f56c6c;
+  padding: 20px;
+  text-align: center;
+}
+
+.error button {
+  margin-top: 15px;
+  padding: 8px 16px;
+  background: #f56c6c;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+}
 </style>

+ 98 - 16
src/components/cdStatictics/refluxcdStatictics.vue

@@ -1,6 +1,14 @@
 <template>
   <div class="container">    
     <div class="chart-container">
+      <div v-if="isLoading" class="loading-overlay">
+        <div class="spinner"></div>
+        <span class="loading-text">数据加载中...</span>
+      </div>
+      <div v-if="error" class="error-overlay">
+        <p>{{ errorMessage }}</p>
+        <button class="retry-btn" @click="loadData">重试</button>
+      </div>
       <div ref="chartRef" class="chart-wrapper"></div>
     </div>
   </div>
@@ -9,21 +17,16 @@
 <script setup>
 import { ref, onMounted, onUnmounted, nextTick } from 'vue' 
 import * as echarts from 'echarts' 
+import { api5000 } from '@/utils/request'; // 导入 api5000 实例
 
 const indicators = [
-  //{ key: 'Al3_plus', name: 'Al3_plus', color: '#3498db' },
-  //{ key: 'CEC', name: 'CEC', color: '#2ecc71' },
-//  { key: 'CL', name: 'CL', color: '#e74c3c' },
-  { key: 'Delta_pH', name: 'Delta_pH', color: '#f39c12' },
-  //{ key: 'H_plus', name: 'H_plus', color: '#9b59b6' },
-  //{ key: 'N', name: 'N', color: '#1abc9c' },
-  //{ key: 'OM', name: 'OM', color: '#d35400' }
+  { key: 'Delta_pH', name: 'Delta_pH', color: '#f39c12' }
 ];
 
 let chart = ref(null) // 存储 ECharts 实例
 let chartData = ref([]) // 存储图表数据
 const chartRef = ref(null) // 图表容器
-const statsByIndex = ref([]);//存储每个指标的统计量
+const statsByIndex = ref([]); // 存储每个指标的统计量
 const isLoading = ref(true);
 const error = ref(false);
 const errorMessage = ref('');
@@ -32,13 +35,9 @@ function initChart() {
   // 确保图表容器已渲染
   if (!chartRef.value) return;
   
- 
-  //console.log('图表容器宽高:', chartRef.value.clientWidth, chartRef.value.clientHeight);
-  
   // 初始化 ECharts 实例
   chart.value = echarts.init(chartRef.value);
   
-
   const option = {
     tooltip: {
       trigger: 'item',
@@ -181,15 +180,28 @@ function calculateBoxplotStats(data, indicators) {
 async function loadData() {
   isLoading.value = true;
   error.value = false;
+  errorMessage.value = '';
   
   try {
-    const response = await fetch('http://localhost:5000/api/table-data?table_name=dataset_60');
-    const result = await response.json();
+    // 使用 api5000 实例获取数据
+    const response = await api5000.get('/api/table-data?table_name=dataset_60');
+    const result = response.data;
+    
+    // 检查响应是否成功
+    if (!result || !result.data) {
+      throw new Error('接口返回数据格式不正确');
+    }
+    
     chartData.value = result.data;
 
     const { boxplotData, statsArray } = calculateBoxplotStats(chartData.value, indicators);
     statsByIndex.value = statsArray;
     
+    // 确保图表已初始化
+    if (!chart.value) {
+      initChart();
+    }
+    
     chart.value.setOption({
       series: [{
         data: boxplotData
@@ -246,8 +258,8 @@ onUnmounted(() => {
   overflow: hidden;
 }
 .chart-container {
-    width: 100%;
-    height: 100%;
+  width: 100%;
+  height: 100%;
   padding: 20px;
   position: relative;
 }
@@ -256,4 +268,74 @@ onUnmounted(() => {
   height: 100%; 
   min-height: 200px;
 }
+
+.loading-overlay {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(255, 255, 255, 0.8);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  z-index: 10;
+}
+
+.spinner {
+  width: 40px;
+  height: 40px;
+  border: 4px solid rgba(0, 0, 0, 0.1);
+  border-radius: 50%;
+  border-left-color: #3498db;
+  animation: spin 1s linear infinite;
+}
+
+.loading-text {
+  margin-top: 15px;
+  font-size: 16px;
+  color: #2c3e50;
+}
+
+.error-overlay {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(255, 255, 255, 0.9);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  z-index: 10;
+  padding: 20px;
+  text-align: center;
+}
+
+.error-overlay p {
+  color: #e74c3c;
+  font-size: 16px;
+  margin-bottom: 20px;
+}
+
+.retry-btn {
+  padding: 8px 16px;
+  background: #3498db;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+  transition: background 0.3s;
+}
+
+.retry-btn:hover {
+  background: #2980b9;
+}
+
+@keyframes spin {
+  to { transform: rotate(360deg); }
+}
 </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;

+ 3 - 3
src/components/detectionStatistics/atmsampleStatistics.vue

@@ -27,14 +27,14 @@
 <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 {
   components: { VChart },
   setup() {
     // -------- 核心配置 --------
-    const apiUrl = ref('http://localhost:8000/api/vector/export/all?table_name=Atmo_sample_data')
+    const apiUrl = ref('/api/vector/export/all?table_name=Atmo_sample_data') // 使用相对路径
     const heavyMetals = [
       { key: 'Cr_particulate', name: '铬 (Cr)', color: '#FF9800' },
       { key: 'As_particulate', name: '砷 (As)', color: '#4CAF50' },
@@ -198,7 +198,7 @@ export default {
    onMounted(async () => {
   try {
     //log('发起API请求...')
-    const response = await axios.get(apiUrl.value)
+    const response = await api8000.get(apiUrl.value) // 使用 api8000 实例
     //console.log('接口原始响应:', response.data) // 调试必看!
 
     let data = response.data

+ 68 - 22
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>
             `;
           }
@@ -203,8 +249,8 @@ export default {
         grid: {
           left: 0,
           right: '15%',
-          top: 20, // 缩减顶部边距
-          bottom: 10, // 缩减底部边距
+          top: 20,
+          bottom: 10,
           containLabel: true
         },
         xAxis: {
@@ -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%',

+ 48 - 21
src/components/detectionStatistics/irrigationstatistics.vue

@@ -1,42 +1,40 @@
 <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 { api8000 } from '@/utils/request' // 导入 api8000 实例
 import { ref, onMounted ,computed} from 'vue'
 
 export default {
   components: { VChart },
   setup() {
     // -------- 基本状态 --------
-    const apiUrl = ref('http://localhost:8000/api/vector/export/all?table_name=water_sampling_data')
+    const apiUrl = ref('/api/vector/export/all?table_name=water_sampling_data') // 使用相对路径
     const apiTimestamp = ref(null)
     const sampleCount = ref(0)
 
@@ -201,12 +199,41 @@ export default {
     onMounted(async () => {
       try {
         //log('发起API请求...')
-        const response = await axios.get(apiUrl.value)
+        const response = await api8000.get(apiUrl.value) // 使用 api8000 实例
         apiTimestamp.value = new Date().toLocaleString()
+        
+        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);
+        }
 
-        if (response.data && response.data.features) {
+        if (data && data.features) {
           // 你的接口:GeoJSON -> features[].properties
-          sampleData.value = response.data.features.map(f => f.properties)
+          sampleData.value = data.features.map(f => f.properties)
           sampleCount.value = sampleData.value.length
           //log(`接口返回数据量: ${sampleCount.value} 条`, '接口')
           initChart()

+ 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>

+ 16 - 16
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
@@ -157,7 +152,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',
@@ -216,7 +216,7 @@ onBeforeUnmount(() => {
 
 .chart-container {
   width: 100%; 
-  height: 400px;
+  height: 500px; /* 增加高度以容纳旋转的标签 */
   margin: 0 auto;
   border-radius: 12px;
 }

+ 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) => {

+ 1 - 1
src/components/irrpollution/waterassaydata1.vue

@@ -175,7 +175,7 @@ const renderBoxplot = () => {
 const loadData = async () => {
   try {
     const response = await axios.get<any[]>(
-      'http://localhost:3000/table/Water_assay_data',
+      'https://www.soilgd.com:3000/table/Water_assay_data',
       { timeout: 5000 }
     );
     

+ 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;

+ 1 - 1
src/components/irrpollution/waterassaydata3.vue

@@ -13,7 +13,7 @@ import Chart from 'chart.js/auto';
 import axios from 'axios';
 
 // ========== 接口配置(和柱状图对齐) ==========
-const ASSAY_API = 'http://localhost:3000/table/Water_assay_data'; // 复用柱状图的接口
+const ASSAY_API = 'https://www.soilgd.com:3000/table/Water_assay_data'; // 复用柱状图的接口
 
 // ========== 配置项(模仿柱状图) ==========
 const EXCLUDE_FIELDS = [

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

@@ -24,8 +24,8 @@ import * as echarts from 'echarts';
 import axios from 'axios';
 
 // ========== 接口配置 ==========
-const SAMPLING_API = 'http://localhost:3000/table/Water_sampling_data';
-const ASSAY_API = 'http://localhost:3000/table/Water_assay_data';
+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 = [

+ 58 - 7
src/components/irrpollution/waterdataline.vue

@@ -99,7 +99,7 @@
 
 <script setup>
 import { ref, computed, onMounted } from 'vue';
-import axios from 'axios';
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
 // 适配接口的字段映射
 const displayColumns = ref([
@@ -140,12 +140,47 @@ 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'
-    );
-    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;
   }
@@ -165,8 +200,24 @@ const sortedData = computed(() => {
   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;
   });
 });

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

@@ -142,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",
@@ -288,6 +290,9 @@ const tabs = computed(() => {
           "/heavyMetalEnterprise",
           "/airSampleData",
           "/airInputFlux",
+          "/totalInputFlux",
+          "/totalInputFluxDesc"
+
         ],
       },
       {
@@ -303,6 +308,8 @@ const tabs = computed(() => {
           "/subsurfaceLeakageInputFlux",
           "/samplingDesc4",
           "/surfaceRunoffInputFlux",
+           "/totalOutputFlux",
+          "/totalOutputFluxDesc"
         ],
       },
       /*{

+ 32 - 4
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',

+ 16 - 17
src/components/soilcdStatistics/cropcdStatictics.vue

@@ -54,21 +54,18 @@
 <script setup>
 import { ref, onMounted, watch, 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("");
@@ -132,12 +129,11 @@ const fieldConfig = {
   ]
 };
 
-
 // 数据请求(作物态Cd接口)
 const fetchData = async () => {
   try {
-    const apiUrl = 'http://localhost:8000/api/vector/export/all?table_name=CropCd_input_data&format=json';
-    const response = await axios.get(apiUrl);
+    const apiUrl = '/api/vector/export/all?table_name=CropCd_input_data&format=json';
+    const response = await api8000.get(apiUrl);
     
     // 接口返回格式判断(GeoJSON或直接数组)
     const rawData = response.data.features 
@@ -173,22 +169,28 @@ const calculateFieldStats = (data, fieldKey, fieldName) => {
     ...fieldConfig.nutrient,
     ...fieldConfig.extra
   ].find(f => f.key === fieldKey);
+  
   // 提取并清洗数据
   const rawValues = data.map(item => item[fieldKey]);
   const values = rawValues
     .map((val, idx) => {
       let num = Number(val);
+      
+      // 处理无效值
+      if (isNaN(num)) {
+        return null;
+      }
+      
       // 应用单位转换
       if (fieldConfigItem?.convert && fieldConfigItem.conversionFactor) {
         num = num * fieldConfigItem.conversionFactor;
       }
-      //if (isNaN(num)) log(`无效数据: 第${idx+1}条 → ${val}`, fieldName);
-      return isNaN(num) ? null : num;
+      
+      return num;
     })
     .filter(v => v !== null);
   
   if (values.length === 0) {
-    //log(`无有效数据`, fieldName);
     return { key: fieldKey, name: fieldName, min: null, q1: null, median: null, q3: null, max: null };
   }
   
@@ -218,9 +220,8 @@ const calculateFieldStats = (data, fieldKey, fieldName) => {
 const calculateAllStats = (data) => {
   // 1. 作物态Cd指标统计
   pollutionStats.value = fieldConfig.pollution.map(field => {
-     const stats =calculateFieldStats(data, field.key, field.name);
-    //console.log(`${field.name}统计结果:`,stats);
-    return stats;
+     const stats = calculateFieldStats(data, field.key, field.name);
+     return stats;
   });
   
   // 2. 养分元素统计
@@ -277,7 +278,6 @@ const initPollutionChart = () => {
       data: barData
     }]
   });
-  
 };
 
 // 初始化养分元素图表
@@ -348,7 +348,6 @@ const initExtraChart = () => {
       });
 };
 
-
 // 格式化Tooltip
 const formatTooltip = (stat , unit ='') => {
   if (!stat || !stat.min) {
@@ -392,7 +391,7 @@ onMounted(() => {
   
   // 窗口resize处理
   const handleResize = () => {
-    [chartInstanceCd, chartInstanceNutrient, chartInstanceExtra, chartInstancePopup]
+    [chartInstanceCd, chartInstanceNutrient, chartInstanceExtra]
       .forEach(inst => inst && inst.resize());
   };
   window.addEventListener('resize', handleResize);
@@ -400,7 +399,7 @@ onMounted(() => {
   // 组件卸载清理
   return () => {
     window.removeEventListener('resize', handleResize);
-    [chartInstanceCd, chartInstanceNutrient, chartInstanceExtra, chartInstancePopup]
+    [chartInstanceCd, chartInstanceNutrient, chartInstanceExtra]
       .forEach(inst => inst && inst.dispose());
   };
 });

+ 109 - 33
src/components/soilcdStatistics/effcdStatistics.vue

@@ -46,7 +46,7 @@
 <script setup>
 import { ref, onMounted, watch, nextTick } from 'vue';
 import * as echarts from 'echarts';
-import axios from 'axios';
+import { api8000 } from '@/utils/request'; // 导入 api8000 实例
 
 // 图表实例引用
 const cdBarChart = ref(null);
@@ -58,7 +58,6 @@ let chartInstanceCd = null;
 let chartInstanceNutrient = null;
 let chartInstanceExtra = null;
 
-
 // 响应式状态
 const isLoading = ref(true);
 const error = ref(null);
@@ -101,14 +100,47 @@ const fieldConfig = {
   ]
 };
 
-
 // 数据请求
 const fetchData = async () => {
   try {
-    // 实际项目中替换为真实API
-     const res = await axios.get("http://localhost:8000/api/vector/export/all?table_name=EffCd_input_data&format=json");
-     return res.data;
-
+    const apiUrl = '/api/vector/export/all?table_name=EffCd_input_data&format=json';
+    const response = await api8000.get(apiUrl); // 使用 api8000 实例
+    
+    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;
+      
+    return rawData;
   } catch (err) {
     throw new Error('数据加载失败: ' + err.message);
   }
@@ -139,21 +171,28 @@ const calculateFieldStats = (data, fieldKey, fieldName) => {
     ...fieldConfig.nutrient,
     ...fieldConfig.extra
   ].find(f => f.key === fieldKey);
+  
   // 提取并清洗数据
   const rawValues = data.map(item => item[fieldKey]);
   const values = rawValues
     .map((val, idx) => {
       let num = Number(val);
-      if(fieldConfigItem?.convert && fieldConfigItem.conversionFactor){
-        num = num*fieldConfigItem.conversionFactor;
+      
+      // 处理无效值
+      if (isNaN(num)) {
+        return null;
       }
-      //if (isNaN(num)) log(`无效数据: 第${idx+1}条 → ${val}`, fieldName);
-      return isNaN(num) ? null : num;
+      
+      // 应用单位转换
+      if (fieldConfigItem?.convert && fieldConfigItem.conversionFactor) {
+        num = num * fieldConfigItem.conversionFactor;
+      }
+      
+      return num;
     })
     .filter(v => v !== null);
   
   if (values.length === 0) {
-    //log(`无有效数据`, fieldName);
     return { key: fieldKey, name: fieldName, min: null, q1: null, median: null, q3: null, max: null };
   }
   
@@ -170,10 +209,6 @@ const calculateFieldStats = (data, fieldKey, fieldName) => {
   // 强制校正顺序(核心修复)
   const sortedStats = [min, q1, median, q3, max].sort((a, b) => a - b);
   
-  //log(`统计量: min=${finalStats.min.toFixed(4)}, q1=${finalStats.q1.toFixed(4)}, 
-    //median=${finalStats.median.toFixed(4)}, q3=${finalStats.q3.toFixed(4)}, max=${finalStats.max.toFixed(4)}`, 
-    //fieldName);
-  
   return {
     key: fieldKey,
     name: fieldName,
@@ -182,7 +217,7 @@ const calculateFieldStats = (data, fieldKey, fieldName) => {
     median: sortedStats[2],
     q3: sortedStats[3],
     max: sortedStats[4],
-    mean:mean
+    mean: mean
   };
 };
 
@@ -207,8 +242,8 @@ const calculateAllStats = (data) => {
   const totalCdStats = pollutionStats.value.find(s => s.key === 'TCd_IDW');
   const effCdStats = pollutionStats.value.find(s => s.key === 'Cdsolution');
   stats.value = {
-    totalCdAvg: totalCdStats ? (totalCdStats.min + totalCdStats.max) / 2 : 0, // 示例:用范围中点模拟平均值
-    effCdAvg: effCdStats ? (effCdStats.min + effCdStats.max) / 2 : 0,
+    totalCdAvg: totalCdStats ? totalCdStats.mean : 0,
+    effCdAvg: effCdStats ? effCdStats.mean : 0,
     samples: data.length
   };
 };
@@ -228,7 +263,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 } },
@@ -237,11 +272,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
     }]
   });
@@ -262,11 +311,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
     }]
   });
@@ -287,18 +350,31 @@ const initExtraChart = () => {
             formatter: (params) => formatTooltip(extraStats.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: '%', nameTextStyle: { fontSize: 12 }, axisLabel: { fontSize: 11 } },
+          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: '%', 
+            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) {
@@ -342,7 +418,7 @@ onMounted(() => {
   
   // 窗口 resize 处理
   const handleResize = () => {
-    [chartInstanceCd, chartInstanceNutrient, chartInstanceExtra, chartInstancePopup]
+    [chartInstanceCd, chartInstanceNutrient, chartInstanceExtra]
       .forEach(inst => inst && inst.resize());
   };
   window.addEventListener('resize', handleResize);
@@ -350,7 +426,7 @@ onMounted(() => {
   // 组件卸载清理
   return () => {
     window.removeEventListener('resize', handleResize);
-    [chartInstanceCd, chartInstanceNutrient, chartInstanceExtra, chartInstancePopup]
+    [chartInstanceCd, chartInstanceNutrient, chartInstanceExtra]
       .forEach(inst => inst && inst.dispose());
   };
 });
@@ -437,4 +513,4 @@ onMounted(() => {
   border-radius: 50%;
   margin-right: 5px;
 }
-</style>
+</style>

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

@@ -29,31 +29,31 @@
       </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图表
@@ -63,7 +63,6 @@ const chartInstanceOther = ref(null);     // 其他指标实例
 const chartInstancePopup = ref(null);     // 弹窗实例
 
 // 响应式状态
-
 const isLoading = ref(true);
 const error = ref(null);
 const stats = ref({ samples: 0 });
@@ -87,21 +86,46 @@ const fieldConfig = {
   ]
 };
 
-// 日志工具
-const log = (message, field = '') => {
-  console.log(`%c[${field || '全局'}] %c${message}`,
-    'color:#2196F3;font-weight:bold', 'color:#333');
-};
-
-
 // 数据请求
 const fetchData = async () => {
   try {
-    const apiUrl = 'http://localhost:8000/api/vector/export/all?table_name=FluxCd_input_data&format=json';
-    const response = await axios.get(apiUrl);
-    const rawData = response.data.features 
-      ? response.data.features.map(f => f.properties) 
-      : response.data;
+    const apiUrl = '/api/vector/export/all?table_name=FluxCd_input_data&format=json';
+    const response = await api8000.get(apiUrl); // 使用 api8000 实例
+    
+    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;
+      
     return rawData;
   } catch (err) {
     throw new Error('数据加载失败: ' + err.message);
@@ -131,13 +155,11 @@ const calculateFieldStats = (data, fieldKey, fieldName) => {
     .map((val, idx) => {
       // 更严格的数值校验
       if (val === null || val === undefined || val === '' || isNaN(Number(val))) {
-        log(`无效数据: ${fieldName} 第${idx+1}条 → ${val}`, fieldName);
         return null;
       }
       const num = Number(val);
       // 过滤极端值(根据业务需求调整阈值)
       if (num < -1000000 || num > 1000000) {
-        log(`极端值过滤: ${fieldName} 第${idx+1}条 → ${num}`, fieldName);
         return null;
       }
       return num;
@@ -146,7 +168,6 @@ const calculateFieldStats = (data, fieldKey, fieldName) => {
   
   // 处理无有效数据的情况
   if (values.length === 0) {
-    log(`无有效数据: ${fieldName}`, fieldName);
     return { key: fieldKey, name: fieldName, min: null, q1: null, median: null, q3: null, max: null };
   }
   
@@ -224,7 +245,6 @@ const initInitialCdChart = () => {
     const boxData = buildBoxplotData(initialCdStats.value);
     
     chartInstanceInitial.value.setOption({
-      // 保持原配置不变...
       title: { text: '初始Cd分布箱线图', left: 'center', textStyle: { fontSize: 14 } },
       tooltip: {
         trigger: "item",
@@ -259,7 +279,6 @@ const initInitialCdChart = () => {
   }
 };
 
-
 // 初始化【其他指标】合并图表
 const initOtherIndicatorsChart = () => {
   // 容器存在性检查
@@ -289,7 +308,6 @@ const initOtherIndicatorsChart = () => {
     const boxData = buildBoxplotData(otherIndicatorsStats.value);
     
     chartInstanceOther.value.setOption({
-      // 保持原配置不变...
       title: { text: '其他通量Cd指标分布对比', left: 'center', textStyle: { fontSize: 14 } },
       tooltip: {
         trigger: "item",
@@ -299,7 +317,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", 
@@ -323,8 +346,6 @@ const initOtherIndicatorsChart = () => {
   }
 };
 
-
-
 // Tooltip格式化(通用逻辑)
 const formatTooltip = (stat) => {
   if (!stat || !stat.min) {

+ 14 - 0
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",

+ 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: 300000,
+  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: 300000,
+  withCredentials: true
+});
+
+export const apiMain: CustomAxiosInstance = axios.create({
+  baseURL: isDevelopment 
+    ? 'http://localhost' 
+    : 'https://www.soilgd.com',
+  timeout: 300000,
+  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
+};

+ 29 - 42
src/views/User/HmOutFlux/agriInput/prodInputFlux.vue

@@ -6,98 +6,81 @@
         <div class="input-section">
           <el-form label-width="250px" label-position="left">
             <div class="form-section">
-              <!-- 单行布局,每行一个输入栏 -->
+              <!-- 第一行:氮肥的两个输入栏 -->
               <div class="input-row">
                 <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>
-              </div>
-              
-              <div class="input-row">
                 <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>
               
+              <!-- 第二行:磷肥的两个输入栏 -->
               <div class="input-row">
                 <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>
-              </div>
-              
-              <div class="input-row">
                 <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-row">
                 <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>
-              </div>
-              
-              <div class="input-row">
                 <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-row">
                 <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>
-              </div>
-              
-              <div class="input-row">
                 <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-row">
                 <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>
-              </div>
-              
-              <div class="input-row">
                 <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-row">
                 <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>
-              </div>
-              
-              <div class="input-row">
                 <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-row">
                 <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>
-              </div>
-              
-              <div class="input-row">
                 <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-row">
                 <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>
-              </div>
-              
-              <div class="input-row">
                 <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>
@@ -165,6 +148,7 @@
 import axios from 'axios';
 import * as echarts from 'echarts';
 import { ElNotification } from 'element-plus';
+import { api8000 } from '@/utils/request';  // 导入api8000
 
 export default {
   data() {
@@ -250,8 +234,8 @@ export default {
 
         // 调用计算并可视化接口(返回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: {
@@ -262,16 +246,15 @@ 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调用计算接口
+          api8000.post(
+            '/api/agricultural-input/calculate-with-custom-data',
             requestBody
           )
         ]);
-
         // 处理地图响应
         const blob = new Blob([mapResponse.data], { type: 'image/jpeg' });
         this.mapImageUrl = URL.createObjectURL(blob);
@@ -283,10 +266,7 @@ export default {
             data: calcResponse.data.data
           };
 
-          // 显示结果页面
           this.showInputForm = false;
-
-          // 初始化图表
           this.$nextTick(() => {
             this.initCharts();
           });
@@ -437,7 +417,7 @@ export default {
 
 .form-card {
   width: 90%;
-  max-width: 800px; /* 减小宽度使整体更紧凑 */
+  max-width: 1200px; /* 增加宽度以容纳两列 */
   margin: 0 auto;
   background: linear-gradient(135deg, #FAFDFF, #FFFAA2);
   border: 1px solid #e6e6e6;
@@ -476,15 +456,18 @@ export default {
   height: 100%;
 }
 
+/* 修改:输入行样式 - 一行两个输入栏 */
 .input-row {
   display: flex;
-  justify-content: center;
+  justify-content: space-between;
   margin-bottom: 15px;
   width: 100%;
+  gap: 20px; /* 添加间距 */
 }
 
 .form-item {
-  width: 100%; /* 宽度调整为100% */
+  flex: 1; /* 每个输入栏占据相同宽度 */
+  min-width: 0; /* 防止内容溢出 */
   margin-bottom: 0;
   display: flex;
   flex-direction: row; /* 横向排列标签和输入框 */
@@ -693,10 +676,14 @@ export default {
 }
 
 /* 响应式调整 */
-@media (max-width: 768px) {
-  .form-card {
-    width: 95%;
-    max-width: 100%;
+@media (max-width: 992px) {
+  .input-row {
+    flex-direction: column; /* 小屏幕下垂直排列 */
+    gap: 10px;
+  }
+  
+  .form-item {
+    width: 100%;
   }
   
   .el-form-item__label {

+ 77 - 52
src/views/User/HmOutFlux/irrigationWater/irriWaterInputFlux.vue

@@ -23,22 +23,24 @@
                 </div>
                 
                 <div class="input-group">
+                  <!-- 修改:将两个输入栏放在同一行 -->
                   <div class="input-row">
-                    <div class="input-title">灌溉水用量 (m³/亩/年)</div>
-                    <el-input
-                      v-model="irrigationWaterUsage"
-                      placeholder="请输入灌溉水用量"
-                      size="large"
-                    />
-                  </div>
-                  
-                  <div class="input-row">
-                    <div class="input-title">灌溉水有效利用率 (%)</div>
-                    <el-input
-                      v-model="irrigationEfficiency"
-                      placeholder="请输入灌溉水有效利用率"
-                      size="large"
-                    />
+                    <div class="input-column">
+                      <div class="input-title">灌溉水用量 (m³/亩/年)</div>
+                      <el-input
+                        v-model="irrigationWaterUsage"
+                        placeholder="请输入灌溉水用量"
+                        size="large"
+                      />
+                    </div>
+                    <div class="input-column">
+                      <div class="input-title">灌溉水有效利用率 (%)</div>
+                      <el-input
+                        v-model="irrigationEfficiency"
+                        placeholder="请输入灌溉水有效利用率"
+                        size="large"
+                      />
+                    </div>
                   </div>
                 </div>
               </div>
@@ -57,22 +59,24 @@
                 </div>
                 
                 <div class="input-group">
+                  <!-- 修改:将两个输入栏放在同一行 -->
                   <div class="input-row">
-                    <div class="input-title">灌溉水用量 (m³/亩/年)</div>
-                    <el-input
-                      v-model="irrigatedWaterUsage"
-                      placeholder="请输入灌溉水用量"
-                      size="large"
-                    />
-                  </div>
-                  
-                  <div class="input-row">
-                    <div class="input-title">灌溉水有效利用率 (%)</div>
-                    <el-input
-                      v-model="irrigatedEfficiency"
-                      placeholder="请输入灌溉水有效利用率"
-                      size="large"
-                    />
+                    <div class="input-column">
+                      <div class="input-title">灌溉水用量 (m³/亩/年)</div>
+                      <el-input
+                        v-model="irrigatedWaterUsage"
+                        placeholder="请输入灌溉水用量"
+                        size="large"
+                      />
+                    </div>
+                    <div class="input-column">
+                      <div class="input-title">灌溉水有效利用率 (%)</div>
+                      <el-input
+                        v-model="irrigatedEfficiency"
+                        placeholder="请输入灌溉水有效利用率"
+                        size="large"
+                      />
+                    </div>
                   </div>
                 </div>
               </div>
@@ -91,22 +95,24 @@
                 </div>
                 
                 <div class="input-group">
+                  <!-- 修改:将两个输入栏放在同一行 -->
                   <div class="input-row">
-                    <div class="input-title">灌溉水用量 (m³/亩/年)</div>
-                    <el-input
-                      v-model="dryWaterUsage"
-                      placeholder="请输入灌溉水用量"
-                      size="large"
-                    />
-                  </div>
-                  
-                  <div class="input-row">
-                    <div class="input-title">灌溉水有效利用率 (%)</div>
-                    <el-input
-                      v-model="dryEfficiency"
-                      placeholder="请输入灌溉水有效利用率"
-                      size="large"
-                    />
+                    <div class="input-column">
+                      <div class="input-title">灌溉水用量 (m³/亩/年)</div>
+                      <el-input
+                        v-model="dryWaterUsage"
+                        placeholder="请输入灌溉水用量"
+                        size="large"
+                      />
+                    </div>
+                    <div class="input-column">
+                      <div class="input-title">灌溉水有效利用率 (%)</div>
+                      <el-input
+                        v-model="dryEfficiency"
+                        placeholder="请输入灌溉水有效利用率"
+                        size="large"
+                      />
+                    </div>
                   </div>
                 </div>
               </div>
@@ -213,9 +219,9 @@
   </div>
 </template>
 
+// 修改后的script部分
 <script>
 import { ref } from 'vue';
-import axios from 'axios';
 import { 
   ElCheckbox,
   ElCheckboxGroup,
@@ -228,6 +234,7 @@ import {
   ElTable, 
   ElTableColumn 
 } from 'element-plus';
+import { api8000 } from '@/utils/request';  // 导入api8000
 
 // 土地类型映射
 const landTypeMap = {
@@ -284,7 +291,7 @@ export default {
     // 获取默认地图
     const fetchDefaultMap = async (landTypeChinese) => {
       try {
-        const response = await axios.get('http://localhost:8000/api/water/default-map', {
+        const response = await api8000.get('/api/water/default-map', {
           params: { land_type: landTypeChinese },
           responseType: 'blob' // 接收二进制数据
         });
@@ -301,13 +308,13 @@ export default {
     // 获取默认直方图
     const fetchDefaultHistogram = async (landTypeChinese) => {
       try {
-        const response = await axios.get('http://localhost:8000/api/water/default-histogram', {
+        const response = await api8000.get('/api/water/default-histogram', {
           params: { land_type: landTypeChinese },
           responseType: 'blob' // 接收二进制数据
         });
         
         // 创建对象URL
-        return URL.createObjectURL(response.data);
+        return URL.createObjectURL(response.data)
       } catch (error) {
         console.error('获取默认直方图失败:', error);
         ElMessage.error('获取直方图失败,请重试');
@@ -318,7 +325,7 @@ export default {
     // 获取统计数据
     const fetchStatistics = async (landTypeChinese) => {
       try {
-        const response = await axios.get('http://localhost:8000/api/water/statistics', {
+        const response = await api8000.get('/api/water/statistics', {
           params: { land_type: landTypeChinese }
         });
         return response.data;
@@ -419,7 +426,7 @@ export default {
         formData.append('output_size', 8);
 
         // 调用计算接口
-        await axios.post('http://localhost:8000/api/water/calculate', formData, {
+        await api8000.post('/api/water/calculate', formData, {
           headers: {
             'Content-Type': 'multipart/form-data'
           }
@@ -698,10 +705,28 @@ export default {
   margin-top: 15px; /* 增加顶部间距 */
 }
 
+
 .input-row {
+  display: flex;
+  gap: 15px; /* 输入框之间的间距 */
   margin-bottom: 15px;
 }
 
+.input-column {
+  flex: 1; /* 每个输入栏占据相同宽度 */
+  min-width: 0; /* 防止内容溢出 */
+}
+
+/* 响应式调整:小屏幕下垂直排列 */
+@media (max-width: 992px) {
+  .input-row {
+    flex-direction: column;
+    gap: 10px;
+  }
+}
+
+
+
 /* 多选框样式增强 */
 .custom-checkbox {
   padding: 15px 20px;
@@ -714,7 +739,7 @@ export default {
 
 .custom-checkbox:hover {
   transform: translateY(-2px);
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  box-shadow: 0 4px  12px rgba(0, 0, 0, 0.15);
   border-color: #1a8cff;
 }
 

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

@@ -0,0 +1,450 @@
+<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>是指通过灌溉水、农业投入品和大气沉降三种主要途径
+            进入农田生态系统的重金属总量,通常以<span class="highlight">克/公顷/年(g/ha/a)</span>为单位表示。
+          </p>
+          <p>
+            该指标综合反映了农田系统从外部环境输入的重金属污染负荷,是评估农田重金属污染风险的重要依据。
+          </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 class="content-section">
+      <div class="section-header">
+        <div class="section-number">2</div>
+        <h2>各输入途径贡献占比</h2>
+      </div>
+      
+      <div class="contribution-container">
+        <div class="contribution-card">
+          <div class="contribution-icon">💧</div>
+          <div class="contribution-content">
+            <h3>灌溉水输入</h3>
+            <p>主要受水质和灌溉量影响</p>
+            <p>计算公式:F<sub>灌溉水</sub> = Q × C × η</p>
+          </div>
+        </div>
+        
+        <div class="contribution-card">
+          <div class="contribution-icon">🌾</div>
+          <div class="contribution-content">
+            <h3>农业投入品</h3>
+            <p>与肥料农药使用量直接相关</p>
+            <p>计算公式:F<sub>农业投入品</sub> = Σ(N<sub>i</sub> × C<sub>i</sub>)</p>
+          </div>
+        </div>
+        
+        <div class="contribution-card">
+          <div class="contribution-icon">☁️</div>
+          <div class="contribution-content">
+            <h3>大气沉降</h3>
+            <p>受周边工业排放影响显著</p>
+            <p>计算公式:F<sub>大气沉降</sub> = D × A × T</p>
+          </div>
+        </div>
+      </div>
+    </div>
+    
+    <!-- 第三部分:应用价值 -->
+    <div class="content-section">
+      <div class="section-header">
+        <div class="section-number">3</div>
+        <h2>输入总通量的应用价值</h2>
+      </div>
+      
+      <div class="application-container">
+        <div class="application-card">
+          <div class="application-icon">🛡️</div>
+          <div class="application-content">
+            <h3>污染风险评估</h3>
+            <p>通过量化重金属输入总量,评估农田生态系统污染风险等级</p>
+          </div>
+        </div>
+        
+        <div class="application-card">
+          <div class="application-icon">📉</div>
+          <div class="application-content">
+            <h3>污染源解析</h3>
+            <p>识别主要污染输入途径,为精准治理提供依据</p>
+          </div>
+        </div>
+        
+        <div class="application-card">
+          <div class="application-icon">🌱</div>
+          <div class="application-content">
+            <h3>安全种植指导</h3>
+            <p>根据输入通量水平调整种植结构和农艺措施</p>
+          </div>
+        </div>
+        
+        <div class="application-card">
+          <div class="application-icon">📝</div>
+          <div class="application-content">
+            <h3>政策制定参考</h3>
+            <p>为区域农田重金属污染防治政策提供数据支撑</p>
+          </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);
   }
 };
 

+ 5 - 3
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;

+ 12 - 12
src/views/User/cadmiumPrediction/CropCadmiumPrediction.vue

@@ -25,7 +25,7 @@
         <el-button class="custom-button" :disabled="!mapBlob" @click="exportMap">
           <el-icon class="upload-icon"><Download /></el-icon>
           导出地图</el-button>
-        <el-button class="custom-button" :disabled="!histogramBlob" @极简版click="exportHistogram">
+        <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">
@@ -116,7 +116,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 
@@ -198,8 +198,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' }
         );
         
@@ -214,8 +214,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' }
         );
         
@@ -393,8 +393,8 @@ export default {
       try {
         this.loadingStats = true;
         
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-prediction/crop-cd/statistics/${this.countyName}`
+        const response = await api8000.get(
+          `/api/cd-prediction/crop-cd/statistics/${this.countyName}`
         );
         
         if (response.data.success && response.data.data) {
@@ -437,8 +437,8 @@ export default {
         formData.append('data_file', this.selectedFile);
         
         // 调用作物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: {
@@ -514,8 +514,8 @@ export default {
       try {
         this.$message.info('正在获取作物Cd预测数据...');
         
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-prediction/download-final-crop-cd-csv`,
+        const response = await api8000.get(
+          `/api/cd-prediction/download-final-crop-cd-csv`,
           { responseType: 'blob' }
         );
         

+ 12 - 12
src/views/User/cadmiumPrediction/EffectiveCadmiumPrediction.vue

@@ -25,7 +25,7 @@
         <el-button class="custom-button" :disabled="!mapBlob" @click="exportMap">
           <el-icon class="upload-icon"><Download /></el-icon>
           导出地图</el-button>
-        <el-button class="custom-button" :disabled="!histogramBlob" @极简版click="exportHistogram">
+        <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">
@@ -116,7 +116,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 
@@ -198,8 +198,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' }
         );
         
@@ -214,8 +214,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' }
         );
         
@@ -393,8 +393,8 @@ export default {
       try {
         this.loadingStats = true;
         
-        const response = await axios.get(
-          `http://localhost:8000/api/cd-prediction/effective-cd/statistics/${this.countyName}`
+        const response = await api8000.get(
+          `/api/cd-prediction/effective-cd/statistics/${this.countyName}`
         );
         
         if (response.data.success && response.data.data) {
@@ -437,8 +437,8 @@ export default {
         formData.append('data_file', this.selectedFile);
         
         // 调用有效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: {
@@ -514,8 +514,8 @@ export default {
       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' }
         );
         

+ 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: {

+ 11 - 15
src/views/User/cadmiumPrediction/currentYearConcentration.vue

@@ -88,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 
@@ -167,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' }
         );
         
@@ -183,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' }
         );
         
@@ -213,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);
@@ -257,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: {
@@ -327,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' }
         );
         

+ 13 - 20
src/views/User/cadmiumPrediction/netFlux.vue

@@ -88,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 
@@ -128,10 +128,7 @@ export default {
     if (this.distributionChart) this.distributionChart.dispose();
   },
   methods: {
-    // 触发文件选择
-    triggerFileUpload() {
-      this.$refs.fileInput.click();
-    },
+   
     
     // 处理文件上传
     handleFileUpload(event) {
@@ -167,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' }
         );
         
@@ -183,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' }
         );
         
@@ -213,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);
@@ -257,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: {
@@ -327,15 +320,15 @@ 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' }
         );
         
         const blob = new Blob([response.data], { type: 'text/csv' });
         const link = document.createElement('a');
         link.href = URL.createObjectURL(blob);
-        link.download = `Cd净量数据.csv`;
+        link.download = `Cd净量数据.csv`;
         link.click();
         URL.revokeObjectURL(link.href);
         

+ 16 - 14
src/views/User/cadmiumPrediction/totalInputFlux.vue

@@ -90,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';
@@ -164,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' }
         );
         
@@ -179,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' }
         );
         
@@ -195,6 +195,8 @@ export default {
         this.$message.warning('获取最新直方图失败,请先执行预测');
       }
     },
+
+
     
     // 格式化统计数据
     formatStatisticsData(stats) {
@@ -208,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) {
@@ -257,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: {
@@ -327,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' }
         );
         

+ 11 - 11
src/views/User/cadmiumPrediction/totalOutputFlux.vue

@@ -88,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 
@@ -167,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' }
         );
         
@@ -183,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' }
         );
         
@@ -213,8 +213,8 @@ 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) {
@@ -253,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: {
@@ -323,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' }
         );
         

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

@@ -3,14 +3,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. 处理数据
@@ -40,27 +42,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
           },
@@ -75,15 +97,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)
@@ -95,17 +121,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>
@@ -114,5 +137,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 = {
@@ -105,12 +106,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>
 

+ 13 - 21
src/views/User/hmInFlux/grainRemoval/grainRemovalInputFlux.vue

@@ -106,6 +106,7 @@
 <script>
 import { ref, computed, onMounted } from 'vue';
 import { ElTable, ElTableColumn, ElPagination } from 'element-plus';
+import { api8000 } from '@/utils/request';  // 导入api8000
 
 export default {
   name: 'GrainRemovalResult',
@@ -161,37 +162,28 @@ export default {
     const fetchData = async () => {
       try {
         // 获取计算结果数据
-        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}`);
-        }
-        
-        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/grain-removal/visualize?area=${encodeURIComponent(props.area)}&level=county`
+        const imageResponse = await api8000.get(
+          `/api/cd-flux-removal/grain-removal/visualize?area=${encodeURIComponent(props.area)}&level=county`,
+          { responseType: 'blob' }
         );
         
-        if (!imageResponse.ok) {
-          throw new Error(`图片获取失败: ${imageResponse.statusText}`);
-        }
-        
         // 创建图片Blob URL
-        const blob = await imageResponse.blob();
+        const blob = new Blob([imageResponse.data], { type: imageResponse.headers['content-type'] });
         visualizationImage.value = URL.createObjectURL(blob);
         
         // 设置报告时间

+ 13 - 22
src/views/User/hmInFlux/strawRemoval/strawRemovalInputFlux.vue

@@ -106,6 +106,7 @@
 <script>
 import { ref, computed, onMounted } from 'vue';
 import { ElTable, ElTableColumn, ElPagination } from 'element-plus';
+import { api8000 } from '@/utils/request';  // 导入api8000
 
 export default {
   name: 'StrawRemovalResult',
@@ -161,37 +162,28 @@ export default {
     const fetchData = async () => {
       try {
         // 获取计算结果数据
-        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=county`
+        const imageResponse = await api8000.get(
+          `/api/cd-flux-removal/straw-removal/visualize?area=${encodeURIComponent(props.area)}&level=county`,
+          { responseType: 'blob' }
         );
         
-        if (!imageResponse.ok) {
-          throw new Error(`图片获取失败: ${imageResponse.statusText}`);
-        }
-        
         // 创建图片Blob URL
-        const blob = await imageResponse.blob();
+        const blob = new Blob([imageResponse.data], { type: imageResponse.headers['content-type'] });
         visualizationImage.value = URL.createObjectURL(blob);
         
         // 设置报告时间
@@ -234,7 +226,6 @@ export default {
   }
 };
 </script>
-
 <style scoped>
 /* 保留您原有的样式,只添加Element UI的自定义样式 */
 .container {

+ 5 - 5
src/views/User/hmInFlux/subsurfaceLeakage/samplingDesc3.vue

@@ -1,14 +1,14 @@
 <template>
   <div class="sampling-process">
     <div class="header-section">
-      <h2>土壤渗流</h2>
+      <h2>地下渗漏</h2>
       <div class="subtitle">重金属在土壤-农作物系统中的迁移与输出机制</div>
     </div>
     
     <div class="content-section">
       <div class="text-content">
             <p>
-              土壤渗流是农田土壤重金属输出的重要途径,过度耕种会加剧土壤水分和营养的流失,而降水会对这一过程产生影响。通常,土壤重金属渗流通量通过质量平衡模型间接计算,该模型假设土壤中未渗流的重金属通过径流途径输出。
+              地下渗漏是农田土壤重金属输出的重要途径,过度耕种会加剧土壤水分和营养的流失,而降水会对这一过程产生影响。通常,土壤地下渗漏通量通过质量平衡模型间接计算,该模型假设土壤中未渗流的重金属通过径流途径输出。
             </p>
              <p>
               图详细展示了土壤渗流作为重金属纵向迁移重要途径的过程。如图所示,土壤中的重金属在降水和重力作用下,通过土壤孔隙向下渗透,最终进入地下水系统。图中左侧部分清晰地描绘了农田土壤剖面,包括表层土壤、根系分布区以及下方的地下水层。土壤中的水分和溶解态重金属随着水流向下移动,形成明显的渗流路径。
@@ -18,15 +18,15 @@
 
     <div class="image-row">
       <div class="image-container">
-        <el-image :src="image3" alt="土壤渗流是重金属迁移的重要途径" class="sampling-image"></el-image>
+        <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>
+        <h3 class="video-title">地下渗漏过程视频演示</h3>
         <div class="video-container">
           <video controls class="sampling-video">
             <source src="@/assets/videos/地下渗漏.mp4" type="video/mp4">

+ 13 - 14
src/views/User/hmInFlux/subsurfaceLeakage/subsurfaceLeakageInputFlux.vue

@@ -38,7 +38,7 @@
 </template>
 
 <script>
-import axios from 'axios';
+import { api8000 } from '@/utils/request';  // 导入api8000
 
 export default {
   name: 'LeakageFluxMap',
@@ -64,22 +64,21 @@ 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
-        };
-        
         // 发送API请求
-        const response = await axios.get(apiUrl, {
-          params,
-          responseType: 'blob' // 接收二进制数据
-        });
+        const response = await api8000.get(
+          `/api/cd-flux-removal/groundwater_leaching/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) {

+ 13 - 15
src/views/User/hmInFlux/surfaceRunoff/surfaceRunoffInputFlux.vue

@@ -38,7 +38,7 @@
 </template>
 
 <script>
-import axios from 'axios';
+import { api8000 } from '@/utils/request';  // 导入api8000
 
 export default {
   name: 'RunoffFluxMap',
@@ -51,7 +51,6 @@ export default {
       area: '乐昌市',
       level: 'county',
       colormap: 'blues',
-      
       lastUpdated: '2025年8月21日',
     };
   },
@@ -64,22 +63,21 @@ 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) {

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

@@ -0,0 +1,463 @@
+<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>是指通过籽粒移除、秸秆移除、地下渗漏和地表径流四种主要途径
+            从农田生态系统输出的重金属总量,通常以<span class="highlight">克/公顷/年(g/ha/a)</span>为单位表示。
+          </p>
+          <p>
+            该指标综合反映了农田系统向外部环境输出的重金属污染负荷,是评估农田重金属平衡和生态风险的重要依据。
+          </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 class="content-section">
+      <div class="section-header">
+        <div class="section-number">2</div>
+        <h2>各输出途径贡献占比</h2>
+      </div>
+      
+      <div class="contribution-container">
+        <div class="contribution-card">
+          <div class="contribution-icon">🌾</div>
+          <div class="contribution-content">
+            <h3>籽粒移除</h3>
+            <p>与作物品种和重金属富集能力密切相关</p>
+            <p>计算公式:F<sub>籽粒</sub> = Y × C<sub>籽粒</sub></p>
+          </div>
+        </div>
+        
+        <div class="contribution-card">
+          <div class="contribution-icon">🌿</div>
+          <div class="contribution-content">
+            <h3>秸秆移除</h3>
+            <p>受秸秆处理方式和重金属迁移能力影响</p>
+            <p>计算公式:F<sub>秸秆</sub> = B × C<sub>秸秆</sub></p>
+          </div>
+        </div>
+        
+        <div class="contribution-card">
+          <div class="contribution-icon">💧</div>
+          <div class="contribution-content">
+            <h3>地下渗漏</h3>
+            <p>与土壤质地和降水条件相关</p>
+            <p>计算公式:F<sub>渗漏</sub> = L × C<sub>渗漏</sub></p>
+          </div>
+        </div>
+
+        <div class="contribution-card">
+          <div class="contribution-icon">🌊</div>
+          <div class="contribution-content">
+            <h3>地表径流</h3>
+            <p>受地形坡度和降雨强度影响显著</p>
+            <p>计算公式:F<sub>径流</sub> = R × C<sub>径流</sub></p>
+          </div>
+        </div>
+      </div>
+    </div>
+    
+    <!-- 第三部分:应用价值 -->
+    <div class="content-section">
+      <div class="section-header">
+        <div class="section-number">3</div>
+        <h2>输出总通量的应用价值</h2>
+      </div>
+      
+      <div class="application-container">
+        <div class="application-card">
+          <div class="application-icon">⚖️</div>
+          <div class="application-content">
+            <h3>重金属平衡评估</h3>
+            <p>通过比较输入和输出通量,评估农田重金属累积或减少趋势</p>
+          </div>
+        </div>
+        
+        <div class="application-card">
+          <div class="application-icon">🌍</div>
+          <div class="application-content">
+            <h3>环境风险预警</h3>
+            <p>识别主要输出途径,预测重金属对周边环境的污染风险</p>
+          </div>
+        </div>
+        
+        <div class="application-card">
+          <div class="application-icon">🔄</div>
+          <div class="application-content">
+            <h3>循环利用指导</h3>
+            <p>根据秸秆和籽粒重金属含量,指导农业废弃物的安全利用</p>
+          </div>
+        </div>
+        
+        <div class="application-card">
+          <div class="application-icon">📊</div>
+          <div class="application-content">
+            <h3>污染治理决策</h3>
+            <p>为制定针对性重金属污染防控措施提供科学依据</p>
+          </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'
       }

+ 3 - 3
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;

+ 34 - 16
src/views/login/loginView.vue

@@ -214,40 +214,58 @@ const showMessage = (message: string, type: "success" | "error" = "success") =>
 // ============ 登录逻辑 ============
 const onSubmit = async () => {
   if (!formRef.value) return;
+  
   try {
+    // 验证表单
     await formRef.value.validate();
     loading.value = true;
 
-    const res = await login({
+    // 调用登录API
+    const response = await login({
       name: form.name,
       password: form.password,
       usertype: userType.value,
     });
 
-    const ok = res?.data?.success === true;
-    if (!ok) {
-      showMessage(res?.data?.message || t("login.loginFailed"), "error");
-      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('缺少必要的用户字段');
+    }
 
+    // 保存用户信息到store
     store.saveToken({
-      userId,
-      name,
-      loginType: userType.value,
+      userId: Number(userData.id),
+      name: userData.name,
+      loginType: userData.userType || userType.value, // 优先使用后端返回的userType
     });
 
-    // 使用全局消息提示
-    showMessage(res.data?.message || t("login.loginSuccess"));
+    // 显示成功消息
+    showMessage(response.data.message || '登录成功');
 
-    // 跳转
-    await router.push({ name: "samplingMethodDevice1"});
+    // 跳转到目标页面
+    await router.push({ name: 'samplingMethodDevice1' });
 
   } catch (error: any) {
-    console.error("登录异常:", error);
-    showMessage(error?.response?.data?.detail || t("login.loginFailed"), "error");
+    console.error('登录失败:', {
+      error: error.message,
+      stack: error.stack,
+      response: error.response?.data
+    });
+    
+    // 显示错误消息
+    const errorMsg = error.response?.data?.detail || 
+                    error.message || 
+                    '登录失败,请检查用户名和密码';
+    showMessage(errorMsg, 'error');
   } finally {
     loading.value = false;
   }